Commands

Commands Overview

Implementing commands is how a remote project is built. Devices, network management, encryption, the HTTP client and server and support code is all implemented in Nebula. Developers should only have to write the command code necessary for their networked application along with with any necessary user interfaces. It is therefore very important that Nebula developers understand how commands are made and executed.

All commands are built using a simple 3 step process. First the client, the device sending the command, runs code that prepares any data that must be sent with the command. Next the server, the device receiving the command, runs the command code and returns a success/fail code and any response data to the client. Finally, the client finishes by reading the server reply and running its response code.

Commands may be nested. If prepare requires data from another device, it can send a command to the device to retrieve the data in its prepare code. If a server reply requires that another command has to be run, the command can be sent in the finish code. This allows for complex device communication without human intervention. The user code in each of the 3 command methods can do anything the device is capable of.

When running the 3 step process, Nebula recognizes one associated keyword FAIL in all capital letters. The command methods to prepare, run and finish a command all return a String. If the return String begins with the keyword FAIL, Nebula assumes that the step failed for some reason and that the command’s user code responded accordingly. For example assume that a server runs its code in a try block and that an exception occurred. The catch block should return the String “FAIL + “the reason”.

Every command sent and every reply received via HTTP is wrapped in the patented Galixsys Network Protocol. The default protocol is 20 bytes long and detailed in the next section.

How commands are sent is handled by the networking.SendCommand class. In brief, it calls the command’s prepare method, determines how to contact the server and then sends the command via HTTP or SMS, gets the server reply and passes it to the command’s finish method.

Command Protocol

The class galixi_server_parser.GalixiPcol is the class that structures the protocol. The default class object consists of 6 String elements. Stings are used because text is all HTTP or the Internet in general can pass. Five of the elements are 4 byte hexadecimal values (0000 - FFFF). The sixth String element is called the payload. The payload is any data sent to or returned from the server. Following is the default structure of a GalixiPcol object.

bytes 0-3 – When sent is the user defined command ID, a hexadecimal value 0000 - FFFF. When received as a reply, a failed command returns its command ID. A successfully executed command returns its success code. A success code must be different than the command ID. The success code is a user defined hexadecimal value 0000 - FFFF. So every command has 2 hex values associated with it, the command ID and the success ID. The command ID and success ID are mutually exclusive meaning that the success ID of one command can be the command ID of a different command. All command IDs must be unique, that is no two commands can have the same command ID. Commands may have more than one success code to indicate different successful outcomes to finish code.
bytes 4-7 – The initiating (client) device ID, a hexadecimal value 0001 - FFFF. This is the unique device ID of the device sending the command. Device IDs are managed by Nebula and assigned during device installation. If a device is removed from the VPN its device ID is returned to the pool of available IDs and may be assigned to a new device being added to the VPN.
bytes 8-11 – The receiving (server) device ID, a hexadecimal value 0001 - FFFF. This is the unique device ID of the device the command is intended to go to. All APK and JAR devices have the ability to reroute a received command not intended for it to the intended device.
bytes 12-15 – The length (number of bytes) of the payload that follows. A hexadecimal value 0000 - FFFF. This is a legacy value required by Andromeda (the C language interface to the protocol) to extract the payload. Nebula includes the value but caps it at 65,534(FFFE), if the value is FFFF it means the payload is greater than or equal to 65,535 bytes. Nebula extracts the payload using a substring of the GalixiPcol that starts at byte 16 and ends at the length of the GalixiPcol-4.
PAYLOAD – A string sent to the server with the command, or returned in the response from the server. The Payload can be an empty string for commands where the unique command ID sent and success reply is enough for the command to complete.
bytes 16-19 – Footer, a user defined hexadecimal value 0000 - FFFF that developers can use to control different server responses when sent or control different client post-execution when received in the server reply.

Command Class For All Commands

All elements are String and have associated getter and setter methods.

