Difference between revisions of "SHIPHarbour"

From Serious Documentation
Jump to: navigation, search
(Controlling a module)
(Connecting to SHIPHarbour Master)
 
(43 intermediate revisions by 3 users not shown)
Line 11: Line 11:
 
== Introduction ==
 
== Introduction ==
 
===SHIPHarbour API Overview===
 
===SHIPHarbour API Overview===
All Serious Integrated modules are capable of using the SHIPBridge protocol to be remote controlled over common communication interfaces (e.g., USB, UART, SPI, etc.). The SHIPBridge API is freely available to be ported to customer-defined embedded platforms. However, customers may also wish to control modules using a PC-based application. Most PC application developers are not familiar with embedded C and are not be prepared to port the SHIPBridge API to their target language. The SHIPHarbour API allows the developer to use familiar and expected design concepts of modern high-level languages.
+
All Serious Integrated modules are capable of using the SHIPBridge protocol to be remote controlled over common communication interfaces (e.g., USB, UART, SPI, etc.). The SHIPBridge API is freely available to be ported to customer-defined embedded platforms. However, customers may also wish to control modules using a PC-based application. Most PC application developers are not familiar with embedded C and are not prepared to port the SHIPBridge API to their target language. The SHIPHarbour API allows the developer to use familiar and expected design concepts of modern high-level languages.
  
 
SHIPHarbour is more than just a high-level API to execute individual SHIPBridge commands. Due to the real-time embedded nature of the platforms, we cannot expect the module to service a SHIPBridge command without either blocking or being blocked by other important processes. Therefore, most SHIPBridge commands are simplified to ensure shorter execution and response times overall. This also means multiple SHIPBridge commands may need to be issued to complete what a user might think should be a single operation. SHIPHarbour messages are designed to reduce this complexity for the high-level developer such that an operation that takes dozens of SHIPBridge commands will instead only require a single SHIPHarbour request. SHIPHarbour also facilitates multiple applications communicating with a module over singular or multiple physical connections all simultaneously. Furthermore, no changes to the API or application code are explicitly required to enable communications over new interfaces. It is not necessary here to go into detail how the SHIPHarbour messages are translated into SHIPBridge commands or how the SHIPBridge commands and responses are transmitted and received. This is because all the necessary processing is standardized and abstracted by the SHIPHarbour Master PC application. Customer applications using SHIPHarbour are expected to be implemented as clients of this program.
 
SHIPHarbour is more than just a high-level API to execute individual SHIPBridge commands. Due to the real-time embedded nature of the platforms, we cannot expect the module to service a SHIPBridge command without either blocking or being blocked by other important processes. Therefore, most SHIPBridge commands are simplified to ensure shorter execution and response times overall. This also means multiple SHIPBridge commands may need to be issued to complete what a user might think should be a single operation. SHIPHarbour messages are designed to reduce this complexity for the high-level developer such that an operation that takes dozens of SHIPBridge commands will instead only require a single SHIPHarbour request. SHIPHarbour also facilitates multiple applications communicating with a module over singular or multiple physical connections all simultaneously. Furthermore, no changes to the API or application code are explicitly required to enable communications over new interfaces. It is not necessary here to go into detail how the SHIPHarbour messages are translated into SHIPBridge commands or how the SHIPBridge commands and responses are transmitted and received. This is because all the necessary processing is standardized and abstracted by the SHIPHarbour Master PC application. Customer applications using SHIPHarbour are expected to be implemented as clients of this program.
Line 18: Line 18:
 
While it is not ultimately required to use SHIPHarbour or SHIPHarbour Master to control modules, it is certainly the fastest method for most developers looking to control modules directly from the PC environment. Part of the reason for this is the choice of sockets as the method for communicating with SHIPHarbour Master. Every modern high-level programming language will be able to use sockets and will typically have a built-in API for configuring and using sockets. Most of these APIs will also allow the developer to take advantage of multi-threading principles. This is important as the developer typically cannot predict when SHIPHarbour Master will need to send messages about the changes in the status of a module. This also avoids having to enforce strict synchronization between the customer application and SHIPHarbour Master about when requests are allowed.
 
While it is not ultimately required to use SHIPHarbour or SHIPHarbour Master to control modules, it is certainly the fastest method for most developers looking to control modules directly from the PC environment. Part of the reason for this is the choice of sockets as the method for communicating with SHIPHarbour Master. Every modern high-level programming language will be able to use sockets and will typically have a built-in API for configuring and using sockets. Most of these APIs will also allow the developer to take advantage of multi-threading principles. This is important as the developer typically cannot predict when SHIPHarbour Master will need to send messages about the changes in the status of a module. This also avoids having to enforce strict synchronization between the customer application and SHIPHarbour Master about when requests are allowed.
  
The term socket, put simply, typically refers to one half of a bidirectional Ethernet or WiFi interface which is capable of exchanging arbitrary data. However, using the correct addresses, the PC can simply transmit the data to itself without having to send the data over an actual network connection. This allows SHIPHarbour Master to exchange data with the reliability and universality provided by using the TCP/IP standard. Incidentally, this also means that it is possible for the developer to configure SHIPHarbour Master to be accessible from the network. The receive socket is typically referred to as a server since it will listen for incoming requests and pass the request data to an application to service the request. The transmit socket is referred to as a client since it merely sends requests to a server.
+
The term socket, used here, refers to one half of a bidirectional TCP/IP interface which is capable of exchanging arbitrary data. Using the correct addresses, the PC can transmit the data to itself without having to send the data over an actual network connection. This allows SHIPHarbour Master to exchange data with the reliability and universality provided by using the TCP/IP standard. Incidentally, this also means that it is possible for the developer to configure SHIPHarbour Master to be accessible from an actual network connection. The receive socket is typically referred to as a server since it will listen for incoming requests and pass the request data to an application to service the request. The transmit socket is referred to as a client since it merely sends requests to a server.
  
 
The server sockets the developer must implement are categorized as Application Control or Module Control sockets. An Application Control server socket must be active throughout the entire time the custom application is intended to communicate with SHIPHarbour Master. These sockets are used to receive spontaneous module status updates as well as other data required for the custom application to create Routine requests. There is typically only one Application Control server socket per application. In contrast, multiple Module Control server sockets may be created. These sockets are used to receive any Routine status updates to a previously issued Routine request.
 
The server sockets the developer must implement are categorized as Application Control or Module Control sockets. An Application Control server socket must be active throughout the entire time the custom application is intended to communicate with SHIPHarbour Master. These sockets are used to receive spontaneous module status updates as well as other data required for the custom application to create Routine requests. There is typically only one Application Control server socket per application. In contrast, multiple Module Control server sockets may be created. These sockets are used to receive any Routine status updates to a previously issued Routine request.
Line 30: Line 30:
 
===File structure===
 
===File structure===
 
The following files define the SHIPHarbour API:
 
