timmbogner / Farm-Data-Relay-System

A system that uses ESP-NOW, LoRa, and other protocols to transport sensor data in remote areas without relying on WiFi.
MIT License
524 stars 116 forks source link

Update Irrigation example to support time limited activation. #221

Open aviateur17 opened 5 months ago

timmbogner commented 5 months ago

Something's still wrong with this from my mistake I think.... The sketch is still messed up, for example it doesn't have the definition for irrigController. I'll post your complete and updated sketch below for reference. Sorry again for the hassle.

Your new sketch seems to work great, except that it doesn't seem to send back accurate pin statuses in checkCoils(). I'll keep looking into that.

//  FARM DATA RELAY SYSTEM
//
//  Irrigation Controller
//
//  Developed by Timm Bogner (timmbogner@gmail.com) in Urbana, Illinois, USA.
//
//

#include "fdrs_node_config.h"
#include <fdrs_node.h>

#define CMD_GET 1
#define CMD_SET 0

typedef struct irrigController
{
  uint address;
  int coilPin;
  bool updatePending;
  unsigned long status;
} irrigController;

/*

Format of loadFDRS....
loadFDRS(data, type, address);

Get Coil status of address 102 (data value is ignored)
loadFDRS(1, 1, 102);  

Turn off (set) Coil at address 102 for an indefinite amount of time
loadFDRS(0, 0, 102);  

Turn on Coil (set) at address 102 for an indefinite amount of time
loadFDRS(1, 0, 102);  

Turn on (set) Coil at address 102 for 300 seconds
loadFDRS(300, 0, 102);  

When turning on coil for certain amount of time the data value
must be 10 or greater and is in units of seconds.

*/

#define CONTROL_1 101  //Address for controller 1
#define CONTROL_2 102  //Address for controller 2
#define CONTROL_3 103  //Address for controller 3
#define CONTROL_4 104  //Address for controller 4

#define COIL_1 GPIO_NUM_16   //Coil Pin 1
#define COIL_2 GPIO_NUM_17   //Coil Pin 2
#define COIL_3 GPIO_NUM_18  //Coil Pin 3
#define COIL_4 GPIO_NUM_19  //Coil Pin 4

// These are set up for relay module which are active-LOW. 
// Swap 'HIGH'and 'LOW' to use the inverse.
#define ON LOW
#define OFF HIGH

irrigController cont[] = { 
  [0] = { .address = CONTROL_1, .coilPin = COIL_1, .updatePending = false, .status = 0 },
  [1] = { .address = CONTROL_2, .coilPin = COIL_2, .updatePending = false, .status = 0 },
  [2] = { .address = CONTROL_3, .coilPin = COIL_3, .updatePending = false, .status = 0 },
  [3] = { .address = CONTROL_4, .coilPin = COIL_4, .updatePending = false, .status = 0 },
  // [4] = { .address = CONTROL_5, .coilPin = COIL_5, .updatePending = false, .status = 0 },
  // [5] = { .address = CONTROL_6, .coilPin = COIL_6, .updatePending = false, .status = 0 },
  // [6] = { .address = CONTROL_7, .coilPin = COIL_7, .updatePending = false, .status = 0 },
  // [7] = { .address = CONTROL_8, .coilPin = COIL_8, .updatePending = false, .status = 0 },
  // [8] = { .address = CONTROL_9, .coilPin = COIL_9, .updatePending = false, .status = 0 },
  // [9] = { .address = CONTROL_10, .coilPin = COIL_10, .updatePending = false, .status = 0 },
  // [10] = { .address = CONTROL_11, .coilPin = COIL_11, .updatePending = false, .status = 0 },
  // [11] = { .address = CONTROL_12, .coilPin = COIL_12, .updatePending = false, .status = 0 },
  // [12] = { .address = CONTROL_13, .coilPin = COIL_13, .updatePending = false, .status = 0 },
};

unsigned long statusCheck = 0;
bool isData = false;
bool newStatus = false;
uint numcontrollers;

// Callback function in the controller that receives data to get or set coils
void fdrs_recv_cb(DataReading theData) {

  switch (theData.t) {
    case CMD_SET:  // Incoming command is to SET a value
      for(int i = 0; i < numcontrollers; i++) {
        if(cont[i].address == (uint) theData.id) {
          cont[i].status = (unsigned long) theData.d;
          cont[i].updatePending = true;
          isData = true;
          DBG1("Received SET cmd. Address: " + String(theData.id) + " value: " + String(theData.d));
          break;
        }
      }
      break;

    case CMD_GET:  // Incoming command is to GET a value
      for(int i = 0; i < numcontrollers; i++) {
        if(cont[i].address == theData.id) {
          if (digitalRead(cont[i].coilPin) == HIGH) {
            loadFDRS(1, STATUS_T, cont[i].address);
          } else {
            loadFDRS(0, STATUS_T, cont[i].address);
          }
          DBG1("Received GET cmd for address: " + String(theData.id));
          newStatus = true;
        }
      }
      break;

    default:
      DBG1("Unknown command: " + String(theData.t) + " address: " + String(theData.id) + " value: " + String(theData.d));
      break;

  }
}

void checkCoils() {  // Sends back a status report for each coil pin.
  for(int i = 0; i < numcontrollers; i++) {
    if (digitalRead(cont[i].coilPin == HIGH)) {
      loadFDRS(1, STATUS_T, cont[i].address);
    } else {
      loadFDRS(0, STATUS_T, cont[i].address);
    }
  }
  if (sendFDRS()) {
    DBG("Packet received by gateway");
  } else {
    DBG("Unable to communicate with gateway!");
  }
}