mCmdName – Unique authoritative name of the command shown to user if mCmdShow = “true”. May be different than the class name.
mCmdNum – Unique hex(4) command ID assigned by the command’s developer.
mCmdOK – Success response hex(4) assigned by the command’s developer.
mCmdInfilter – package.command class name so Nebula can instantiate by reflection.
mCmdShow – “true” allows the user to see mCmdName, select and send from the main UI or “false” if it’s not user selectable.
mCmdUserAction – “none” or className.method() to get user input for the command’s prepare method.
mCmdConnTime – milliseconds allowed to connect to server before timeout fail.
mCmdReadTime – milliseconds allowed for server to reply before timeout fail.
mCmdRespTime – milliseconds allowed for SMS reply (Not used, hard coded at 60 seconds in SmsClient).
mCmdRetrys – Number of times to retry if command fails (Not used, currently only 1 try).

The only methods in the Command class are the getters and setters of the elements.

Individual Command Classes

The best way to show how a Command class is made is with the simple Ping command and a few general statements.

  • All commands are held in an array (StaticValues.cmdsArray) and made during Startup. This allows helper methods like getCommandByName() to quickly access them without having to go to a database or file. Each command is about 100 bytes depending mostly on its package and name length.
  • Command classes can extend, implement, start threads, launch other classes or Activities etc.. They are just a standard Java class.
  • The command must have its 10 “private final String” class elements defined. None of them can be null.
  • All commands must implement at least these 4 methods: These 4 methods are called by Nebula not the user. The user supplies the code for the first 3, the fourth, returnSelf(), is identical for every command.
    1. public String clientPreparePayload()
    2. public String parserRunCommand(String galixiPcol)
    3. public String clientPostAction(String remoteResp)
    4. public Command returnSelf()
  • Methods 1, 2 and 3 above are shown for JAR devices. For APK devices the methods also include a Context argument which Nebula passes for user code convenience. Better to have the context than trying to determine it.
    1. public String clientPreparePayload(Context context)
    2. public String parserRunCommand(Context context, String galixiPcol)
    3. public String clientPostAction(Context context, String remoteResp)
    4. public Command returnSelf()
  • Commands are separated into two groups within the StaticValues. There are nativeCommands which must exist on all devices to allow for device management, and demoCommands which only need to be made and included on the devices that will use them. Some Nebula demo commands (video, voice, location etc.) can only complete successfully on devices that have requisite hardware. Devices that cannot run a particular command will respond with an appropriate message.

The Ping class example

public class Ping {
//Developers must identify these strings. None can be empty or null.
private final String inIfilter = this.getClass().getName(); //Must be package.className
private final String cmdName = “Ping”; //UI display name required for reference and retrieval
private final String cmdID = StaticValues.ping; //hex(4) command code assigned in StaticValues
private final String cmdOK = StaticValues.pingOK; //hex(4) success code assigned in StaticValues
private final String show = “true”; //Show command in UI: “true” or “false”
private final String userAction = “none”; //”class.method” for input prior to command being run
private final String connTime = “30000”; //Max wait for HTTP connect mills
private final String readTime = “30000”; //Max wait for HTTP response mills
private final String respTime = “60000”; //Max wait for SMS response mills
private final String retrys = “2”; //Max times the client will resend on failure

Client runs clientPreparePayload() code before sending.

public String clientPreparePayload() {
return “”; // Command ID says it all, no payload to send with command.
}

Server -> GalixiParser runs parserRunCommand() code upon receiving the command.

public String parserRunCommand(String galixiPcol) {
//galixiPcol may be parsed for its elements with the following static methods in GalixiPcol.
String cmdID = GalixiPcol.getCommandFromString(galixiPcol);
String fromDevID = GalixiPcol.getFromdevFromString(galixiPcol);
String toDevID = GalixiPcol.getTodevFromString(galixiPcol);
String payload = GalixiPcol.getPayloadFromString(galixiPcol);
String footr = GalixiPcol.getFootFromString(galixiPcol);

//Just return the response payload, GalixiParser called this method and will format the response.
return StaticValues.sThisDevice.getDevName() + ” has been pinged”;
}

Client runs clientPostAction() code after receiving the server response.

public String clientPostAction(String remoteResp) {
//remoteResp is a GalixiPcol and may be parsed for its elements.
String cmdReply = GalixiPcol.getCommandFromString(remoteResp);
String fromDevID = GalixiPcol.getFromdevFromString(remoteResp);
String toDevID = GalixiPcol.getTodevFromString(remoteResp);
String payload = GalixiPcol.getPayloadFromString(remoteResp);
String footr = GalixiPcol.getFootFromString(remoteResp);

//Test for success
if(cmdReply.equals(cmdOK)) {
return payload; //”serverDeviceName has been pinged”
} else {
return “Failed to Ping”;
}
}

Running returnSelf() code returns this command object.

public Command returnSelf() {
Command c = new Command();
c.setCmdInfilter(inIfilter);
c.setCmdName(cmdName);
c.setCmdNum(cmdID);
c.setCmdOK(cmdOK);
c.setCmdShow(show);
c.setmCmdUserAction(userAction);
c.setCmdConnTime(connTime);
c.setCmdReadTime(readTime);
c.setCmdRespTime(respTime);
c.setCmdRetrys(retrys);
return c;
}

}