The following files define the SHIPHarbour API:
* '''SHIPHarbour_DataTypes.proto -''' Defines the basic types and message structures used as fields in other messages
+
* '''[[SHIPHarbour:DataTypes|SHIPHarbour_DataTypes.proto]]''' - Defines the basic types and message structures used as fields in other messages
* '''SHIPHarbour_Generic.proto -''' Defines the only message that is allowed to be sent over a socket
+
* '''[[SHIPHarbour:Generic|SHIPHarbour_Generic.proto]]''' - Defines the wrapper message that is used to send all other messages
* '''SHIPHarbour_Master.proto -''' Defines the message structures required to issue control requests
+
* '''[[SHIPHarbour:Master|SHIPHarbour_Master.proto]]''' - Defines the message structures required to execute SHIPHarbour Master control Routines
* '''SHIPHarbour_Firmware.proto -''' Defines the message structures required to execute firmware-related Routines
+
* '''[[SHIPHarbour:Platform|SHIPHarbour_Platform.proto]]''' - Defines the message structures required to execute platform-related Routines
* '''SHIPHarbour_Platform.proto -''' Defines the message structures required to execute platform-related Routines
+
* '''[[SHIPHarbour:Firmware|SHIPHarbour_Firmware.proto]]''' - Defines the message structures required to execute firmware-related Routines
 +
* '''[[SHIPHarbour:Files|SHIPHarbour_Files.proto]]''' - Defines the message structures required to execute file-related Routines
 +
* '''[[SHIPHarbour:XVars|SHIPHarbour_XVars.proto]]''' - Defines the message structures required to execute external-variable-related Routines
  
 
===Keywords===
 
===Keywords===
Line 43: Line 45:
 
* '''repeated -''' indicates the field should be accessed as a list, may be empty
 
* '''repeated -''' indicates the field should be accessed as a list, may be empty
  
<div class="code-no-border">
+
 
 
A simple example of proto3 syntax:
 
A simple example of proto3 syntax:
  <code>message RoutineInfo<span class="numbered-example">1</span> {</code>
+
<div class="code-no-border fake-source">
  <code>    Endpoint<span class="numbered-example">2</span> ep<span class="numbered-example">3</span> = 1<span class="numbered-example">4</span>;</code> '''
+
  <code><span style="color: #008000;">message</span> <span style="color: #008080;">RoutineInfo</span><span class="numbered-example">1</span> {</code>
  <code>    UInt32Value<span class="numbered-example">5</span> id = 2;</code>
+
  <code>    Endpoint<span class="numbered-example">2</span>    <span style="color: #45b64b;">ep</span><span class="numbered-example">3</span>   <span style="color: #00b3b3;">=</span> <span style="color: #8d0;">1</span><span class="numbered-example">4</span>;</code> '''
  <code>    bool<span class="numbered-example">6</span> start = 3;</code>
+
  <code>    UInt32Value<span class="numbered-example">5</span> <span style="color: #45b64b;">id</span>    <span style="color: #00b3b3;">=</span> <span style="color: #8d0;">2</span>;</code>
 +
  <code>    <span style="color: #ed7b32;">bool</span><span class="numbered-example">6</span>        <span style="color: #45b64b;">start</span> <span style="color: #00b3b3;">=</span> <span style="color: #8d0;">3</span>;</code>
 
  <code>}</code>
 
  <code>}</code>
 
</div>
 
</div>
  
<span class="numbered-definition">1</span> The name that will be used to refer to this type of message
+
<span class="numbered-definition">1</span> The name that will be used to refer to this type of message<br/>
 
+
<span class="numbered-definition">2</span> A previously defined message type contained within this message definition<br/>
<span class="numbered-definition">2</span> A previously defined message type contained within this message definition
+
<span class="numbered-definition">3</span> The name that will be used to refer to this field of the message<br/>
 
+
<span class="numbered-definition">4</span> An internally used number that uniquely identifies a field<br/>
<span class="numbered-definition">3</span> The name that will be used to refer to this field of the message
+
<span class="numbered-definition">5</span> A wrapper for a primitive data type used to facilitate detecting if the field is present in a received message<br/>
 
 
<span class="numbered-definition">4</span> An internally used number that uniquely identifies a field
 
 
 
<span class="numbered-definition">5</span> A wrapper for a primitive data type used to facilitate detecting if the field is present in a received message
 
 
 
 
<span class="numbered-definition">6</span> A primitive data type, default values are always 0 (no distinction can be made between the assigned value being equivalent to the default and the value not having been assigned)
 
<span class="numbered-definition">6</span> A primitive data type, default values are always 0 (no distinction can be made between the assigned value being equivalent to the default and the value not having been assigned)
  
For more details on the proto3 syntax, go to <ext>https://developers.google.com/protocol-buffers/docs/proto3</ext>
+
For more details on the proto3 syntax, go to https://developers.google.com/protocol-buffers/docs/proto3.
  
In order to avoid name collisions with other libraries, the typical naming scheme for SHIPHarbour API structures will start with something similar to “SHIPHarbour.Message.followed by a shortened version of the filename of the .proto file in which the structure is defined, after which, the specific definition will appear. The full namespace for creating a message to cancel a previously requested Routine will look something like: “SHIPHarbour.Message.Generic.Routine.Cancel“. The exact syntax will depend on the target language being used. Additional details may be found at <ext>https://developers.google.com/protocol-buffers/docs/reference/overview</ext>
+
In order to avoid name collisions with other libraries, the typical naming scheme for SHIPHarbour API structures will start with something similar to "SHIPHarbour.Message." followed by a shortened version of the filename of the .proto file in which the structure is defined, after which, the specific definition will appear. For example, the full namespace for creating a message to cancel a previously requested Routine may look something like: "SHIPHarbour.Message.Generic.Routine.Cancel". The exact syntax will depend on the target language being used. For additional details, go to https://developers.google.com/protocol-buffers/docs/reference/overview.
  
 
===SHIPHarbour Messages===
 
===SHIPHarbour Messages===
Line 72: Line 70:
  
 
* Examples of control messages
 
* Examples of control messages
** SHIPHarbour.Message.Master.Open
+
** [[SHIPHarbour:Master:Open|SHIPHarbour.Message.Master.Open]]
** SHIPHarbour.Message.Master.Close
+
** [[SHIPHarbour:Master:ModuleInfo|SHIPHarbour.Message.Master.ModuleInfo]]
** SHIPHarbour.Message.Master.PluginInfo
 
** SHIPHarbour.Message.Master.ModuleInfo
 
 
* Examples of routine messages
 
* Examples of routine messages
** SHIPHarbour.Message.Platform.SetState
+
** [[SHIPHarbour:Platform:SetState|SHIPHarbour.Message.Platform.SetState]]
** SHIPHarbour.Message.Firmware.Installed
+
** [[SHIPHarbour:Firmware:Update|SHIPHarbour.Message.Firmware.Installed]]
** SHIPHarbour.Message.Firmware.Update
+
** [[SHIPHarbour:XVars:Exchange|SHIPHarbour.Message.XVars.Exchange]]
** SHIPHarbour.Message.Firmware.Remove
 
  
==Usage==
+
==Using Protobufs==
 
===Building messages===
 
===Building messages===
 
While the exact code for constructing a SHIPHarbour message using the protobuf API is language dependent, there are a few things to note:
 
While the exact code for constructing a SHIPHarbour message using the protobuf API is language dependent, there are a few things to note:
* All messages sent to or received from SHIPHarbour Master must use the Generic message as a wrapper.
+
:* All messages sent to or received from SHIPHarbour Master must use the Generic message as a wrapper.
  
* All messages must have the version field set.
+
:* All messages must have the version field set.
  
* All messages must have exactly one of the fields of the payload field set.
+
:* All messages must have exactly one of the fields of the payload field set.
  
* A message which is not a response to a previous request should not have the error field set.
+
:* A message which is not a response to a previous request should not have the error field set.
  
