Author Topic: Sending consecutive commands from Gateway to >1 Node  (Read 2208 times)

Scram

  • NewMember
  • *
  • Posts: 29
  • Country: de
Sending consecutive commands from Gateway to >1 Node
« on: January 15, 2018, 12:04:40 PM »
I am using the Gateway now for over a year and my "universe" is constantly growing. Meanwhile it's 7 nodes for switching mains power to devices pretty similar to Felix' switchmotes. They act on 'BTN1:0' / 'BTN1:1' commands to enable or disable the power and also answer with the same strings 'BTN1:0' / 'BTN1:1' to confirm the action and update the gateway.
I wanted to program some kind of "batch" command from the gateway which switches a group of motes 'on' or 'off' and here comes my problem.
When i try to send multiple 'BTN1:X' commands in a row it becomes obvious that the Moteino-Gateway misses some sort of queuing mechanism to organise things in a certain order. When i just send them out consecutively things are messed up and only some nodes are reacting. It's probably the Moteino-Gateway sketch which is not able to cope with this kind of multitasking requirement.
So far so good, i kind of expected some problems here so i tried to get around it on the Node.js Gateway but the Node.js event loop model is driving me nuts. I'd need some kind of queuing  like:

1. Send command 'BTN1:0' to Node [n]
2. Wait for Node [n] to confirm it's off
3. Send command 'BTN1:0' to Node [n+1]
....

What sounds like a pretty simple task seems to be not so easy in reality as there's nothing like wait in Javascript (which is actually a good thing from what I've learned already  ::)). But now with the given architecture of the Gateway my programming skills came to a point where I have no idea how to solve this other than trying to solve the problem on the Moteino-Gateway side which I actually wanted to avoid as the weak ATMEGA328 with very little resources in memory and multitasking (radio&serial!?) seems to be not the right device to solve this problem although I would probably do better in C than in Node.js  ;D.

Did anybody have a similar problem and can offer some kind of tips, hints or possible solutions?

« Last Edit: January 15, 2018, 12:10:44 PM by Scram »

Felix

  • Administrator
  • Hero Member
  • *****
  • Posts: 6866
  • Country: us
    • LowPowerLab
Re: Sending consecutive commands from Gateway to >1 Node
« Reply #1 on: January 18, 2018, 10:17:12 AM »
I have seen my own switchMotes do something like this sometimes. I ruled out interference.

So for instance I have some Switchmotes setup to turn on/off at sunset/sunrise. Because events fire at the same time in nodeJs, a message is essentially sent to all of them at the "same time". Sometimes it seems one does not respond with the ACK and the node in the Gateway remains at the OFF position, although the Switchmote turned ON. So there is a race/collision condition of some sort. I think the last time I spent trying to trace this issue, I determined that the serial interface actually delivers the messages to the MightyHat (or Moteino MCU) which then handles the delivery to end nodes. If that is always the case, then the problem might be harder to fix since some delays must be introduced in a smart way, in the gateway sketch itself.

I thought of implementing a queue mechanism in gateway.js that ensures messages are not dumped into the serial port too fast, but since node is event based, that will be a bit of a challenge, at least I am not sure how doable and how easy that would be.

Any suggestions/solutions are welcome.

Scram

  • NewMember
  • *
  • Posts: 29
  • Country: de
Re: Sending consecutive commands from Gateway to >1 Node
« Reply #2 on: January 18, 2018, 11:37:02 AM »
I implemented it on the Moteino side now. Seems to work for me.

My Gateway Moteino is also doing some IR Control of my AVR, just ignore this part...

Apart from this issue I do observed lost packets as I read somewhere else and the serial speed was reduced to 19200 in some of the last GW versions by you. My conclusion was that it must have had something to do with the usage of strings instead of char arrays in the demo sketch. I eliminated this also few month ago and set the speed up to 115200 again and did never observe any loss again since I was using this version of coding.

My code is linked and included below.

https://www.dropbox.com/s/1q9wlroebvf16t4/Moteino-GW_V3.txt?dl=0

Code: [Select]
// ***************************************************************************************************************
// RFM69 receiver/gateway sketch, with ACK and optional encryption, and Automatic Transmission Control
// Passes through any wireless received messages to the serial port and vice versa + responds to ACKs
// ***************************************************************************************************************
// Scram @ 17.1.2018
//
// - first version with queuing
//
// ***************************************************************************************************************

//******************************* INCLUDES *********************************************
#include <RFM69.h>         //get it here: https://www.github.com/lowpowerlab/rfm69
#include <RFM69_ATC.h>     //get it here: https://www.github.com/lowpowerlab/rfm69