Running a Command

By now you can see how a Nebula command is structured and the methods that make it work, but how does it actually get run? Well here’s where a little Java magic takes place. The 4 required functions of any command are run by reflection. Using the command’s inFilter element the command’s class can be instantiated by reflection any time it’s needed. Once instantiated you have full access to all its methods. Without Java’s unique ability to use reflection you’re left with long if/else or switch/case coding for all commands.

When a user selects a command to run we get the commands name. From that we run:
Command cx = new CommandInfo().getCommandByName(name)
Now we have the command object in cx. From the command object we get its inFilter element with:
String cmdif = cx.getCmdInfilter()

From that we can now instantiate the class and for example run its clientPreparePayload() method with the following code:

private String getPayload(String cmdif) {
try {
final Class<?> cmdName = Class.forName(cmdif);
final Object t = cmdName.newInstance();
Method prep = cmdName.getDeclaredMethod(“clientPreparePayload”);
prep.setAccessible(true);
//Run prep to get a payload to send with the command
return (String) prep.invoke(t);

} catch (Exception cnfe) {
cnfe.printStackTrace();
return “FAIL instantiate ” + sCommand.getCmdName() + ” command”;
}
}

Look at command_files.CommandUtils().makeCmds() to see how all commands are made. It runs each command’s returnSelf() method by reflection in a loop. On the server side look at galixi_server_parser.GalixiParser().doCommand(String cmdid). It gets the incoming command ID from the first 4 bytes of the Galixi-protocol. It then uses the command ID to make the command object. The command object provides the command’s inFilter. With the command’s inFilter you can get an instance of the command’s class by reflection. Once the class is instantiated its methods are available. GalixiParser runs the command’s parserRunCommand() method passing to it the payload received from the client.

Adding a New Command

First a word about StaticValues. You may have noticed a few places in the Nebula Commands documentation where reference was made to StaticValues. The StaticValues class is in the command_files package. It is used as the authoritative source for all command ID and success ID values. Using StaticValues eliminates having to look through all the commands to see if a value has been already used. The default constructor for StaticValues is private so you can never instantiate it with the keyword new. StaticValues also holds reference to all Nebula file paths and commonly referenced variables.

To add a new command to Nebula:

1. All commands must be in the commands_builtin package as it’s hard-coded in the CommandUtils().makeCmds() method. Be sure to edit this if your package path is different from “com.galixsys.nebula”.
2. Right click on the package commands_builtin and select New -> Java Class
3. Enter a name for the new command’s class.
4. Open the CommandBlank file.
5. Use Edit -> Select All and copy the CommandBlank file to the clipboard.
6. Paste the CommandBlank so it replaces your new command file leaving only your top line package path.
7. Edit CommandBlank with your new class name.
8. Open StaticValues. Add your new command’s class name to either the nativeCmds[] or demoCmds[] array so it will get built on startup.
9. Also in StaticValues, add unique command ID and success ID hex string reference values.
10. Go back to your new command class file and add your code to make the command do what you want.

Hello World

To get a real feel for making Nebula commands we’ll go through the process for the supported platforms. This section assumes you have become familiar with the basics of the associated IDEs, have setup at least a 2 device VPN and have sent at least a successful Ping command from one device to another.

