Controlling Photon / Electron Strategy: Controlling Relays & Lessons Learned

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.

4 Likes

Just a hint.
Since the pin numbers don’t necessarily follow (e.g. there are no pins 8 & 9) the more general approach would be via an array

const int myPins[] = { A1, C3, D5, RX, TX, BTN, D0 }; // you can add & remove pins without altering any other code
const int pinCount = sizeof(myPins) / sizeof(myPins[0]);

void setup()
{  
  for (int i = 0; i < pinCount; i++)
  { 
    pinMode(myPins[i], OUTPUT);
  }
}

There is also a platforms.h which gives you a more readable way to check for PLATFORM_ID

#define PLATFORM_SPARK_CORE                 0
#define PLATFORM_SPARK_CORE_HD              2
#define PLATFORM_GCC                        3
#define PLATFORM_PHOTON_DEV                 4
#define PLATFORM_TEACUP_PIGTAIL_DEV         5
#define PLATFORM_PHOTON_PRODUCTION          6
#define PLATFORM_TEACUP_PIGTAIL_PRODUCTION  7
#define PLATFORM_P1                         8
#define PLATFORM_ETHERNET_PROTO             9
#define PLATFORM_ELECTRON_PRODUCTION        10

#if (PLATFORM_ID == PLATFORM_ELECTRON_PRODUCTION)
// that's an Electron
#endif
3 Likes

Thank you - that’s a good idea!