//****************************************************************************************************************
//************ IMPORTANT SETTINGS - YOU MUST CHANGE/CONFIGURE TO FIT YOUR HARDWARE *******************************
//****************************************************************************************************************
#define NODEID              6    //unique for each node on same network
#define NETWORKID           225  //the same on all nodes that talk to each other
#define FREQUENCY           RF69_868MHZ //Match frequency to the hardware version of the radio on your Moteino (uncomment one):
#define ENCRYPTKEY          "***********" //exactly the same 16 characters/bytes on all nodes!
#define RETRY_COUNT         5    //try so many times before giving up [2 is default]
#define RETRY_DELAY         50   //time in ms to retry [40ms is default]
#define IS_RFM69HW_HCW           //uncomment only for RFM69HW/HCW! Leave out if you have RFM69W/CW!
//Auto Transmission Control - dials down transmit power to save battery
//Usually you do not need to always transmit at max output power
//By reducing TX power even a little you save a significant amount of battery power
//This setting enables this gateway to work with remote nodes that have ATC enabled to
//dial their power down to only the required level
#define ENABLE_ATC          //comment out this line to disable AUTO TRANSMISSION CONTROL
#define MAX_BUFF_CHARS      12      // max command length in bytes
#define MAX_BUFF_LINES      32      // max commands in queue
#define RF_REPLY_TIMEOUT    600     // [600ms] timeout between received ACK of request till positive answer from target in ms

//#define SERIAL_EN           // comment this out when deploying to an installed SM to save a few KB of sketch size
#ifdef  SERIAL_EN
  #define SERIAL_BAUD    115200
  #define DEBUG(input)   {Serial.print(input);   delay(1);}
  #define DEBUGln(input) {Serial.println(input); delay(1);}
  #define SERIALFLUSH()  {Serial.flush();}
#else
  #define SERIAL_BAUD    115200
  #define DEBUG(input);
  #define DEBUGln(input);
  #define SERIALFLUSH();
#endif

//****************************************************************************************************************
//************ HARDWARE PINs *************************************************************************************
//****************************************************************************************************************
#define LED_PIN       9    // D9
#define txPinIR       3    // D3
//****************************************************************************************************************

//******************* Constants IR *******************************************************************************
#define Carrier_Frequency 43000             //erzeugt auf meinem Oszi 37,7kHz
#define Duty_Cycle 61                       //49:51 gemessen
#define NARROW_PW 400                       //Pulse width in us
#define WIDE_PW 1270                        //Pulse width in us
#define PREAMBLMARK 3285                    //Pulse width in us
#define PREAMBLSPACE 1755                   //Pulse width in us
#define PERIOD (1000000+Carrier_Frequency/2)/Carrier_Frequency
#define HIGHTIME PERIOD*Duty_Cycle/100
#define LOWTIME PERIOD - HIGHTIME
#define IR_REPEAT 8             //defines how often a IR-Command is sent repeatedly

unsigned CUSTOMCODE =        0xCDAB;   //16-Bit Code is the same for all commands
unsigned PARITY =            0xF;      //4-Bit Code is the same for all commands
unsigned SYSTEMCODE =        0xB;      //4-Bit Code is the same for all commands
unsigned PRODUCT[] =         {0xDE,0xCE,0xFE};     //8-Bit PRODUCT Code for "switch on", "stdby", "raspi input & on"
unsigned FUNCTION[] =        {0xFF,0xFF,0xD2};     //8-Bit FUNCTION Code for "switch on", "stdby", "raspi input & on"
unsigned DATACHECK[] =       {0x9E,0x8E,0x93};     //8-Bit DATACHECK Code for "switch on", "stdby", "raspi input & on"

//******************************* GLOBALS **************************************************************************
#ifdef ENABLE_ATC
  RFM69_ATC radio;
#else
  RFM69 radio;
#endif

char * cmd_queue[MAX_BUFF_CHARS][MAX_BUFF_LINES];
byte add_pointer      = 0;
byte get_pointer      = 0;
byte size_of_queue    = 0;
long send_time        = 0;
bool wait_for_reply   = false;
byte target_to_reply  = 0;

unsigned long packetCount = 0;  // global packet counter
byte          bitarray[50];     // holds the 48 Bits for sendin IR Kaseiyko Code


//******************************* SETUP **************************************************************************