Hello World JAR or APK

There is one difference between JAR and APK commands. Android (APK) commands include a Context argument first in their clientPreparePayload(), parserRunCommand() and clientPostAction() methods. Nebula passes the application context even in cases where it may be unused in anticipation that developers may need it for their application. It’s better to have it and not need it than to try to trace back and add it to nested calls. In making the HelloWorld command, the guide assumes IntelliJ (JAR) but if you are working with Android Studio (APK) be sure to include the Context argument.

I. Initialize the command

1. Open the Nebula project in the IDE and drop-down src > com.galixsys.nebula > commands_builtin so all the command classes are showing.
2. Right click on the package commands_builtin and select New -> Java Class
3. Name the New Java Class HelloWorld - the HelloWorld.java file is added to the commands_builtin package and the file opens in the Editor window.
4. Double-click on the CommandBlank file. It will open in the Editor. Use Ctl-A then Ctl-C to copy the CommandBlank file to the clipboard, click anywhere on the Editor to deselect all, then close the CommandBlank file.
5. Left-click and drag your mouse starting from line 2, to select everything in the HelloWorld.java file except the package path on line 1. Now use Ctl-V to paste the CommandBlank text in the HelloWorld.java file.
6. Edit the class name from “NewCommand” to “HelloWorld”. You can remove the comment on that same line.
7. Edit the “private final String” command elements:
7.1. inIfilter = this.getClass().getName(); //always
7.2. cmdName = “Hello World”; //adding a space for the command shown to the user
7.3. cmdID = StaticValues.hello; //value for command ID
7.4. cmdOK = StaticValues.helloOK; //value for success ID
7.5. show = “true”; //show this command for the user to select
7.6. userAction = “none”; //does not require user input for the payload sent to server
7.7. connTime = “30000”; //allow max 30 seconds for client to connect to server before error
7.8. readTime = “30000”; //allow max 30 seconds for server to respond before error
7.9. respTime = “60000”; //if sent by SMS allow 60 seconds before informing user of no reply
7.10. retrys = “2”; //This is not yet implemented in Nebula. It’s available for developers
8. Open command_files.StaticValues in the Editor.
8.1. Scroll down to “static final String[] demoCmds” and add “HelloWorld” to the beginning of the list. Use quotes and comma separate it from the next command. Where you add it will be the order its cmdName is shown to the user.
8.2. Scroll down further around line 128 and add the cmdID & cmdOK values:
// My first Hello World command
public static final String hello = "0700"; //Unique hex(4) string (0000 - FFFF)
public static final String helloOK = "0701"; //Any other hex(4) string
9. Add import statements to the HelloWorld.java file:
9.1. Click on a red “StaticValues” then Alt-Enter.
9.2. Click on a red “Command” then Alt-Enter.
9.3. All errors should now be cleared.

II. Write the code for method clientPreparePayload()

This method must return the String that will be sent as the GalixiPcol payload to the server device. The String may be empty because in some cases the unique cmdID alone is sufficient for the server device to know what to do. The String may be some sensor data that the server will store in a database. It could also be some data that was made available by the user in a userAction class.method. In any event it is any data that the server device needs in order to complete its task.

While the HelloWorld command may not need any accompanying data, let’s send the string “Hello World” to the server device so that in the server code we can demonstrate how to read and operate on a payload.

1. In the HelloWorld.java file, find the clientPreparePayload() method and replace the line:
return “payload to server string”; with return “Hello World”;.

III. Write the code for method parserRunCommand(String galixiPcol)

The symbiotic nature of Nebula commands means that all devices that use a particular command have the same command installed. All devices with the command installed can send it and know what to do when it’s received. If a particular device will never send the command, it’s true that there’s no reason to have code for its clientPreparePayload() or clientPostAction() methods. It would only need the parserRunCommand() method. This however defeats the “developers sandbox” purpose of Nebula and leads to having multiple versions that change depending on the use by a device. Certainly nothing in Nebula stops a developer from structuring their application to best suit the needs. Commands can even be added as separate packages. Android permits access to all application code that has the same package path, and with Java you can have multiple JARs and use arguments to Main or files to pass data.