<div class="code-no-border">
+
C++ Example:
C++ example:
+
<source lang="cpp">
<code>SHIPHarbour::Message::Generic generic;</code>
+
SHIPHarbour::Message::Generic generic;
<code>generic.set_version(<span style="color: #249900;">/* version */</span>);</code>
+
generic.set_version(/* version */);
<code>SHIPHarbour::Message::Master::Open * pOpen = generic.mutable_open();</code>
+
 
<code>pOpen->mutable_host()->set_value(<span style="color: #249900;">/* host address string */</span>);</code>
+
SHIPHarbour::Message::Master::Open * pOpen = generic.mutable_open();
<code>pOpen->mutable_port()->set_value(<span style="color: #249900;">/* port number */</span>);</code>
+
pOpen->mutable_host()->set_value(/* host address string */);
</div>
+
pOpen->mutable_port()->set_value(/* port number */);
 +
</source>
  
<div class="code-no-border">
 
 
Java example:
 
Java example:
<code>StringValue host = StringValue.newBuilder()</code>
+
<source lang="java">
<code>    .setValue(<span style="color: #249900;">/* host address string */</span>);</code>
+
StringValue host = StringValue.newBuilder()
<code>    .build();</code>
+
.setValue(/* host address string */)
<code></code>
+
.build();
<code>UInt32Value port = UInt32Value.newBuilder()</code>
+
 
<code>    .setValue(<span style="color: #249900;">/* port number */</span>);</code>
+
UInt32Value port = UInt32Value.newBuilder()
<code>    .build();</code>
+
.setValue(/* port number */)
<code></code>
+
.build();
<code>Open open = Open.newBuilder()</code>
+
 
<code>    .setHost(host)</code>
+
Open open = Open.newBuilder()
<code>    .setPort(port)</code>
+
.setHost(host)
<code>    .build();</code>
+
.setPort(port)
<code></code>
+
.build();
<code>Generic generic = Generic.newBuilder()</code>
+
 
<code>    .setVersion(<span style="color: #249900;">/* version */</span>);</code>
+
Generic generic = Generic.newBuilder()
<code>    .setOpen(open)</code>
+
.setVersion(/* version */)
<code>    .build();</code>
+
.setOpen(open)
</div>
+
.build();
 +
</source>
  
 
For more information on using protobufs in the target language, visit https://developers.google.com/protocol-buffers/docs/reference/overview
 
For more information on using protobufs in the target language, visit https://developers.google.com/protocol-buffers/docs/reference/overview
Line 128: Line 124:
 
By default, the serialized protobuf does not include a description of the full size of the message. In order to allow sending messages as fast as possible, the SHIPHarbour API requires that the byte size of the message to be parsed appear as a varint-encoded 32-bit integer immediately before the message bytes. For some languages, the protobuf API implements a set of functions named similarly to writeDelimitedTo and parseDelimitedFrom. All messages must either be encoded/decoded using these functions, or the developer must implement the varint encoding/decoding of the message size manually.
 
By default, the serialized protobuf does not include a description of the full size of the message. In order to allow sending messages as fast as possible, the SHIPHarbour API requires that the byte size of the message to be parsed appear as a varint-encoded 32-bit integer immediately before the message bytes. For some languages, the protobuf API implements a set of functions named similarly to writeDelimitedTo and parseDelimitedFrom. All messages must either be encoded/decoded using these functions, or the developer must implement the varint encoding/decoding of the message size manually.
  
<div class="code-no-border">
 
 
C++ example:
 
C++ example:
<code style="color: #249900;">/*</code>
+
<source lang="cpp">
<code style="color: #249900;"> * Least-significant byte first, most-significant bit is 1 when more bytes. There can be up to 5 bytes of size information.</code>
+
 
  <code style="color: #249900;"> */</code>
+
/*  
<code>void parseVarInt32(char * pSrc, int * pVarInt, int * pIndex) {</code>
+
  Least-significant byte first, most-significant bit is 1 when more bytes.  
<code>    char data;</code>
+
  There can be up to 5 bytes of size information.
<code>    *pVarInt = 0;</code>
+
  */
<code>    do {</code>
+
void parseVarInt32(char * pSrc, int * pVarInt, int * pIndex) {
<code>        if (pSrc->size() <= *pIndex) {</code>
+
char data;
<code>            break;</code>
+
*pVarInt = 0;
<code>        }</code>
+
do {
<code>        data     = pSrc[*pIndex];</code>
+
if (pSrc->size() <= *pIndex) {
<code>        *pVarInt = (data & 0x7F) << ((*pIndex)++ * 7);</code>
+
break;
<code>    } while ((data & 0x80) && (*pIndex < 6));</code>
+
}
<code>}</code>
+
data = pSrc[*pIndex];
<code></code>
+
*pVarInt = (data & 0x7F) << ((*pIndex)++ * 7);
<code>void serializeVarInt32(char * pDst, int * pIndex, int varint) {</code>
+
} while ((data & 0x80) && (*pIndex < 6));
<code>    char data;</code>
+
}
<code>    do {</code>
+
 
<code>        data     = varint & 0x7F;</code>
+
void serializeVarInt32(char * pDst, int * pIndex, int varint) {
<code>        varint   = varint >> 7;</code>
+
char data;
<code>        if (varint) {</code>
+
do {
<code>            data = data | 0x80;</code>
+
data = varint & 0x7F;  
<code>        }</code>
+
varint = varint >> 7;
<code>        pDst[(*pIndex)++] = data;</code>
+
if (varint) {
<code>    } while (varint);</code>
+
data = data | 0x80;  
<code>}</code>
+
}
<code></code>
+
pDst[(*pIndex)++] = data;
<code>ByteArray msg = new ByteArray(5 + generic.ByteSize());</code>
+
} while (varint);
<code></code>
+
}
<code>int index;</code>
+
 
<code>serializeVarInt32(msg.data(), &index, generic.ByteSize());</code>
+
ByteArray msg = new ByteArray(5 + generic.ByteSize());
<code></code>
+
 
<code>generic.SerializeToArray(&(msg.data()[index]), generic.ByteSize());</code>
+
int index;
<code>pSocket->write(msg);</code>
+
serializeVarInt32(msg.data(), &index, generic.ByteSize());
 +
 
 +
generic.SerializeToArray(&(msg.data()[index]), generic.ByteSize());
 +
pSocket->write(msg);
 +
 
 +
</source>
 +
 
 +
Java example:
 +
<div class="warning-code">
 +
<source lang="java">
 +
ByteArrayOutputStream bos = new ByteArrayOutputStream();
 +
generic.writeDelimitedTo(bos);
 +
socket.getOutputStream().write(bos.toByteArray());
 +
</source>
 
</div>
 
</div>
<div class="code-no-border">
+
<div style="margin: 0 0 16px; padding: 25px; color: #ff0000; font-weight: bold; background: #222; border-top: solid 1px #ff0000;">
Java example:
+
<p style="margin: 0;">WARNING</p>
<code>ByteArrayOutputStream bos = new ByteArrayOutputStream();</code>
+
<p style="margin: 0;">In the above Java example, notice that the protobuf API was not used to write directly to the socket’s output stream. At the time of writing, there is a bug which will unpredictably cause messages written this way to become corrupted on the receiving end. This bug has also been observed when using languages other than Java. As a result, it is highly recommended to avoid using the protobuf API to directly access a socket. The protobuf API should be used to read from or write to an intermediate structure.</p>
<code>generic.writeDelimitedTo(bos);</code>
 
