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.
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.
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:
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:
If the setup is valid then execution continues without any error messages:
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.
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.
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:
- Run the command’s clientPreparePayload() method. The method returns the payload string accompanying the command.
- Determine the IP address, public WAN or private LAN, and port number of the device the command is going to.
- Format the HTTP payload into the Galixsys HTTP payload protocol (GalixiPcol).
- Check for and encrypt the HTTP payload if required.
- Format an HTTP header string with the IP address and port number.
- Setup a thread with a maximum timeout for completion that the HTTP request/response will run on.
- 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.
- The receiving device runs the command’s parserRunCommand(String payload) method and returns a pass/fail code with a response string.
- The client thread saves the reply and closes the network connection.
- The reply is decrypted if required.
- 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.
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:
- Check for and decrypt if required the GalixiPcol payload string.
- Parse and log the command elements.
- Check if this is the device the command is intended for.
- If the command is intended for this device, pass the payload to GalixiParser for command execution with an interface callback for the response.
- 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.
- 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.
- GalixiParser formats the reply to a Galixi-protocol string and returns it to the server via its callback interface.
- The server encrypts the reply string if required, then wraps it in a “200 OK” HTTP header and returns it to the initiating device.