With all that said let’s get back to coding the HelloWorld command. The details for how the parserRunCommand(String galixiPcol) method gets its argument is here. The method must return a String. If the command runs successfully it can return an empty String and Nebula will return the cmdOK code. If the command fails for any reason it must return a String that starts with the keyword “FAIL”. Then Nebula will return the cmdID instead of cmdOK and the returned GalixiPcol.payload will contain the FAIL plus any reason you give.

One last thing to note, both the parserRunCommand() and the clientPostAction() receive a String arguments that is in the GalixiPcol format. For the HelloWorld.java file we’ll want to extract the payload. There several ways to parse a String containing GalixiPcol elements. Obviously only the necessary elements have to be decoded not all of them.

Instantiate new – GalixiPcol gp = new GalixiPcol().pcolFromString(galixiPcol);
String payload = gp.getPayload();
Use static method – String payload = GalixiPcol.getPayloadFromString(galixiPcol);
Substring (not recommended) – String payload = galixiPcol.substring(16, galixiPcol.length() - 4);
1. In the HelloWorld.java file, find the parserRunCommand(String galixiPcol) method and add the following code:
String payloadFromClient = GalixiPcol.getPayloadFromString(galixiPcol);
String fromDevID = GalixiPcol.getFromdevFromString(galixiPcol);

if(! payloadFromClient.equals("Hello World")) {
    return "FAIL incorrect payload"; //Change "Hello World" to test fail response
}
Device d = new DeviceInfo().getDeviceById(fromDevID);
//Success response will not begin with keyword FAIL
return StaticValues.sThisDevice.getDevName() + " says hello back to " + d.getDevName();
2. Use Alt+Enter to import the classes used as necessary.
3. The above code first checks that the received payload is what was sent and FAILs the command if it’s not. If the payload is correct it makes a Device object of the sending device in order to access its elements. The return statement builds the command success string for the reply payload. It gets its own name from sThisDevice, which is a static reference to the device, and the name of the sending device. Whether the command fails or succeeds, Nebula will wrap the returned String value in a GalixiPcol String to be used by the final command method clientPostAction(String remoteResp).

IV. Write the code for method clientPostAction(String remoteResp)

The clientPostAction() method is used to describe what the client will do based on the reply from the server. Nebula calls this method using the String(GalixiPcol) returned from parserRunCommand(). As with parserRunCommand() the String argument can be made into a GalixiPcol object. We’ll do that here so it will become more obvious.

The clientPostAction() method must return a String. For many of the demo commands the payload is parsed out and returned where, by default, Nebula displays it to the user in the Running window text box. Especially if the command returns FAIL. Speaking of a failed command, there are two ways to test for it. First is the checking if the GalixiPcol().getCmd().equals(cmdID) and second is checking if the GalixiPcol().getPayload().startsWith(“FAIL”). If either is true, the command failed to run or run successfully on the server.

1. In the HelloWorld.java file, find the clientPostAction(String remoteResp) method and replace it entirely with the following code:
//Add "Context context" as first argument for Android
public String clientPostAction(String remoteResp) {
    GalixiPcol gp = new GalixiPcol().pcolFromString(remoteResp);
    String backToUser = gp.getPayload();

    //Log the reply
    NebLog.d("HelloWorld", backToUser);

    //Test for any failure
    if(gp.getCmd().equals(cmdID)) { //true means it failed
        //Test for fail due to incorrect payload
        if(backToUser.contains("incorrect")) {
            backToUser = "Somebody changed Hello World";
        } else {
            //Command failed for some reason
            Device d = new DeviceInfo().getDeviceById(gp.getTodev());
            backToUser = d.getDevName() + " failed to run " + sCommand.getCmdName();
        }

    } else {
        //Assume here that the command was successful
        //gp.getCmd().equals(cmdOK) would prove success
        backToUser = sCommand.getCmdName() + " was successful\nReply was " + gp.getPayload();
    }
    //Display the final message. The String returned here gets displayed in the client's Running text box by default.
    return backToUser;
}
2. Use Alt+Enter to import GalixiPcol, Neblog and sCommand. Nebula uses sCommand to hold the current command being run.
3. Note the use of Neblog to log the reply. NebLog is a class in the Utils package for both APK and JAR. It unifies the logging code between Java(System.out.println) and Android(Log.<type>). It’s also used by Galixsys Networks to limit the log output(Log.a) for our free trial version.