<code>socket.getOutputStream().write(bos.toByteArray());</code>
 
 
</div>
 
</div>
 
===<span style="color: #cc0000;display: block;border-bottom: solid 2px #cc0000;">WARNING</span>===
 
In the above Java example, notice that the protobuf API was not used to write directly to the socket’s output stream. At the time of writing, there exists some bug which will unpredictably cause messages written this way to become corrupted on the receiving end. This bug has also been observed when using languages other than Java. As such, it is highly recommended to avoid using the protobuf API to directly access a socket. The protobuf API should be used to read from or write to an intermediate structure.
 
  
 
===Available Language Bindings===
 
===Available Language Bindings===
Line 191: Line 194:
 
# Download a pre-built version of the protobuf library
 
# Download a pre-built version of the protobuf library
 
# Compile the proto files for the target language
 
# Compile the proto files for the target language
#* All of the .proto files for the SHIPHarbour API should be compiled with the protobuf compiler "protoc" using the following command structure:<br/> <pre>protoc --<language_switch>=<destination_dir> --proto_path=<source_dir></pre>
+
#* All of the .proto files for the SHIPHarbour API should be compiled with the protobuf compiler "protoc" using the following command structure:<br/> <source lang="protobuf">protoc --<language_switch>=<destination_dir> --proto_path=<source_dir></source>
#* Examples of language switch: <code>--cpp_out</code> <code>--csharp_out</code> <code>--java_out</code><br/>
+
#* Examples of language switch:<br/><source lang="protobuf">
#** Use the following command to find the appropriate switch for the target language:<br/><pre>protoc --help</pre>
+
--cpp_out
 +
--csharp_out
 +
--java_out
 +
</source>
 +
#* Use the following command to find the appropriate switch for the target language:<br/><source lang="protobuf">protoc --help</source>
 
#* The destination directory can be any location and is where the generated files will appear<br/>
 
#* The destination directory can be any location and is where the generated files will appear<br/>
 
#* The source directory must be the location of all of the SHIPHarbour .proto files.
 
#* The source directory must be the location of all of the SHIPHarbour .proto files.
  
 
===Connecting to SHIPHarbour Master===
 
===Connecting to SHIPHarbour Master===
 +
[[File:Hmaster connection flowchart.png|350px|right]]
 
The below steps should be followed to establish connection between SHIPHarbour Master and another application:
 
The below steps should be followed to establish connection between SHIPHarbour Master and another application:
  
* Open a receive socket on any port (referred to as Socket_AppControl_Rx).
+
:* Open a receive socket on any port (referred to as {{Socket|Socket_AppControl_Rx}}).
* Open a transmit socket to SHIPHarbour Master, the default port is 10400 (referred to as Socket_Announce_Tx).
+
:* Open a transmit socket to SHIPHarbour Master, the default port is 10400 (referred to as {{Socket|Socket_Announce_Tx}}).
* Send an Open message into Socket_Announce_Tx with your host address and the port number used by Socket_AppControl_Rx.
+
:* Send an [[SHIPHarbour:Master:Open|Open]] message into {{Socket|Socket_Announce_Tx}} with your host address and the port number used by {{Socket|Socket_AppControl_Rx}}.
* Socket_Announce_Tx will be forcibly closed when the message is received as it is no longer needed.
+
:* {{Socket|Socket_Announce_Tx}} will be forcibly closed when the message is received as it is no longer needed.
* An Open message will be sent into Socket_AppControl_Rx describing how to create Socket_AppControl_Tx.
+
:* An [[SHIPHarbour:Master:Open|Open]] message will be sent into {{Socket|Socket_AppControl_Rx}} describing how to create {{Socket|Socket_AppControl_Tx}}.
* A PluginInfo message will be sent into Socket_AppControl_Rx describing what plugins SHIPHarbour Master has loaded.
+
:* A [[SHIPHarbour:Master:PluginInfo |PluginInfo]] message will be sent into {{Socket|Socket_AppControl_Rx}} describing what plugins SHIPHarbour Master has loaded.
* A ModuleInfo message will be sent into Socket_AppControl_Rx describing which modules are currently connected to SHIPHarbour Master.
+
:* A [[SHIPHarbour:Master:ModuleInfo |ModuleInfo]] message will be sent into {{Socket|Socket_AppControl_Rx}} describing which modules are currently connected to SHIPHarbour Master.
* It is now possible to issue control messages.
+
:* It is now possible to issue application-level control messages using {{Socket|Socket_AppControl_Tx}}.
* The above sequence corresponds to the first half of the flow chart.
+
:* The above sequence corresponds to the first half of the flow chart.
  
 
===Getting information on connected modules===
 
===Getting information on connected modules===
  
While it is possible to poll this information by sending a ModuleInfo message into Socket_AppControl_Tx, it is not recommended or necessary to do so. SHIPHarbour Master will always send a ModuleInfo message into Socket_AppControl_Rx whenever the connection status of a module or one of the endpoints changes.
+
While it is possible to poll this information by sending a ModuleInfo message into {{Socket|Socket_AppControl_Tx}}, it is not recommended or necessary to do so. SHIPHarbour Master will always send a ModuleInfo message into {{Socket|Socket_AppControl_Rx}} whenever the connection status of a module or one of the endpoints changes.
  
 
===Connecting To a Module===
 
===Connecting To a Module===
 
The below steps should be followed to establish connection between a custom PC application and a module:
 
The below steps should be followed to establish connection between a custom PC application and a module:
  
* Open a receive socket on any port (referred to as Socket_ModuleControl_Rx).
+
:* Open a receive socket on any port (referred to as {{Socket|Socket_ModuleControl_Rx}}).
* Send an Open message into Socket_AppControl_Rx with your host address and the port number used by Socket_ModuleControl_Rx.
+
:* Send an [[SHIPHarbour:Master:Open|Open]] message into {{Socket|Socket_AppControl_Tx}} with your host address and the port number used by {{Socket|Socket_ModuleControl_Rx}}.
* An Open message will be sent into Socket_ModuleControl_Rx describing how to create Socket_ModuleControl_Tx.
+
:* An [[SHIPHarbour:Master:Open|Open]] message will be sent into {{Socket|Socket_ModuleControl_Rx}} describing how to create {{Socket|Socket_ModuleControl_Tx}}.
* It is now possible to issue routine messages to a specified module.
+
:* It is now possible to issue routine messages to a specified module using {{Socket|Socket_ModuleControl_Tx}}.
* The above sequence corresponds to the second half of the flow chart.
+
:* The above sequence corresponds to the second half of the flow chart.
  
 
===Using ModuleInfo Messages===
 
===Using ModuleInfo Messages===
SHIPHarbour_Master.proto:
 
<div class="code-no-border">
 
<code>message ModuleInfo {</code>
 
<code>    repeated DataTypes.ModuleDetails info = 1;</code>
 
<code>}</code>
 
 
</div>
 
 
 
A ModuleInfo message has only one field which is marked as a repeatable of type ModuleDetails. This means that a single ModuleInfo message may contain the information for multiple modules so the field must be accessed as a list, array, or some other collection of ModuleDetails depending on the target language.
 
