I looked for the category “Troubleshooting” but it isn’t selectable, so this category is my best guess as to where this query should be posted.
Application:
My application employs two Electrons that, together, control the on/off status of a pump in a water well. Under normal circumstances, firmware on an Electron named ‘Tank’ at the location of a large water storage tank sends a ‘0’ or a ‘1’ to another Electron named ‘Pump’ located at the wellhead about 1500 feet away. A ‘0’ indicates that the pump should be off, while a ‘1’ indicates - as a function of a float switch in the tank - that the pump should come on.
Issue:
Due to problems with a float switch in the tank, the ‘Tank’ Electron has been unplugged for a while and the pump has been controlled manually by calling an on/off function in the firmware running on the ‘Pump’ Electron. Yesterday, the ‘Pump’ Electron went off line briefly, likely due to a loss of cellular signal (happens a lot; signal is very weak at all times). When the device came back on line, the code on that Electron executed a Particle.publish command that placed an entry in the event log suggesting that the ‘Tank’ Electron sent a ‘1’ and the pump was turned on. If I hadn’t noticed it and manually turned the pump off, the tank would have eventually overflowed.
Since the ‘Tank’ Electron is off line - and has been for a couple of weeks - there is no way it could have sent anything. So my question is: how/why was the myHandler routine invoked if there is no incoming event? This has never happened before, so I am both perplexed and concerned that it could happen again.
Following is the myHandler routine. I have also attached a screenshot of the event log that shows the event in question…At the very bottom of this post I have also provided the entire code running on the ‘Pump’ Electron.
void myHandler(const char *event, const char *data)
{
if (manualTimer){ // Do not react to publishes if command was sent to manually turn pump on for time period.
return;
}
if (manualSwitch){ // Do not react to publishes if command was sent to manually turn pump on.
return;
}
lastTankUpdate = millis();
if ( linkUp == false ){
linkUp = true; // If the ink was previously down, it just now came up
// Particle.publish("Link", "Up", PRIVATE); // Send status to IFTTT for notification and logging
}
String d = String(data);
int status = d.toInt();
if (status & 1){
controller.turnOnRelay(1);
pumpOn = true;
if (relayState == 0){
relayState = controller.readRelayStatus (1);
pumpOnTime = millis(); // Set start time
pumpFault = false;
Particle.publish("Status", "Tank On", PRIVATE); // Send status to IFTTT for notification and logging
}
}else{
controller.turnOffRelay(1);
pumpOn = false;
pumpFault = false;
if (relayState == 1){
relayState = controller.readRelayStatus (1);
Particle.publish("Status", "Tank Off", PRIVATE); // Send status to IFTTT for notification and logging
}
}
}
Does anyone have any idea what could have caused this?
Entire code:
// This #include statement was automatically added by the Particle IDE.
#include <NCD2Relay.h>
NCD2Relay controller;
SYSTEM_THREAD(ENABLED)
unsigned long previousMillis = 0;
unsigned long lastPublish = millis();
unsigned long lastReset = millis();
unsigned long lastTankUpdate = millis();
static unsigned long pumpOnTime = 0;
static unsigned long tempRuntime = 0;
int relayState;
int previousStatus;
bool manualTimer = false;
bool manualSwitch = false;
bool linkUp = true;
bool pumpFault = false;
bool pumpOn = false;
void myHandler(const char *event, const char *data);
void setup() {
Particle.variable("Pump_Status", relayState);
Particle.function("Pump_On_Off", triggerRelay);
Particle.function("Pump_Minutes", pumpWaterNow); // Gets runtime in minutes from calling funtion on Particle Console; value must be an Integer....1,2,3, etc.
controller.setAddress(0,0,0); // Re-initialize processor
relayState = controller.readRelayStatus(1); // Set variable to current state of relay #1
Particle.subscribe("IO", myHandler, MY_DEVICES); // Look for incoming IO message from tank
linkUp = true;
lastReset = millis();
}
void myHandler(const char *event, const char *data)
{
if (manualTimer){ // Do not react to publishes if command was sent to manually turn pump on for time period.
return;
}
if (manualSwitch){ // Do not react to publishes if command was sent to manually turn pump on.
return;
}
lastTankUpdate = millis();
if ( linkUp == false ){
linkUp = true; // If the ink was previously down, it just now came up
// Particle.publish("Link", "Up", PRIVATE); // Send status to IFTTT for notification and logging
}
String d = String(data);
int status = d.toInt();
if (status & 1){
controller.turnOnRelay(1);
pumpOn = true;
if (relayState == 0){
relayState = controller.readRelayStatus (1);
pumpOnTime = millis(); // Set start time
pumpFault = false;
Particle.publish("Status", "Tank On", PRIVATE); // Send status to IFTTT for notification and logging
}
}else{
controller.turnOffRelay(1);
pumpOn = false;
pumpFault = false;
if (relayState == 1){
relayState = controller.readRelayStatus (1);
Particle.publish("Status", "Tank Off", PRIVATE); // Send status to IFTTT for notification and logging
}
}
}
// Function to turn pump on or off manually:
int triggerRelay(String command){
String relayCommand = command;
if (relayCommand.equalsIgnoreCase("on")){
controller.turnOnRelay(1);
relayState = controller.readRelayStatus (1);
Particle.publish("Status", "Manual On", PRIVATE); // Send status to IFTTT for notification and logging
manualSwitch = true;
return 1;
}
if (relayCommand.equalsIgnoreCase("off")){
controller.turnOffRelay(1);
relayState = controller.readRelayStatus (1);
Particle.publish("Status", "Manual Off", PRIVATE); // Send status to IFTTT for notification and logging
manualSwitch = false;
return 1;
}
}
// Function to turn pump on for a specified number of minutes
int pumpWaterNow(String command) { // Gets Runtime in Minutes; value must be an Integer....1,2,3, etc.
tempRuntime = ( command.toInt() * 60 * 1000 ); // Minutes * 60 seconds * 1000 ms
previousMillis = millis();
controller.turnOnRelay(1); // Turn ON Water Pump
relayState = controller.readRelayStatus (1);
Particle.publish("Status", "Timer On", PRIVATE); // Send status to IFTTT for notifciation and logging
manualTimer = true;
while ( (millis() - previousMillis) < (tempRuntime) ) { // Better than delay()
Particle.process();
} // End While
controller.turnOffRelay(1); // Turn OFF Water Pump
relayState = controller.readRelayStatus (1);
Particle.publish("Status", "Timer Off", PRIVATE); // Send status to IFTTT for notification and logging
manualTimer = false;
} // End pumpWaterNow Function
void loop(){
if (pumpOn){
if (pumpFault == false){ // No previous fault reported
if ( millis() - pumpOnTime > 10800000){ // If pump has been on for over 3 hours
Particle.publish("Status", "Pump Fail?", PRIVATE); // Send status to IFTTT for notification and logging if pump has been running longer than 2 hours
pumpFault = true;
pumpOnTime = millis(); // Set newstart time
}
}
}
if (pumpOn == false){ // Don't want to execute the following code if the pump is running
if ( millis() > lastReset + 3600000 ){ // If it's been more than 60 minutes since last reset
controller.setAddress(0,0,0); // Reinitialize the MCP23008 chip on the NCD relay board
lastReset = millis(); // Reset last initialization time to current program time
}
}
if ( millis() - lastTankUpdate > 720000 ){ // If it's been more than 12 minutes since last 'IO' message from tank
controller.turnOffRelay(1); // In case pump was running when link was lost, turn it off to avoid overfilling
lastTankUpdate = millis(); // Reset tank update time to current program time
// Particle.publish("Link", "Down", PRIVATE); // Send status to IFTTT for notifciation and logging
linkUp = false; // Link is down
}
if ( millis() - lastPublish > 600000 ){ // Has 10 minutes elapsed since last keepalive?
// Particle.publish("KA", "OK", PRIVATE); // Send an "I'm here" keepalive to tank
lastPublish = millis(); // Reset last keepalive time to current program time
}
}