void setup() {
  Serial.begin(SERIAL_BAUD);
  delay(10);
  pinMode(txPinIR,OUTPUT);   //IR amplifier
  digitalWrite(txPinIR,LOW);
  radio.initialize(FREQUENCY,NODEID,NETWORKID);
  #ifdef IS_RFM69HW_HCW
    radio.setHighPower(); //must include this only for RFM69HW/HCW!
  #endif
  radio.encrypt(ENCRYPTKEY);
  radio.promiscuous(false);
  char buff[50];
  sprintf(buff, "\nListening at %d Mhz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915);
  Serial.println(buff);
  Serial.flush();
}

//******************************* FUNCTIONS *********************************************

// send IR MARK
void mark(unsigned long mLen) { 
 if (mLen==0) return;
 unsigned long now = micros();
 while ((micros() - now) < mLen) {
 digitalWrite(txPinIR, HIGH);
 delayMicroseconds(HIGHTIME-5);
 digitalWrite(txPinIR, LOW);
 delayMicroseconds(LOWTIME-4);
 }
}

// send IR SPACE
void space(unsigned long sLen) {
 if (sLen==0) return;
 while (sLen>16383) {
 delayMicroseconds(16383);
 sLen -= 16383;
 }
 delayMicroseconds(sLen);
}

//fill IR array prior to sending
void fill_ir_array(byte item) {
 
    int j=0;
   
    for(int i=0 ;i<=15;i++) {
      bitarray[j]=bitRead(CUSTOMCODE, i);
      j++;
    }
    for(int i=0 ;i<=3;i++) {
      bitarray[j]=bitRead(PARITY, i);
      j++;
    }
    for(int i=0 ;i<=3;i++) {
      bitarray[j]=bitRead(SYSTEMCODE, i);
      j++;
    }
    for(int i=0 ;i<=7;i++) {
      bitarray[j]=bitRead(PRODUCT[item], i);
      j++;
    }
    for(int i=0 ;i<=7;i++) {
      bitarray[j]=bitRead(FUNCTION[item], i);
      j++;
    }
    for(int i=0 ;i<=7;i++) {
      bitarray[j]=bitRead(DATACHECK[item], i);
      j++;
    }
}

void ir_send(byte command_id) {
    DEBUG("Sending IR Code: "); DEBUGln(command_id);
    for(int i=0;i<IR_REPEAT;i++) {
      fill_ir_array(command_id);  // load bitarray with codes "0", "1", "2" -> "switch on", "stdby", "raspi input & on"
      mark(PREAMBLMARK);          // send preamble mark
      space(PREAMBLSPACE);        // & space
      for(int i=0 ;i<48;i++) {    // send bits
        if(bitarray[i]==0) {mark(NARROW_PW); space(WIDE_PW); }
        if(bitarray[i]==1) {mark(NARROW_PW); space(NARROW_PW); }
      }
      mark(NARROW_PW);       // seems to need it in the end as kindof trailer (like a half "1")
      delay(70);             // intercommand delay
    }
}

// here to process incoming serial data after a terminator received
void process_cmd (char * data) {
 
  char delimiter[]  = ":";
  char *ptr;
  char part[MAX_BUFF_CHARS];
  byte targetId;
  byte sendLen = 0;
  byte temperature = 0;

  if (strncmp(data, "t", 1) == 0) {                       // if Gateway asked for temperature
      byte temperature =  radio.readTemperature(-2);    // -2 = user cal factor, adjust for correct ambient
      Serial.print( "Radio Temp is ");
      Serial.print(temperature);
      Serial.println(" Centigrade");
      Serial.flush();
  }
  if (strncmp(data, "IR", 2) == 0) {          // if Gateway asked for IR command
      ptr = strtok(data,delimiter);           // search for delimiter
     
      if(ptr != NULL) {                       // if delimiter ist found somewhere we consider a proper command is here
        sprintf(part, "%s", ptr);             // part is now "IR"
        ptr = strtok(NULL, "");               // rest of string
        sprintf(part, "%s", ptr);             // part is now everything right of delimiter -> hopefully only the ID
        targetId = atoi(part);                // convert to integer
        if (targetId >= 0 && targetId<=2)     // if in allowed range     
        {
          ir_send(targetId);                  // send IR-Code
          //Serial.println();
          //Serial.print(F("send IR command : "));Serial.println(targetId);
        }
      }
  } else {
    ptr = strtok(data,delimiter);      // If not IR command it's probably some message to any node -> search for delimiter ':'
   
    if(ptr != NULL) {                  // if delimiter ist found somewhere we consider a proper command is here
      sprintf(part, "%s", ptr); 
      targetId = atoi(part);
      ptr = strtok(NULL, "");          // rest of string
      sprintf(part, "%s", ptr);
      if (targetId > 0 && targetId<=255)     
      {
        while(part[sendLen]!='\n') {
          sendLen++;
        }
        DEBUG("sending...");
        //we're waiting only if we got ACK from target
        if(radio.sendWithRetry(targetId, part, sendLen, RETRY_COUNT, RETRY_DELAY)) {
         
          DEBUGln("ACK ok");
         
          // we're expecting an answer from the target so we:
          // 1. remember the sent time
          // 2. set flag "wait_for_reply" which is blocking the queue processing
          // 3. remember the target ID for which answer we're waiting for
          send_time = millis();
          wait_for_reply = true;
          target_to_reply = targetId;
     
        } else {
          DEBUGln("no ACK");     
        }
      }
    }
  }
}  // end of process_data

// add char* to global queue
void add_to_queue (char * data) {
 
  int i=0;
 
  if((size_of_queue) == MAX_BUFF_LINES) {
    //buffer overflow -> add nothing
    DEBUGln(F("Buffer overflow!"));
  } else {
    // add line to cmd_queue[][add_pointer]
    DEBUG(F("-> ")); 
   
    while(data[i]!='\n') {
      DEBUG((char)data[i]);
      cmd_queue[i][add_pointer]=data[i];
      i++;
    }
    cmd_queue[i][add_pointer]='\n';
   
    DEBUGln();
    size_of_queue++;
   
    //round robin counter 0,1,2...(MAX_BUFF_LINES-1) results in MAX_BUFF_LINES elements
    add_pointer = (add_pointer + 1) % MAX_BUFF_LINES;
   
  }
}

// execute next cmd from global queue if there's one
void execute_cmd_from_queue() {

  int i=0;
  char tmp_cmd[MAX_BUFF_CHARS];

  if(size_of_queue>0) {
    DEBUG(F("<- "));
   
    while(cmd_queue[i][get_pointer]!='\n') {
        DEBUG((char)cmd_queue[i][get_pointer]);
        tmp_cmd[i] = cmd_queue[i][get_pointer];
        i++;
    }
    tmp_cmd[i] = cmd_queue[i][get_pointer];
    DEBUGln();
    size_of_queue--;
   
    //round robin counter 0,1,2...(MAX_BUFF_LINES-1) results in MAX_BUFF_LINES elements
    get_pointer = (get_pointer + 1) % MAX_BUFF_LINES;

    process_cmd(tmp_cmd);
   
  } else {
    // basically do nothing when queue is empty
  }
}

// here's the processing of single char/bytes as soon as they're coming from UART
void processIncomingByte (const byte inByte) {
 
  static char input_line [MAX_BUFF_CHARS];
  static unsigned int input_pos = 0;

  switch (inByte)
    {

    case '\r':   // end of text         
      input_pos = 0;   //  \r leads to buffer reset
      break;

    case '\n':   
      input_line [input_pos] = inByte;      // terminating string with \n

      add_to_queue(input_line);             // fill up queue
     
      // reset buffer for next time
      input_pos = 0; 
      break;

    default:
      // keep adding if not full ... allow for terminating byte
      if (input_pos < (MAX_BUFF_CHARS - 1)) {
        input_line [input_pos++] = inByte;
      } else {
        // if theres no EOL coming before MAX_BUFF_CHARS is exceeded we'll just terminate and send it, last char is then lost
        input_line [input_pos] = '\n';    // terminating string with \n
        add_to_queue(input_line);         // fill up queue
        // reset buffer for next time
        input_pos = 0; 
      }
      break;

    }  // end of switch 
} // end of processIncomingByte

void handle_radio_message() {
 
  byte sending_target = 0;
 
  sending_target = radio.SENDERID;

  // if we're waiting for this specific node to reply reset flags -> enable sending of further commands from queue
  if(wait_for_reply == true) {
    if(target_to_reply == sending_target) {
      wait_for_reply = false;
      target_to_reply = 0;
      DEBUG("time till answer: ");DEBUG((millis() - send_time));DEBUGln("ms");
    }
  }
  //*******************************************************************************************
  // all Serial.prints here are definitely needed
  //*******************************************************************************************

  // handover the RF data to the Raspberry Gateway Interface
  Serial.print("#[");
  Serial.print(++packetCount);
  Serial.print(']');
  Serial.print('[');Serial.print(sending_target, DEC);Serial.print("] ");

  for (byte i = 0; i < radio.DATALEN; i++)
    Serial.print((char)radio.DATA[i]);
  Serial.print("   [RSSI:");Serial.print(radio.RSSI);Serial.print("]");
 
  if (radio.ACKRequested())
  {
    byte theNodeID = radio.SENDERID;
    radio.sendACK();
    Serial.print("[ACK sent]");
  }
  Serial.println();
  Serial.flush();
  //*******************************************************************************************
  // all Serial.prints here are definitely needed
  //*******************************************************************************************
}

//******************************* MAIN **************************************************************************

void loop() { 
 
  // if somehting is received over UART process it
  if(Serial.available () > 0) processIncomingByte (Serial.read ());

  // if somehting is received over the air process it
  if (radio.receiveDone()) handle_radio_message();

  // if we're in wait state -> monitor for possible timout
  if(wait_for_reply == true) {
    if((millis() - send_time) >= RF_REPLY_TIMEOUT) {
      // timeout -> we're no longer waiting for anything -> reset all flags so further queue processing is happening
      wait_for_reply = false;
      target_to_reply = 0;
      DEBUGln(F("Node Timeout!"));
    }
  }
 
  // if somehting is in the queue and we're not in wait state -> process it
  if(wait_for_reply == false) {
    // we're not waiting for any node to reply so keep on processing queue if something is within
    if(size_of_queue>0) {
      execute_cmd_from_queue();
    }
  }

}// end main
« Last Edit: January 18, 2018, 12:10:11 PM by Felix »

Felix

  • Administrator
  • Hero Member
  • *****
  • Posts: 6866
  • Country: us
    • LowPowerLab
Re: Sending consecutive commands from Gateway to >1 Node
« Reply #3 on: January 18, 2018, 12:05:11 PM »
Well, great you got it solved.
I think in my case, the baud did not change things to the worse as I could observe this condition from time to time even before at 115200.
Thanks for sharing your sketch, I will include it in your post for posterity if you don't mind.

Scram

  • NewMember
  • *
  • Posts: 29
  • Country: de
Re: Sending consecutive commands from Gateway to >1 Node
« Reply #4 on: January 18, 2018, 12:44:22 PM »
Thanks for putting it in a code block. Got no problem with that. I couldn't do it due to a size restriction of 15000 chars.

Regarding the serial baud you got me wrong (or I got you wrong ;-) ). I assumed you were suspecting packets being lost due to the high baud but my feeling is that they were lost due to some strange behaviour of the Moteino. I had cases were my gateway wasn't updated any more at all and finally had to reset the Moteino to get it up and running which now does no longer occur any more.

