Program Flow

This section is intended to give an overview of Nebula functionality. Specific classes and functions used in Nebula are discussed in detail later in the documentation.

APK vs JAR vs BIN

The basic program flow of all Nebula devices is the same. Starting from program launch:

1. Determine if the device is new or existing.

2. If new go to installation. If existing restore its previous known condition.

3. After installation or restoration the device goes to running mode. In running mode it waits for a command to come in or for the user to initiate sending a command to another device.

APK and JAR are mostly identical. They do differ in how user interface screens are made. Android uses XML files while Java uses in-line Swing code. JavaFX looks promising in being able to write identical code but in practice, not yet.

Another difference is the use of Context on Android. Generally, with a Java program, the only concern is running the application. With Android the main application can change. An incoming phone call, the user switches applications, background utilities etc.. Android uses Context to track where and what it’s doing at any given time. On Nebula, for the most part, you will find that the only difference between the APK and JAR coding is the use of Context. Nebula passes the application context even in cases where it is 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.

The program used for Arduino BIN devices differs significantly from APK and JAR. Nebula is built for Arduino due to the popularity of low-cost WiFi enabled devices that can use the Arduino platform for development. The Nebula BIN device platform has all the native functionality to attach to a WiFi network, be installed on a Nebula VPN, maintain a synchronized VPN device database, parse and execute incoming commands and structure and send commands to other devices. The examples code for BIN devices includes writing sensor data to a VPN database, I/O control via Nebula voice, Alexa(Wemos) voice, html web-page or direct command from another VPN device.

Main

Every Java program, Android included, must contain a Main class where the program execution starts. For APK devices the Main class is called MainActivity and its only function is to go to Startup. For JAR devices Main simply goes to Startup. BIN devices do not use a Main class. They use traditional Arduino setup() and loop() functions but the basic functionality is the same as both APK and JAR devices.

Startup

This is where things really begin. Information about the device is collected and logged. Nebula filesystem paths are set and if the $HOME/Nebula directory exists, the device is determined to be existing. If the $HOME/Nebula directory does not exist the device is determined to be new and program execution moves to installation.

For an existing device:

1. The Nebula files are checked for coherency. A fatal alert will pop-up if any files are missing or corrupted.
2. VPN network variables(VPN publicIP or DNS, VPN port and VPN cryptoKey) are restored from thisNetwork.csv file.
3. Device 1 is assumed to be immutable(always running Nebula on a home local network).
4. Two device objects are built:
4.1. deviceWas - is made by restoring the device from thisDevice.csv file
4.2. deviceIs - is made by collecting current device elements from the NetworkConditions class.
5. If deviceWas != deviceIs then a ModifyDevice command is sent to Device 1 with a payload of the current device (deviceIs). Device 1 will in turn, update its database and send ModifyDevice to all other VPN devices.
6. If deviceWas = deviceIs AND thisDevice is not Device 1, then a SyncDatabase command with a hash value of allDevices.csv as its payload is sent to Device 1. Device 1 compares the received hash with a hash of its allDevices.csv. If they match Device 1 responds with DBOK. If they don’t match Device 1 responds with its device database (formatted allDevices.csv). The existing device will use the reply to update its device database.
7. Existing devices now go to Running.

Installation

Installation involves setting up a VPN with Device 1 first, then associating additional devices with Device 1. The appearance of the Installation screens and a full description of each of the widgets is here. The program flow is to have the user fill in the device description and requirements while prompting the user as the widgets are selected. Installation waits for the user to click EXECUTE. For APK devices Nebula checks the version for >= 5.0 and if true, directs the user to accept/deny permissions. It then checks if the APK device is provisioned for mobile operation and if true, prompts the user to enter email credentials so it can receive commands when mobile via SMS.

On EXECUTE Installation will:

1. Check for a name
2. If Device 1:
2.1. Verify the VPN port number
2.2. Verify the VPN public IP or DNS address
3. If adding to VPN:
3.1. Verify contact IP of Device 1.

If the setup is valid then execution continues without any error messages:

1. Initialize commands.
2. Create the Nebula files.
3. Create an SQL database if required.
4. If Device 1, make the device and a crypto key if required.
4.1. Write the network and device files.
4.2. Wait for the user to click EXIT.
5. If adding to VPN, verify connection with Device 1.
6. Send getDatabase command to Device 1.
6.1. For installation the device ID is 0000 as its device ID is unknown.
6.2. Response from Device 1 is a formatted String of the next available device ID, thisNetwork.csv and allDevices.csv. This response is sent in clear text so it is advised that VPNs are built on a private LAN or some unique security protocol is setup prior to adding devices over the public Internet. Someone would have to be listening on the selected port number to be a security threat.
7. Make the device being added.
8. Write the thisDevice, thisNetwork and allDevices files.
9. Send AddDevice command with thisDevice as its payload to all known devices.
10. Wait for the user to click EXIT.

The user can now scroll through the the replies and see if the device was added to all others before clicking on EXIT. The EXECUTE button is disabled to prevent accidentally adding the device again. If any fatal errors are encountered during installation, there is a method called startOver() which will remove the Nebula files and restart installation.