V. Compile and deploy HelloWorld

1. Use the IDE run button (green arrow) to compile and launch (JAR) or upload to a device (APK) Nebula. For most commands that do not use streaming or outside network code the command can be sent to itself. That is the same device is both client and server. This is very useful in debugging code before deployment. When sending and receiving a command on the same device, a JAR Device 1 is all you need so it’s fast and easy.
2. Nebula should open to its Running window and “Hello World” should be selectable in the Commands list.
3. Select Hello World and select the device you are are using to send the command to. Click the Send button.
4. The command should complete successfully and the reply message shown to the user is:
Hello World was successful
Reply was <toDevName> says hello back to <fromDevName>
5. Introduce some errors like misspelling “Hello World” in either clientPreparePayload() or parserRunCommand(). You can start the success response with “FAIL” to introduce an unknown error. Obviously you have to compile and redeploy the code when you modify it.
6. If all works correctly, remove any error code, compile and deploy Nebula on all your JAR and APK devices.
7. Test sending the Hello World command between devices.

VI. Study other Nebula demo commands

1. See SendFile: If you need some data, like a file name, from a user before a command can run, identify the class.method in the commands userAction element. When you send the command, the Running class will check the commands userAction and run the class.method before sending. The command class can extend the named userAction class for access to variables and methods. Also see VoiceControl for speech-to-text before the command is sent.
2. See VideoCameraBack: Streaming video starts new Activities from the command on both the client and server.
3. See DBwriteReply: Shows a text message to the server user by using NebulaBroadcast to update the Running user interface. Note that Running.updateUI() for both JAR and APK updates the device selector list as well as the text box.
4. See WhereIs: Android will error if there is too much time away from the current UI loop and Nebula blocks while running a command. For short delays you can use a CountDownLatch. Also see DeleteDeviceFromVpn: Where the server runs its code after returning based on a time delay.

NOTE: Some of the coding for Nebula was done to keep Android and Java code the same. Where Java is very flexible, Android is quite strict. Your application and the devices it’s intended to run on will dictate how and where to use the Nebula code.

Hello World BIN

The Nebula BIN device code is written for use on ESP8266 modules. The source code is the standard Arduino.ino but if you want to use it for an Arduino Uno, Due, Mega or other board you’ll have to extract the pertinent code to implement.

While the Nebula code is substantially different for Arduino than for either Java or Android it effectively does the same thing. It maintains a file for the VPN devices database, the device elements can be extracted and used, incoming and outgoing commands use the GalixiPcol String structure and all applicable Nebula native commands are implemented.

This guide assumes:

  1. You have completed the BIN development steps.
  2. You have installed the module in a VPN with another device that has the Hello World command installed.
  3. You have the Nebula BIN project open in the Arduino IDE.
  4. That the Arduino Serial Monitor is open and shows output from the BIN device.
  5. That you can send Ping to the BIN device and it responds correctly.

From the Arduino IDE add server code

1. Use Ctl-F and Find: “End Nebula Commands”, check Ignore Case and Wrap Around.
2. Above the comment “//End Nebula Commands”, insert the following lines:
String hello = "0700"; //Same command ID for HelloWorld on APK & JAR
String helloOK = "0701"; //Same command OK for HelloWorld on APK & JAR
3. Use Ctl-F and Find: “Add more commands”
4. Above the comment “//Add more commands with “else if” here”, insert the following code:
else if(CMD.equals(hello)) {
  //Server code for Hello World
  Serial.println(F("Command for Hello World"));
  if(! PAYLOAD.equals("Hello World")) {
    respond = "FAIL incorrect payload";
    success = hello; //Command failed, return the command ID("0700")

  } else {
    //Get device from file
    //Use reserved String fileName to build path/name
    fileName = "/nebula/dev";
    fileName += FROMID;
    fileName += ".csv"; //Path/filename for devices = /nebula/dev<devID>.csv

    parseDevice(tempDevArray, readFile(fileName)); //make array from file device
    respond = myName + " says hello back to " + tempDevArray[DEV_NAME];
    success = helloOK; //Command success = command OK code("0701")
  }
  FOOT = "0000"; //Code value for alternate reply actions
}
5. Select Sketch -> Upload to compile and upload the file to your BIN device.
6. Send the Hello World command to your BIN device from another VPN device to verify it works.
6.1. Introduce errors in the code to verify they are caught.
6.2. Review the log output from the both the client and BIN devices.