A ModuleInfo message has only one field which is marked as a repeatable of type ModuleDetails. This means that a single ModuleInfo message may contain the information for multiple modules so the field must be accessed as a list, array, or some other collection of ModuleDetails depending on the target language.
  
 
Detection of whether a module has become connected or disconnected must be done per application. SHIPHarbour Master does not maintain a list of what modules each connected PC application has seen. When any one module (or endpoint within a module) changes state, the current information for all modules is broadcasted to all connected PC applications. The PC application must keep a record of known modules and endpoints and then compare and manage this record accordingly as new ModuleInfo messages are received from SHIPHarbour Master.
 
Detection of whether a module has become connected or disconnected must be done per application. SHIPHarbour Master does not maintain a list of what modules each connected PC application has seen. When any one module (or endpoint within a module) changes state, the current information for all modules is broadcasted to all connected PC applications. The PC application must keep a record of known modules and endpoints and then compare and manage this record accordingly as new ModuleInfo messages are received from SHIPHarbour Master.
  
SHIPHarbour_DataTypes.proto:
 
<div class="code-no-border">
 
<code>message ModuleDetails {</code>
 
<code>    Module      module            = 1;</code>
 
<code>    UInt32Value family            = 2;</code>
 
<code>    UInt32Value version          = 3;</code>
 
<code>    UInt32Value variant          = 4;</code>
 
<code>    UInt32Value lcdCode          = 5;</code>
 
<code>    UInt32Value id                = 6;</code>
 
<code>    Firmware    currentProgram    = 7;</code>
 
<code>}</code>
 
<code></code>
 
<code>message Module {</code>
 
<code>                Fixed64Value      sn          = 1;</code>
 
<code>                BytesValue        product    = 2;</code>
 
<code>    repeated    Endpoint          version    = 3;</code>
 
<code>}</code>
 
</div>
 
  
 +
SHIPHarbour_Master.proto:
 +
<source lang="protobuf">
 +
message ModuleInfo {
 +
    repeated DataTypes.ModuleDetails info = 1;
 +
}
 +
</source>
 
The ModuleDetails that are contained within a ModuleInfo message contain some fields which may be considered superfluous by the PC application. The field that all PC applications must pay attention to is the module field of type Module. The Module datatype contains the unique serial number of the SIM or SCM as a 64-bit value and, more importantly, a list of Endpoint data describing how the module is currently connected to SHIPHarbour Master.
 
The ModuleDetails that are contained within a ModuleInfo message contain some fields which may be considered superfluous by the PC application. The field that all PC applications must pay attention to is the module field of type Module. The Module datatype contains the unique serial number of the SIM or SCM as a 64-bit value and, more importantly, a list of Endpoint data describing how the module is currently connected to SHIPHarbour Master.
  
 
SHIPHarbour_DataTypes.proto:
 
SHIPHarbour_DataTypes.proto:
<div class="code-no-border">
 
<code>message Endpoint {</code>
 
<code>    UInt32Value id      = 1;</code>
 
<code>    UInt32Value plugin  = 2;</code>
 
<code>    UInt64Value speed  = 3;</code>
 
<code>}</code>
 
  
</div>
+
<source lang="protobuf">
 +
message ModuleDetails {
 +
    Module      module          = 1;
 +
    UInt32Value family          = 2;
 +
    UInt32Value version        = 3;
 +
    UInt32Value variant        = 4;
 +
    UInt32Value lcdCode        = 5;
 +
    UInt32Value id              = 6;
 +
    Firmware    currentProgram  = 7;
 +
}
 +
 +
message Module {
 +
                Fixed64Value  sn      = 1;
 +
                BytesValue    product  = 2;
 +
    repeated    Endpoint      version  = 3;
 +
}
 +
</source>
 +
 
  
 
The Endpoint datatype has an id field which is required to send all routine messages. These directly correspond to some physical communications interface over which the module is using SHIPBridge to communicate with SHIPHarbour Master. The PC application must always supply a valid Endpoint within a routine message in order to direct SHIPHarbour Master to begin executing a SHIPBridge operation over a specific physical interface. As the module may reset or otherwise lose the SHIPBridge connection context with SHIPHarbour Master, the id of the Endpoint will change over time. The PC application is responsible for recognizing when the id of a previously known Endpoint has changed and transitioning future operations or in-progress multi-request operations to use the new id.
 
The Endpoint datatype has an id field which is required to send all routine messages. These directly correspond to some physical communications interface over which the module is using SHIPBridge to communicate with SHIPHarbour Master. The PC application must always supply a valid Endpoint within a routine message in order to direct SHIPHarbour Master to begin executing a SHIPBridge operation over a specific physical interface. As the module may reset or otherwise lose the SHIPBridge connection context with SHIPHarbour Master, the id of the Endpoint will change over time. The PC application is responsible for recognizing when the id of a previously known Endpoint has changed and transitioning future operations or in-progress multi-request operations to use the new id.
  
===Controlling a module===
+
SHIPHarbour_DataTypes.proto:
 +
<source lang="protobuf">
 +
message Endpoint {
 +
    UInt32Value id      = 1;
 +
    UInt32Value plugin  = 2;
 +
    UInt64Value speed  = 3;
 +
}
 +
</source>
 +
 
 +
===Controlling a Module===
 
Any operation that can be performed on a module is always done by sending what are classified as routine messages. Routine messages are defined in files according to what feature of the module will be accessed. Due to the way Generic messages are used, all routine messages that can possibly be sent can be seen in the SHIPHarbour_Generic.proto file. The following is an example of sending a routine message to retrieve information about what firmware files are installed on a module:
 
Any operation that can be performed on a module is always done by sending what are classified as routine messages. Routine messages are defined in files according to what feature of the module will be accessed. Due to the way Generic messages are used, all routine messages that can possibly be sent can be seen in the SHIPHarbour_Generic.proto file. The following is an example of sending a routine message to retrieve information about what firmware files are installed on a module:
  
 
C++ example:
 
C++ example:
<div class="code-no-border">
+
<source lang="cpp">
<code>SHIPHarbour::Message::Generic generic;</code>
+
SHIPHarbour::Message::Generic generic;
<code>generic.set_version(<span style="color: #249900;">/* version */</span>);</code>
+
generic.set_version(/* version */);
<code></code>
+
 
<code style="color: #249900;">/*</code>
+
/*  
<code style="color: #249900;"> * This gets us a new instance of a Routine and sets the payload field of the Generic to this new instance.</code>
+
  This gets us a new instance of a Routine and sets the payload field of the
<code style="color: #249900;"> */</code>
+
  Generic to this new instance.
<code>SHIPHarbour::Message::Generic::Routine * pRoutine = generic.mutable_routine();</code>
+
*/
<code>SHIPHarbour::Message::DataTypes::RoutineInfo * pInfo = pRoutine->mutable_info();</code>
+
SHIPHarbour::Message::Generic::Routine * pRoutine = generic.mutable_routine();
<code>pInfo->mutable_ep()->mutable_id()->set_value(<span style="color: #249900;">/* known endpoint id */</span>);</code>
+
SHIPHarbour::Message::DataTypes::RoutineInfo * pInfo = pRoutine->mutable_info();
<code>pInfo->mutable_id()->set_value(<span style="color: #249900;">/* caller-generated unique id */</span>);</code>
+
pInfo->mutable_ep()->mutable_id()->set_value(/* known endpoint id */);
<code>pInfo->set_start(true);</code>
+
pInfo->mutable_id()->set_value(/* caller-generated unique id */);  
<code></code>
+
pInfo->set_start(true);
<code style="color: #249900;">/*</code>
+
 
<code style="color: #249900;"> * This sets the routine field of the Routine to a new instance of an Installed message.</code>
+
/*  
<code style="color: #249900;"> * Since there are no outgoing parameters required, we don’t need to do anything with it.</code>
+
  This sets the routine field of the Routine to a new instance of an Installed
<code style="color: #249900;"> */</code>
+
  message. Since there are no outgoing parameters required, we don’t  