When the user clicks EXIT the program flow moves to Running.

Running

There is not much program flow while in Running. The Running screen is made, the Nebula server is started so commands can be received and the program waits for the user to select a command, select a device to send the command to, and click the Send button.

APK and JAR devices use the localBroadcast interface to add messages to the user prompt widget.

Prior to sending a command the Running activity checks if the command being sent requires any user action, like selecting a file or speaking a voice command, in order to make a payload to accompany the command. If the command does require some user action, Running will transfer program flow to the command’s specified activity. See mCmdUserAction in Command Class Elements.

There is also a developer feature, Run Users Test Code, button which if clicked will run any code a developer adds to the testThis() method provided in the Running class. This has proven very useful in code development and debug situations.

JAR devices have a CLEAR button to clear the user info widget while APK devices use a swipe on the user info widget to clear it.

Oh! one last thing about Running. For APK devices the Running window sets FLAG_KEEP_SCREEN_ON. Without this flag being set, when an APK device or some laptops depending on OS goes to sleep, it shuts down the Nebula server so commands cannot be received except by SMS. This effects battery life which is always a concern. You can use the device BACK button to close the Running window while still keeping Nebula running in the background. There are options to keep the server running on APK and other battery powered devices, but Galixsys Networks decided to leave it up to developers to implement based on their application needs. The server shutting down and power management generally is an issue that needs resolution for any device running on battery power only.

When the user has selected a command and a device to send it to, clicked Send, and no additional user action is required. Running saves the command in StaticValues.sCommand and the device StaticValues.sToDevice and transfers program flow to the SendCommand class.

Send Command

It’s important to have a clear understanding of Nebula commands, how they are implemented and executed. If you don’t already have a clear understanding of Nebula commands, review Nebula Commands before proceeding.

When flow is transferred to SendCommand, it first checks if the command is to be sent by HTTP or SMS.

  • If the command is being sent HTTP:
  1. Run the command’s clientPreparePayload() method. The method returns the payload string accompanying the command.
  2. Determine the IP address, public WAN or private LAN, and port number of the device the command is going to.
  3. Format the HTTP payload into the Galixsys HTTP payload protocol (GalixiPcol).
  4. Check for and encrypt the HTTP payload if required.
  5. Format an HTTP header string with the IP address and port number.
  6. Setup a thread with a maximum timeout for completion that the HTTP request/response will run on.
  7. The thread calls a standard HTTP client where HTTP properties are added to the header. The header is followed by the payload (GalixiPcol) and sent to the server ie. receiving device.
  8. The receiving device runs the command’s parserRunCommand(String payload) method and returns a pass/fail code with a response string.
  9. The client thread saves the reply and closes the network connection.
  10. The reply is decrypted if required.
  11. The reply is passed to the command’s clientPostAction() method where it is checked for success or fail. The response can be displayed to the user, used in a follow-on command or whatever a developer intends to do based on the server’s response.
  • If the command is being sent SMS, see the class description for SmsClient to follow its path.

Receive Command

Nebula implements a custom muti-threaded server class to listen for and process incoming commands. The server is started by the Running class and continues to run while Nebula is active. The server runs on a background thread and “listens” on the command port of the device the user defined during device installation. The Nebula default port is 50500. When the server is started it is also given a fixed number of threads (thread pool) it can use for incoming commands. The default number of threads is 3 but can certainly be increased based on the device’s hardware capability.

When the server receives a request it starts a new thread (ServerHandler) from its thread pool to process it. Each line of the incoming request is parsed. GET requests and favicons are rejected. Only valid command POST requests are processed and they are verified with both HTTP header strings and having a payload of GalixiPcol format. If an incoming request passes all the security checks it is assumed to be a command. Processing a command involves:

  1. Check for and decrypt if required the GalixiPcol payload string.
  2. Parse and log the command elements.
  3. Check if this is the device the command is intended for.
  4. If the command is intended for this device, pass the payload to GalixiParser for command execution with an interface callback for the response.
  5. If the command is not intended for this device, pass the payload, socket number, and crypto true/false to GalixiReroute for command execution on the intended device. GalixiReroute responds directly to the initiating device via the open socket, so the server is effectively finished with any command that is not for it. See the class description for GalixiReroute to follow its path.
  6. GalixiParser runs the command’s parserRunCommand(String galixiPcol) method. If the method code runs into an error in processing, it must prefix its return string reply with the keyword FAIL (all capitals). GalixiParser uses the FAIL keyword to set the command’s response code. If the developer code in parserRunCommand() is successful it returns a response string to be used by the initiating device when it runs its clientPostAction(serverReply) method. An empty response string is acceptable but returning at least “OK” helps if debugging is necessary.
  7. GalixiParser formats the reply to a Galixi-protocol string and returns it to the server via its callback interface.
  8. The server encrypts the reply string if required, then wraps it in a “200 OK” HTTP header and returns it to the initiating device.