I’m involved with a ham radio club and we have a repeater atop a mountain, with great cell coverage and very little broadband service (if any). A few months ago, I presented an idea to provide out of band management of the repeater’s critical functions using an electron, using an electron to control relays. When I set out to start writing code to control relays, the criteria was not well defined. What I want to share are some pearls that resulted from the journey over the last month and much of it wasn’t 100% obvious to me at the time. Note - I used a photon to code up the firmware so I wouldn’t burn up a bunch of cell data on the electron.
Debugging:
In other languages, I found it useful to display debugging info along the way. My style is Code/Test/Next - that is code a bit, test, move on to next task. So I created a function that will publish debug info only when a Boolean flag is set to true - in my case: debug When debug is true, the function will particle.publish(“DEBUG”,command); the text passed to it. The flag also sets a “heart beat” interval to 60 seconds. When false, the heartbeat is set to 1 hour and suppresses all debug messages.
Command Structure. Passing commands to the Electron/Photon:
I didn’t realize when I started that the number of exposed functions was limited and the number of parameters passed to the Electron is also limited. So to accommodate all the things I wanted the electron to do, I came up with the following:
Control Function:
I decided that commands should be in the format of command:value, where the command is fixed length of 4 characters and the value can be the remaining valid length of the string. The first check is to see if the delimiter (in this case a colon) is in position index 4, if yes, continue, if not, bail out.
In Setup:
Particle.function(“DeviceCtl”,DeviceControl);
This is the exposed function. I first had to look at the data passed and see if the data was what’s expected.
int DeviceControl(String command) {
// Control Device - Function sets up the device control, called from API and CLI
signed int ReturnValue = 3; // default value = 3 bad!
unsigned int cnameidx=0;
unsigned int cnamelen=0;
unsigned int x=0;
String cname= "";
String cvalue="";
String msg="";
debugmsg(command);
cnameidx=command.indexOf(":");
if (cnameidx==4) {
// command did contain delimiter in the right position? If yes, Next Parse out left and right.
cnamelen=command.length(); // get total length
cname=command.substring(0,cnameidx); // split out command name
cvalue=command.substring(cnameidx+1,cnamelen); // split out command value
This is an example a command name and value - it’s used to restart the device.
if ((cname=="rebo") && (cvalue=="now")) {
debugmsg(String("System restart scheduled via api"));
resetFlag = true;
rebootSync=millis();
Heartbeat();
ReturnValue=0;
}
I first started by just issuing the reset command when the conditions were met, however, using the CLI during testing would cause the CLI to hang. So I thought about setting a flag and delay so that the clients had time to return a status code and exit. Inside of loop():
#define DELAY_BEFORE_REBOOT (15 * 1000)
void loop() {
if ((resetFlag) && (millis() - rebootSync >= rebootDelayMillis)) {
// do things here before reset and then push the button
Particle.publish("Debug","Remote Reset Initiated",300,PRIVATE);
System.reset();
}
Back now to the Device Control function.
I wanted to abstract out the pin id. Never know what the future holds. I did some asking in the forums and digging and found that the pin id’s are stored in:
I found some nuggets like:
#define D0 0
#define D1 1
#define D2 2
#define D3 3
#define D4 4
Meaning that D1 is actually an integer: 1 This was important because I wanted to build a variable to expose via the cloud api showing how each “relay” was set, through a loop. But I am developing on a Photon and will deploy on an electron. I noticed in that header file pre-compiler directives, such as:
#if PLATFORM_ID == 10 // Electron
#define TOTAL_ANALOG_PINS 12
#elif PLATFORM_ID == 8 // P1
#define TOTAL_ANALOG_PINS 13
#else // Must be Photon
#define TOTAL_ANALOG_PINS 8
#endif
I didn’t realize this was possible! That way code can be build for the specific features of the hardware being deployed. So my debugmsg function looks like:
int debugmsg(String command) {
// Debug Fuction - Publishes selected data to dashboard
if (debug) {
Particle.publish("Debug",command,300,PRIVATE);
Particle.publish("Platform_ID",String(PLATFORM_ID));
#if PLATFORM_ID == 10 // Electron
Particle.publish("DEBUG","Must Be Electron");
#elif PLATFORM_ID == 8 // P1
Particle.publish("DEBUG","Must Be P1");
#else // Must be Photon
Particle.publish("DEBUG","Must Be Photon");
#endif
}
return 0;
}
That’ll come in handy if I want to compile in code only for a certain platform. But after seeing all this, I realize I didn’t have to actually know the ID’s of the PINs to poll and build a status string. Since D0 is just an integer, I can loop through them and not even care of their actual values, as long as they are continuous (and they are):
for(x=D7;x>=D0;x--)
In this case I wanted to count down but could have counted up just as well. Before using A, B, C pins, I’ll have to check the ID’s.
I do not consider myself a professional programmer and I bet some of this would have been obvious to those who are. I’ve learned a bunch from others in the forum so I thought I would share some things I’ve learned along the way.