Sending commands from BIN devices

Typically ESP8266 BIN devices are used to upload sensor or some condition data to a database located on some other VPN device. There are no user selection lists for VPN devices or the commands to send. You have to know ahead of time the device, command, payload and command footer variable to send. Nebula BIN includes a sendCommand() method. Use Ctl-F and Find: “String sendCommand”. You’ll see the method requires the needed arguments and will return the GalixiPcol reply string from the server. So, to send the Hello World command to Device 1 and simply log the reply, you would use the sendCommand() method like this:

Serial.println(sendCommand("0001", hello, "Hello World", "0000"));

Another requirement is to define the condition that will cause the command to be sent. The Nebula BIN code was written using a development module(soon to become available on galixsys.com/store). It has a switch and temperture sensor that could easily be monitored in the Arduino loop() method for a condition in which to send the command. Another way is to use a command to tell the BIN device to send a command. Nebula demonstrates how to do it with the Database Write demo command (Find: “CMD.equals(db_write)”). We’ll use sending a command for Hello World as neither a switch or a sensor may be available on the module you are using.

The BIN device client and server are not multi-threaded. They must complete their respective; send command then process response, and process command then reply, one at a time. The incoming command must be replied to before sending the Hello World command. The example Nebula uses follows this sequence:

1. When the command is received:
1.1. Set a unique boolean flag that is monitored in loop().
1.2. Save the incoming device ID, payload or footer if any are necessary to run the command that’s going to be sent.
1.3. Reply to the incoming command.
2. Nebula sends the HTTP reply to the received command and then returns to loop().
3. The loop() method sees the unique flag is set.
3.1. Reset the unique flag to false.
3.2. Send the identified command and process the result.

From the Arduino IDE add the client code for sending the HelloWorld command

1. Use Ctl-F and Find: “String senderID”. On the next line add:
bool sendHello = false; //for Hello World command
2. Next Find: “Test for Hello World here” in the loop() method. Add the following code:
//Test for Hello World here
if(sendHello) {
  sendHello = false; //Reset the flag

  //Send HelloWorld command back to device that sent it and get reply
  String helloResp = sendCommand(senderID, hello, "Hello World", "0000");

  //postAction - Is command reply OK? If OK print the payload from the reply
  if(helloResp.substring(0,4).equals(helloOK)) {
    Serial.println(helloResp.substring(16, helloResp.length()-4));

  } else { //command failed
    Serial.print(F("Device "));
    Serial.print(senderID);
    Serial.println(F(" Failed Hello World"));
  }
}
3. Typically a specific command would be setup and used to tell the BIN device which command to send. But, for this example we’ll just reuse Ping.
3.1. Use Ctl-F and Find: “CMD.equals(ping)”.
3.2. Comment out the Ping command code and replace it with the following.
//Replace Ping, now ping is command to send HelloWorld
else if(CMD.equals(ping)) {
  Serial.println(F("Command to send Hello World"));
  sendHello = true; //Set a flag, reset to false in loop().
  senderID = FROMID; //Save the sending device ID. Hello World will be sent to it.
  respond = myName + " sending Hello World command back"; //Send an immediate http reply.
  FOOT = "0000"; //Set the footer for any special clientPostAction().
  success = pingOK; //Getting this far is success.
}
4. Use Sketch -> Upload to compile and upload the code.
5. Send the Ping command from a VPN device that has Hello World implemented to the BIN device.
6. Observe the log output from both devices.