<code>pRoutine->mutable_installed();</code>
+
  need to save the return value.
<code></code>
+
*/
<code>ByteArray msg = new ByteArray(5 + generic.ByteSize())</code>
+
pRoutine->mutable_installed();
<code></code>
+
 
<code>int index;</code>
+
ByteArray msg = new ByteArray(5 + generic.ByteSize());
<code>serializeVarInt32(msg.data(), &index, generic.ByteSize());</code>
+
 
<code>generic.SerializeToArray(&(msg.data()[index]), generic.ByteSize());</code>
+
int index;
<code>pSocket->write(msg);</code>
+
serializeVarInt32(msg.data(), &index, generic.ByteSize());
</div>
+
 
 +
generic.SerializeToArray(&(msg.data()[index]), generic.ByteSize());
 +
pSocket->write(msg);
 +
</source>
  
 
Java example:
 
Java example:
 
<div class="code-no-border">
 
<div class="code-no-border">
<code>Endpoint ep = Endpoint.newBuilder()</code>
+
<source lang="java">
<code>    .setId(<span style="color: #249900;">/* known endpoint id */</span>)</code>
+
Endpoint ep = Endpoint.newBuilder()
<code>    .build();</code>
+
.setId(/* known endpoint id */)
<code></code>
+
.build();
<code>RoutineInfo info = RoutineInfo.newBuilder()</code>
+
 
<code>    .setEp(ep)</code>
+
RoutineInfo info = RoutineInfo.newBuilder()
<code>    .setId(<span style="color: #249900;">/* caller-generated unique id */</span>)</code>
+
.setEp(ep)
<code>    .start(true)</code>
+
.setId(/* caller-generated unique id */)
<code>    .build();</code>
+
.start(true)
<code></code>
+
.build();
<code style="color: #249900;">/*</code>
+
 
<code style="color: #249900;"> * There are no outgoing parameters required, so we build the Installed message only as a parameter to the Routine builder.</code>
+
/*  
<code style="color: #249900;"> */</code>
+
  There are no outgoing parameters required, so we build the Installed message
<code>Routine routine = Routine.newBuilder()</code>
+
  only as a parameter to the Routine builder.
<code>    .setInfo(info)</code>
+
*/
<code>    .setInstalled(Installed.newBuilder().build())</code>
+
Routine routine = Routine.newBuilder()
<code>    .build();</code>
+
.setInfo(info)
<code></code>
+
.setInstalled(Installed.newBuilder().build())
<code>Generic generic = Generic.newBuilder()</code>
+
.build();
<code>    .setVersion(<span style="color: #249900;">/* version */</span>)</code>
+
 
<code>    .setRoutine(routine)</code>
+
Generic generic = Generic.newBuilder()
<code>    .build();</code>
+
.setVersion(/* version */)
<code></code>
+
.setRoutine(routine)
<code>ByteArrayOutputStream bos = new ByteArrayOutputStream();</code>
+
.build();
<code>generic.writeDelimitedTo(bos);</code>
+
 
<code>socket.getOutputStream().write(bos.toByteArray());</code>
+
ByteArrayOutputStream bos = new ByteArrayOutputStream();
 +
generic.writeDelimitedTo(bos) ;
 +
socket.getOutputStream().write(bos.toByteArray());
 +
</source>
 
</div>
 
</div>
 +
 +
[[Category:SHIPHarbour]]

Latest revision as of 10:28, 3 December 2018

Glossary of Terms

  • Socket - A communications pipe that uses TCP/IP to send or receive data (not both)
  • protobuf API - The Google protocol buffer library of functions and objects used to encode and decode data across different programming languages
  • SHIPHarbour API - The custom protocol defined by Serious Integrated and written using protobuf syntax
  • SHIPHarbour Language Binding - A language-specific library of functions intended to make the SHIPHarbour API more convenient for use in the target language
  • SHIPHarbour Master - An application developed by Serious Integrated which translates SHIPHarbour API messages to SHIPBridge API commands
  • SHIPBridge API - The proprietary protocol and library of functions defined by Serious Integrated and written in embedded C
  • Message - A series of bytes representing either a Google protocol buffer or a SHIPBridge command/response
  • Routine - A Google protocol buffer message containing instructions to execute one or more SHIPBridge commands

Introduction

SHIPHarbour API Overview

All Serious Integrated modules are capable of using the SHIPBridge protocol to be remote controlled over common communication interfaces (e.g., USB, UART, SPI, etc.). The SHIPBridge API is freely available to be ported to customer-defined embedded platforms. However, customers may also wish to control modules using a PC-based application. Most PC application developers are not familiar with embedded C and are not prepared to port the SHIPBridge API to their target language. The SHIPHarbour API allows the developer to use familiar and expected design concepts of modern high-level languages.

SHIPHarbour is more than just a high-level API to execute individual SHIPBridge commands. Due to the real-time embedded nature of the platforms, we cannot expect the module to service a SHIPBridge command without either blocking or being blocked by other important processes. Therefore, most SHIPBridge commands are simplified to ensure shorter execution and response times overall. This also means multiple SHIPBridge commands may need to be issued to complete what a user might think should be a single operation. SHIPHarbour messages are designed to reduce this complexity for the high-level developer such that an operation that takes dozens of SHIPBridge commands will instead only require a single SHIPHarbour request. SHIPHarbour also facilitates multiple applications communicating with a module over singular or multiple physical connections all simultaneously. Furthermore, no changes to the API or application code are explicitly required to enable communications over new interfaces. It is not necessary here to go into detail how the SHIPHarbour messages are translated into SHIPBridge commands or how the SHIPBridge commands and responses are transmitted and received. This is because all the necessary processing is standardized and abstracted by the SHIPHarbour Master PC application. Customer applications using SHIPHarbour are expected to be implemented as clients of this program.

Sockets Used in SHIPHarbour Master

While it is not ultimately required to use SHIPHarbour or SHIPHarbour Master to control modules, it is certainly the fastest method for most developers looking to control modules directly from the PC environment. Part of the reason for this is the choice of sockets as the method for communicating with SHIPHarbour Master. Every modern high-level programming language will be able to use sockets and will typically have a built-in API for configuring and using sockets. Most of these APIs will also allow the developer to take advantage of multi-threading principles. This is important as the developer typically cannot predict when SHIPHarbour Master will need to send messages about the changes in the status of a module. This also avoids having to enforce strict synchronization between the customer application and SHIPHarbour Master about when requests are allowed.

The term socket, used here, refers to one half of a bidirectional TCP/IP interface which is capable of exchanging arbitrary data. Using the correct addresses, the PC can transmit the data to itself without having to send the data over an actual network connection. This allows SHIPHarbour Master to exchange data with the reliability and universality provided by using the TCP/IP standard. Incidentally, this also means that it is possible for the developer to configure SHIPHarbour Master to be accessible from an actual network connection. The receive socket is typically referred to as a server since it will listen for incoming requests and pass the request data to an application to service the request. The transmit socket is referred to as a client since it merely sends requests to a server.