// Sets coil value according to data received in callback function
void updateCoils() {  
  for(int i = 0; i < numcontrollers; i++) {
    if(cont[i].updatePending == true) {
      if(cont[i].status == 0) {
        digitalWrite(cont[i].coilPin, OFF);
        DBG1("Address " + String(cont[i].address) + " coil pin " + String(cont[i].coilPin) + " off.");
      }
      else {
        digitalWrite(cont[i].coilPin, ON);
        DBG1("Address " + String(cont[i].address) + " coil pin " + String(cont[i].coilPin) + " on.");
      }
      if(cont[i].status >= 10) {
        DBG1("Address " + String(cont[i].address) + " coil pin " + String(cont[i].coilPin) + " on for " + String(cont[i].status) + " seconds.");
        cont[i].status = millis() + (cont[i].status * 1000); // this is the time when the coil will be commanded off
      }
      cont[i].updatePending = false;
    }
  }
}

void setup() {
  beginFDRS();
  DBG("FARM DATA RELAY SYSTEM :: Irrigation Module");
  pingFDRS(1000);

  numcontrollers = (uint) sizeof(cont)/sizeof(irrigController);
  // set up the physical outputs
  for(int i = 0; i < numcontrollers; i++) {
    pinMode(cont[i].coilPin, OUTPUT);
    digitalWrite(cont[i].coilPin, OFF);
  }

  // Register the callback function for received data
  addFDRS(fdrs_recv_cb);
  // Subscribe to Data Readings
  for(int i = 0; i < numcontrollers; i++) {
    subscribeFDRS(cont[i].address);
  }
}

void loop() {
  loopFDRS();
  if (isData) {
    isData = false;
    updateCoils();
    checkCoils();
  }
  if (newStatus) {
    newStatus = false;
    if (sendFDRS()) {
      DBG("Packet received by gateway");
    } else {
      DBG("Unable to communicate with gateway!");
    }
  }
  // periodically check for timer expiration on coils
  if(millis() - statusCheck > 500) {
    for(int i = 0; i < numcontrollers; i++) {
      if(cont[i].status >= 10 && (millis() > cont[i].status)) {
        cont[i].status = 0;
        digitalWrite(cont[i].coilPin, OFF);
        loadFDRS(OFF, STATUS_T, cont[i].address);
        DBG1("Address " + String(cont[i].address) + " coil pin " + String(cont[i].coilPin) + " turned off.");
        newStatus = true;
      }
    }
    statusCheck = millis();
  }
}
aviateur17 commented 5 months ago

Yeah, I see those lines are missing, not sure how that happened. Trying to see if I can fix that and then look at the checkCoils() function.

timmbogner commented 5 months ago

I can't figure out checkCoils. It seems like it should be showing me 1 as the status of the "off" ones, but they are all showing 0. Using the GET command retrieves the correct pin status, which confuses me even more. Here's the readout after setting a value. Everything works, and the relay clicks on:

20:58:21.738 -> [2] Incoming ESP-NOW DataReading from 0x1
20:58:21.738 -> [1] Received SET cmd. Address: 102 value: 15.00
20:58:21.738 -> [1] Address 102 coil pin 17 on.
20:58:21.738 -> [1] Address 102 coil pin 17 on for 15 seconds.
20:58:21.738 ->     Id: 101 - Type: 0 - Data loaded: 0.00
20:58:21.738 ->     Id: 102 - Type: 0 - Data loaded: 0.00
20:58:21.738 ->     Id: 103 - Type: 0 - Data loaded: 0.00
20:58:21.738 ->     Id: 104 - Type: 0 - Data loaded: 0.00
20:58:21.738 ->     Sending FDRS Packet!
20:58:21.738 ->     Packet received by gateway
20:58:37.175 ->     Id: 102 - Type: 0 - Data loaded: 1.00
20:58:37.176 -> [1] Address 102 coil pin 17 turned off.
20:58:37.176 ->     Sending FDRS Packet!
20:58:37.176 ->     Packet received by gateway
timmbogner commented 5 months ago

Ope, found it! It was the tiniest parenthesis misplacement on line 120. Should be if (digitalRead(cont[i].coilPin) == HIGH){. There is another change I'm going to try later today, essentially I want to change to this:

typedef struct irrigController
{
  uint address;
  int coilPin;
  bool updatePending = false;
  unsigned long status = 0;
} irrigController;

So that the initialization can look like this:

irrigController cont[] = { 
  [0] = { .address = CONTROL_1, .coilPin = COIL_1 },
  [1] = { .address = CONTROL_2, .coilPin = COIL_2 },
  [2] = { .address = CONTROL_3, .coilPin = COIL_3 },
  [3] = { .address = CONTROL_4, .coilPin = COIL_4 },

I'd also like to change cont to... not sure maybe coils?

When it turns off a pin using the timer, then sends back the status: I think the status should be read from the pin like in other cases. I don't know if that feature will ever be useful, but hypothetically maybe there's a leak and the relay board malfunctions. If the relay/solenoid was still powered somehow after we changed the IO to off, that would be incredibly good to know.

timmbogner commented 4 months ago

I just published this branch with the updated irrigation sketch and all of the changes I discussed above. You can just copy it into here if it all looks good to you.

aviateur17 commented 4 months ago

Timm, thanks for that example in your branch. I think those last two commits should have everything. Let me know if you see any issues.