Felix

  • Administrator
  • Hero Member
  • *****
  • Posts: 6866
  • Country: us
    • LowPowerLab
Re: Sending consecutive commands from Gateway to >1 Node
« Reply #5 on: January 18, 2018, 02:18:45 PM »
Well the lower baud was indeed intended to reduce the chance of having messages corrupted via serial. The side effect of that is potentially what you observe.
BTW I changed the post limit so longer sketches can be added. Even so 15k is a lot though for a forum post :)

gigawatts

  • NewMember
  • *
  • Posts: 23
Re: Sending consecutive commands from Gateway to >1 Node
« Reply #6 on: April 07, 2019, 12:13:12 AM »
I was trying to use this sketch to solve a similar problem, but this sketch will not compile (Arduino version 1.6.13 on Windows).

Line 266 errors with: invalid conversion from 'char' to 'char*' [-fpermissive]

Scram

  • NewMember
  • *
  • Posts: 29
  • Country: de

gigawatts

  • NewMember
  • *
  • Posts: 23
Re: Sending consecutive commands from Gateway to >1 Node
« Reply #8 on: April 07, 2019, 02:13:11 PM »
Thank you for the quick reply to an old thread, that sketch works and I get ACKs from both nodes now. A bit of backstory, I'm trying to solve this problem (https://lowpowerlab.com/forum/pi-gateway/gateway-does-not-receive-both-acks-from-paired-switchmotes/).

Can I assume, however, that I will need to modify this sketch a bit to remove the "#" and [sequence number] in order to use this with the stock Pi Gateway software? That shouldn't be a big deal to remove, just double checking before I dig in.

Scram

  • NewMember
  • *
  • Posts: 29
  • Country: de
Re: Sending consecutive commands from Gateway to >1 Node
« Reply #9 on: April 07, 2019, 02:28:11 PM »
No need for modifications. Just remove the Infrared sending part and off you go. It's running good for a long time now.

gigawatts

  • NewMember
  • *
  • Posts: 23
Re: Sending consecutive commands from Gateway to >1 Node
« Reply #10 on: April 07, 2019, 02:31:34 PM »
Heh, too late, already removed infrared and sequence parts and it works great with gateway software. That bug has been driving me crazy for 2 years, haha, thank you!