The server sockets the developer must implement are categorized as Application Control or Module Control sockets. An Application Control server socket must be active throughout the entire time the custom application is intended to communicate with SHIPHarbour Master. These sockets are used to receive spontaneous module status updates as well as other data required for the custom application to create Routine requests. There is typically only one Application Control server socket per application. In contrast, multiple Module Control server sockets may be created. These sockets are used to receive any Routine status updates to a previously issued Routine request.

The client sockets the developer must implement are categorized as Announce, Application Control, or Module Control sockets. The Announce client socket is always the starting point for an application to begin connecting to SHIPHarbour Master and will only be active during the sending of a single, predetermined SHIPHarbour request. This socket is used to send information about how SHIPHarbour Master should connect to the custom application’s Application Control server socket. An Application Control client socket is expected to be active after SHIPHarbour Master sends information to the Application Control server socket about how the application should connect the client socket and for the entire time the custom application intends to communicate. An Application Control client socket is used to send requests for managing the socket connections to SHIPHarbour Master. Module Control client sockets are used to send any Routine requests to be executed on a target module. A single Module Control socket may be used to communicate to any and all connected modules simultaneously. However, using this approach requires the developer to analyze certain fields of the response in order to relate it back to the original request properly.

Implementation Using protobuf

All messages defined in the SHIPHarbour API are defined using Google’s protobuf proto3 syntax. Using protobuf allows the message definitions to be language independent while also taking advantage of the provided protoc compiler to generate optimal language-specific code for creating, sending, and receiving each message. The protobuf language has many conveniences that allow message definitions to be inherently extensible, backwards compatible, and result in a highly flexible encoding format. The protobuf library is open and free to use without the typical open source project limitations and supports most common high-level languages such as C++, Java, C#, Python, Go, etc. More information can be found at the following website: https://developers.google.com/protocol-buffers/

SHIPHarbour API

File structure

The following files define the SHIPHarbour API:

Keywords

  • import - used to allow the current file to access the definitions of another .proto file
  • message - indicates the start of a message structure definition
  • oneof - indicates only one of the scoped fields can be populated at any given time
  • enum - indicates the start of a definition of integer values with special meanings
  • repeated - indicates the field should be accessed as a list, may be empty


A simple example of proto3 syntax:

message RoutineInfo1 {
    Endpoint2    ep3   = 14; 
    UInt32Value5 id    = 2;
    bool6        start = 3;
}

1 The name that will be used to refer to this type of message
2 A previously defined message type contained within this message definition
3 The name that will be used to refer to this field of the message
4 An internally used number that uniquely identifies a field
5 A wrapper for a primitive data type used to facilitate detecting if the field is present in a received message
6 A primitive data type, default values are always 0 (no distinction can be made between the assigned value being equivalent to the default and the value not having been assigned)

For more details on the proto3 syntax, go to https://developers.google.com/protocol-buffers/docs/proto3.

In order to avoid name collisions with other libraries, the typical naming scheme for SHIPHarbour API structures will start with something similar to "SHIPHarbour.Message." followed by a shortened version of the filename of the .proto file in which the structure is defined, after which, the specific definition will appear. For example, the full namespace for creating a message to cancel a previously requested Routine may look something like: "SHIPHarbour.Message.Generic.Routine.Cancel". The exact syntax will depend on the target language being used. For additional details, go to https://developers.google.com/protocol-buffers/docs/reference/overview.

SHIPHarbour Messages

There are two logical categories of messages, the control message and the routine message. All message definitions simultaneously define the command and response structure for that particular request. However, this does not mean all requests require a response. Control messages are typically meant to be used along with the Application Control sockets. These messages open and close socket connections and carry important data required for the application to build routine messages correctly. Routine messages must be sent using Module Control sockets and are then sorted, delivered, and queued for execution by SHIPHarbour Master with respect to a specified module. Executing one of these messages means reading the fields to drive internal logic which sends one or more SHIPBridge commands directly to the module.

Using Protobufs

Building messages

While the exact code for constructing a SHIPHarbour message using the protobuf API is language dependent, there are a few things to note:

  • All messages sent to or received from SHIPHarbour Master must use the Generic message as a wrapper.
  • All messages must have the version field set.
  • All messages must have exactly one of the fields of the payload field set.
  • A message which is not a response to a previous request should not have the error field set.

C++ Example:

SHIPHarbour::Message::Generic generic;
generic.set_version(/* version */);

SHIPHarbour::Message::Master::Open * pOpen = generic.mutable_open();
pOpen->mutable_host()->set_value(/* host address string */);
pOpen->mutable_port()->set_value(/* port number */);

Java example:

StringValue host = StringValue.newBuilder()
	.setValue(/* host address string */)
	.build();

UInt32Value port = UInt32Value.newBuilder()
	.setValue(/* port number */)
	.build();

Open open = Open.newBuilder()
	.setHost(host)
	.setPort(port)
	.build();

Generic generic = Generic.newBuilder()
	.setVersion(/* version */)
	.setOpen(open)
	.build();

For more information on using protobufs in the target language, visit https://developers.google.com/protocol-buffers/docs/reference/overview

Sending/Receiving Messages

By default, the serialized protobuf does not include a description of the full size of the message. In order to allow sending messages as fast as possible, the SHIPHarbour API requires that the byte size of the message to be parsed appear as a varint-encoded 32-bit integer immediately before the message bytes. For some languages, the protobuf API implements a set of functions named similarly to writeDelimitedTo and parseDelimitedFrom. All messages must either be encoded/decoded using these functions, or the developer must implement the varint encoding/decoding of the message size manually.

C++ example:

/* 
  Least-significant byte first, most-significant bit is 1 when more bytes. 
  There can be up to 5 bytes of size information.
 */
void parseVarInt32(char * pSrc, int * pVarInt, int * pIndex) {
	char data;
	*pVarInt = 0;
	do {
		if (pSrc->size() <= *pIndex) {
			break;
		}
		data		= pSrc[*pIndex];
		*pVarInt	= (data & 0x7F) << ((*pIndex)++ * 7);
	} while ((data & 0x80) && (*pIndex < 6));
}

void serializeVarInt32(char * pDst, int * pIndex, int varint) {
	char data;
	do {
		data 	= varint & 0x7F; 
		varint	= varint >> 7;
		if (varint) {
			data 	= data | 0x80; 
		}
		pDst[(*pIndex)++]	= data;
	} while (varint);
}

ByteArray msg = new ByteArray(5 + generic.ByteSize());

int index;
serializeVarInt32(msg.data(), &index, generic.ByteSize());

generic.SerializeToArray(&(msg.data()[index]), generic.ByteSize());
pSocket->write(msg);

Java example:

 ByteArrayOutputStream bos = new ByteArrayOutputStream();
 generic.writeDelimitedTo(bos);
 socket.getOutputStream().write(bos.toByteArray());

WARNING

In the above Java example, notice that the protobuf API was not used to write directly to the socket’s output stream. At the time of writing, there is a bug which will unpredictably cause messages written this way to become corrupted on the receiving end. This bug has also been observed when using languages other than Java. As a result, it is highly recommended to avoid using the protobuf API to directly access a socket. The protobuf API should be used to read from or write to an intermediate structure.

Available Language Bindings

For some languages, Serious has already developed a full-featured language binding. These language bindings can be useful as they allow the developer to immediately start connecting and sending messages to SHIPHarbour Master by calling abstracted functions.

If a language binding for the target language is not available or the standard language binding is not suitable for the target environment, Serious may be able to create or modify the official language binding by request. Otherwise, refer to the section "Using the SHIPHarbour API Directly" .

Usage examples, source code, and binaries for each language binding are available by request. Refer to the following pages for documentation on how to use each language binding: - Java

Using the SHIPHarbour API Directly

Getting Started with protobuf

This section of the documentation is only necessary if a language binding made by Serious does not exist or cannot be used. To create a custom language binding the following steps will be required:

  1. Compile the protobuf source into a library for the target language (at the time of writing: v3.0.0-beta3)
  2. Download a pre-built version of the protobuf library
  3. Compile the proto files for the target language
    • All of the .proto files for the SHIPHarbour API should be compiled with the protobuf compiler "protoc" using the following command structure:
      protoc --<language_switch>=<destination_dir> --proto_path=<source_dir>
    • Examples of language switch:
      --cpp_out
      --csharp_out
      --java_out
    • Use the following command to find the appropriate switch for the target language:
      protoc --help
    • The destination directory can be any location and is where the generated files will appear
    • The source directory must be the location of all of the SHIPHarbour .proto files.

Connecting to SHIPHarbour Master

Hmaster connection flowchart.png

The below steps should be followed to establish connection between SHIPHarbour Master and another application:

  • Open a receive socket on any port (referred to as Socket_AppControl_Rx).
  • Open a transmit socket to SHIPHarbour Master, the default port is 10400 (referred to as Socket_Announce_Tx).
  • Send an Open message into Socket_Announce_Tx with your host address and the port number used by Socket_AppControl_Rx.
  • Socket_Announce_Tx will be forcibly closed when the message is received as it is no longer needed.
  • An Open message will be sent into Socket_AppControl_Rx describing how to create Socket_AppControl_Tx.
  • A PluginInfo message will be sent into Socket_AppControl_Rx describing what plugins SHIPHarbour Master has loaded.
  • A ModuleInfo message will be sent into Socket_AppControl_Rx describing which modules are currently connected to SHIPHarbour Master.
  • It is now possible to issue application-level control messages using Socket_AppControl_Tx.
  • The above sequence corresponds to the first half of the flow chart.

Getting information on connected modules

While it is possible to poll this information by sending a ModuleInfo message into Socket_AppControl_Tx, it is not recommended or necessary to do so. SHIPHarbour Master will always send a ModuleInfo message into Socket_AppControl_Rx whenever the connection status of a module or one of the endpoints changes.

Connecting To a Module

The below steps should be followed to establish connection between a custom PC application and a module:

  • Open a receive socket on any port (referred to as Socket_ModuleControl_Rx).
  • Send an Open message into Socket_AppControl_Tx with your host address and the port number used by Socket_ModuleControl_Rx.
  • An Open message will be sent into Socket_ModuleControl_Rx describing how to create Socket_ModuleControl_Tx.
  • It is now possible to issue routine messages to a specified module using Socket_ModuleControl_Tx.
  • The above sequence corresponds to the second half of the flow chart.

Using ModuleInfo Messages

A ModuleInfo message has only one field which is marked as a repeatable of type ModuleDetails. This means that a single ModuleInfo message may contain the information for multiple modules so the field must be accessed as a list, array, or some other collection of ModuleDetails depending on the target language.

Detection of whether a module has become connected or disconnected must be done per application. SHIPHarbour Master does not maintain a list of what modules each connected PC application has seen. When any one module (or endpoint within a module) changes state, the current information for all modules is broadcasted to all connected PC applications. The PC application must keep a record of known modules and endpoints and then compare and manage this record accordingly as new ModuleInfo messages are received from SHIPHarbour Master.


SHIPHarbour_Master.proto:

 message ModuleInfo {
     repeated DataTypes.ModuleDetails info = 1;
 }

The ModuleDetails that are contained within a ModuleInfo message contain some fields which may be considered superfluous by the PC application. The field that all PC applications must pay attention to is the module field of type Module. The Module datatype contains the unique serial number of the SIM or SCM as a 64-bit value and, more importantly, a list of Endpoint data describing how the module is currently connected to SHIPHarbour Master.

SHIPHarbour_DataTypes.proto:

 message ModuleDetails {
     Module      module          = 1;
     UInt32Value family          = 2;
     UInt32Value version         = 3;
     UInt32Value variant         = 4;
     UInt32Value lcdCode         = 5;
     UInt32Value id              = 6;
     Firmware    currentProgram  = 7;
 }
 
 message Module {
                 Fixed64Value   sn       = 1;
                 BytesValue     product  = 2;
     repeated    Endpoint       version  = 3;
 }


The Endpoint datatype has an id field which is required to send all routine messages. These directly correspond to some physical communications interface over which the module is using SHIPBridge to communicate with SHIPHarbour Master. The PC application must always supply a valid Endpoint within a routine message in order to direct SHIPHarbour Master to begin executing a SHIPBridge operation over a specific physical interface. As the module may reset or otherwise lose the SHIPBridge connection context with SHIPHarbour Master, the id of the Endpoint will change over time. The PC application is responsible for recognizing when the id of a previously known Endpoint has changed and transitioning future operations or in-progress multi-request operations to use the new id.

SHIPHarbour_DataTypes.proto:

 message Endpoint {
     UInt32Value id      = 1;
     UInt32Value plugin  = 2;
     UInt64Value speed   = 3;
 }

Controlling a Module

Any operation that can be performed on a module is always done by sending what are classified as routine messages. Routine messages are defined in files according to what feature of the module will be accessed. Due to the way Generic messages are used, all routine messages that can possibly be sent can be seen in the SHIPHarbour_Generic.proto file. The following is an example of sending a routine message to retrieve information about what firmware files are installed on a module:

C++ example:

SHIPHarbour::Message::Generic generic;
generic.set_version(/* version */);

/* 
  This gets us a new instance of a Routine and sets the payload field of the
  Generic to this new instance.
*/
SHIPHarbour::Message::Generic::Routine * pRoutine = generic.mutable_routine();
SHIPHarbour::Message::DataTypes::RoutineInfo * pInfo = pRoutine->mutable_info();
pInfo->mutable_ep()->mutable_id()->set_value(/* known endpoint id */);
pInfo->mutable_id()->set_value(/* caller-generated unique id */); 
pInfo->set_start(true);

/* 
  This sets the routine field of the Routine to a new instance of an Installed
  message. Since there are no outgoing parameters required, we don’t 
  need to save the return value.
*/
pRoutine->mutable_installed();

ByteArray msg = new ByteArray(5 + generic.ByteSize());

int index;
serializeVarInt32(msg.data(), &index, generic.ByteSize());

generic.SerializeToArray(&(msg.data()[index]), generic.ByteSize());
pSocket->write(msg);

Java example:

Endpoint ep = Endpoint.newBuilder()
	.setId(/* known endpoint id */)
	.build();

RoutineInfo info = RoutineInfo.newBuilder()
	.setEp(ep)
	.setId(/* caller-generated unique id */)
	.start(true)
	.build();

/* 
  There are no outgoing parameters required, so we build the Installed message
  only as a parameter to the Routine builder.
*/
Routine routine = Routine.newBuilder()
	.setInfo(info)
	.setInstalled(Installed.newBuilder().build())
	.build();

Generic generic = Generic.newBuilder()
	.setVersion(/* version */)
	.setRoutine(routine)
	.build();

ByteArrayOutputStream bos = new ByteArrayOutputStream();
generic.writeDelimitedTo(bos) ;
socket.getOutputStream().write(bos.toByteArray());