0

Arduino Library not calling onMessage() handler
Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.

Arduino Library not calling onMessage() handler

by pmezey on Tue Feb 12, 2019 1:47 pm

I am using The Adafruit IO Arduino Library and am posting 4 values from the M0 with ATWINC1500 no problem up to an AIO dashboard. Cool! It posts values once every 10 minutes.

I then added a Toggle Block that reflects a boolean value from the M0. I registered a message handler for this feed to allow a web user to change its value and update the M0's actions, but the handler is never called. The M0 updates the toggle, but if I change its value on the website it does not call the handler method back on the M0.

Trying some things, I notice that almost always before posting my values back up to AIO, I get a rash of 4 or 5 "Dropped a packet" errors. It does update all the fields on AIO, but it first seems to drop packets before doing so. I changed my sleep time in the loop() function from 10 minutes to 3 seconds. At that rate, I do not get the "Dropped a packet" error and it does call the handler function as expected.

Is this an issue in the library or does this library require some sort of "always on" connection via a very short sleep time in the main loop() function? I am hoping the former as my project is power limited and chose this library over MQTT because I do not want to maintain a permanent wireless connection but instead poll the service every 10 (or more) minutes.

If this helps to understand the situation, it is my loop() function:

Code: Select all | TOGGLE FULL SIZE
void loop()
{
  io.run();
  getStats();                             // Acquires all data points and sends to AIO
  waterCheck();
  Alarm.delay(delayTime);      // delayTime is set to 10 minutes
}


Thanks for your help!

pmezey
 
Posts: 23
Joined: Tue May 30, 2017 9:56 pm

Re: Arduino Library not calling onMessage() handler

by brubell on Tue Feb 12, 2019 3:38 pm

Can you post your full code?

brubell
 
Posts: 282
Joined: Fri Jul 17, 2015 10:33 pm

Re: Arduino Library not calling onMessage() handler

by pmezey on Tue Feb 12, 2019 4:12 pm

Yes, thanks for taking a look. I am including just my main inline below immediately. I will subsequently zip up all the classes and dependencies so you have a fully working/compilable project to work with - that will just take a me a wee bit longer to gather and zip up.

Code: Select all | TOGGLE FULL SIZE
/***************************************************************************
 * Irrigation Project
 *
 * This project attempts to run the garden irrigation system.  It's key features
 * include:
 *   - Solar powered
 *   - Email connected (sends eamils for warning and/or errors as necessary)
 *   - Time aware.  It gets current local time from NTP servers
 *   - Climate Sensors.  It tracks temperature, pressure, and humidty.
 *   - Soil Sensors.  It tracks soil moisture using capacitive sensors
 *   - Battery Management.  It tracks overall battery health.  Emails as necessary
 *   - Power Management.  Minimizes WiFi and overal power consumption
 *   
  Created by Peter Mezey, December 23, 2018.
  Maybe soon, but not yet Released into the public domain.
  BSD license, all text above must be included in any redistribution
 ***************************************************************************/

 /***
  * TODO List
  * Put into sleep mode when not doing anything.  WiFi and M0
  * Add IOT fields to manually turn valve on/off to override the auto setting based on moisture
  *   Need one of these for each valve, or could get away with both on vs. both off
  * Add IOT field to turn on the valve immediately.  Could get away with one switch for both valves perhaps
  * Add IOT field to track #gallons dispensed per unit time
  * Seems the ClimateSensor can be set to null if initialization fails but it would then crash during a subsequent reading

  * [DONE] Integrate the solar power charger unit into all the battery smarts (e.g. this is both hardware and software additions)
  * Maybe add DHT22 capabilities to the climate sensor
  *
  * Re-check what value of soil moisture sensor sets off the watering
  * Boost conerter set to 8V now which pulls 680-690mA.  Try at 7V if that works
  * Check that voltage divider constants are correct for this battery and resistors in use
  * */
#include <WiFi101.h>
#include <Time.h>
#include <TimeAlarms.h>
#include "NTPTime.h"
#include "IFTTTWebHook.h"
#include "ClimateSensor.h"
#include "Battery.h"
#include "SoilSensor.h"
#include "config.h"


#define SOLENOID_PIN  5                 // Used to turn on the Solenoid via low side switch
#define RELAY_SET_PIN 10
#define RELAY_UNSET_PIN 11
#define LOGLEVEL      LOG_LEVEL_DEBUG  //LOG_LEVEL_NOOUTPUT   // Everything from this level and above is logged to Serial


NTPTime       localTime(WIFI_SSID, WIFI_PASS);
IFTTTWebHook  ifttt(WIFI_SSID, WIFI_PASS, IFTTT_HOST, IFTTT_URL);
ClimateSensor climate;
Battery       power;
SoilSensor    soil;

unsigned long delayTime = 60000;      // Milliseconds
bool wateringScheduled = false;
int  scheduleCount = 0;               // Need a way to count the number of times we schedule watering - only want once!
bool wateringOverride = false;        // If we override the watering via web, store the day we did that here
bool wateringNow = true;              // Start at true so first call to turn off solenoid works

// Setup for Adafruit IO
AdafruitIO_Feed *ioTemperature = io.feed("temperature");
AdafruitIO_Feed *ioPressure = io.feed("pressure");
AdafruitIO_Feed *ioHumidity = io.feed("humidity");
AdafruitIO_Feed *ioBattery = io.feed("battery");
AdafruitIO_Feed *ioSoilMoisture = io.feed("soilmoisture");
AdafruitIO_Feed *ioWaterScheduled = io.feed("waterscheduled");


void setup()
{
  ///// Serial Port and logging setup
  //
  Log.Init(LOGLEVEL, 9600);
 
  /////// WiFi setup
  //
  #if defined(ARDUINO_SAMD_ZERO)
    //Configure pins for Adafruit ATWINC1500 Feather
    WiFi.setPins(8,7,4,2);
  #endif

 
  /////// Solenoid and Relay Setup
  //      We will make sure the water/solenoid is off further down after email is setup
  pinMode(SOLENOID_PIN, OUTPUT);
  digitalWrite(SOLENOID_PIN, LOW);      // Make sure Solenoid is off when we start
  pinMode(RELAY_SET_PIN, OUTPUT);
  digitalWrite(RELAY_SET_PIN, LOW);
  pinMode(RELAY_UNSET_PIN, OUTPUT);
  digitalWrite(RELAY_UNSET_PIN, LOW);

  /////// io.adafruit.com setup.  This also connects to local WiFi network
  //
  Log.Debug("Connecting to Adafruit IO");
  io.connect();
  ioWaterScheduled->onMessage(handleWaterOverride);

  int count = 0;
  while (io.status() < AIO_CONNECTED)
  {
    Log.Debug((char *)(io.statusText()));
    Alarm.delay(1000);
    count += 1;
    if (count > 13)
    {
      Log.Error("*** Unable to connect to Adafruit IO, Stopping ***");
      ifttt.sendEmail("ERROR", SENSOR_NAME, "Unable to connect to Adafruit IO.  Halting");
      while (true) ;
    }
  }
  Log.Debug((char *)(io.statusText()));

  /////// Get local time from NTP servers
  //
  localTime.begin();
  localTime.printWiFiStatus();
  if (localTime.isTimeSet())
  {
    ifttt.sendEmail("INFO", SENSOR_NAME, "Success getting NTP time at startup: " +
                                          localTime.digitalClockDisplay() );
  }
  else
  {
    ifttt.sendEmail("WARNING", SENSOR_NAME, "Failure to set NTP time correctly on startup.  Using: " +
                                             localTime.digitalClockDisplay() );
  }

  /////// Temp/Pressure/Humidity sensor, soil sensor, and battery setup
  //
  climate.begin(SENSOR_NAME, &ifttt);
  power.begin(SENSOR_NAME, &ifttt);
  soil.begin(SENSOR_NAME, &ifttt);


  ///////  Since we are using a latching relay, it stays in whatever position it was in last
  //       time you set it.  So at startup, make sure it's off.  Since this method sends email,
  //       wanted to do this after our network connection and time is all setup
  // NOTE: This should be called after the power.begin() call above
  turnSolenoidOff();

 
  /////// WiFi connection information and tweaks for power saving
  //
  WiFi.lowPowerMode(); // go into power save mode when possible!
}


void loop()
{
  // io.run(); is required for all sketches.
  // it should always be present at the top of your loop
  // function. it keeps the client connected to
  // io.adafruit.com, and processes any incoming data.
  io.run();
  getStats();
  waterCheck();
  Alarm.delay(delayTime);
}

void getStats()
{
  Log.Debug(localTime.digitalClockDisplay().c_str());
 
  climate.climateValues();
  ioTemperature->save(climate.getTemperature());
  ioPressure->save(climate.getPressure());
  ioHumidity->save(climate.getHumidity());
 
  power.batteryValues();
  ioBattery->save(power.getVolts());

  soil.soilValues();
  ioSoilMoisture->save(soil.getMoisture());

  // Schedule watering when the reading gets low enough.  Can only schedule it
  // once per day for a 1/2 hour.  If you override the schedule via the web, the
  // system cannot override your web override until the following day
  if (soil.getMoisture() < SOIL_TOO_DRY && wateringScheduled == false && wateringOverride == false)
  {
    scheduleWatering();
  }
  if (wateringScheduled)
  {
    Log.Debug("Setting ioWaterScheduled to ON");
    ioWaterScheduled->save("ON");
  }
  else
  {
    Log.Debug("Setting ioWaterScheduled to OFF");
    ioWaterScheduled->save("OFF");
  }
}

void turnSolenoidOn()
{
  Log.Debug("Turning the Solenoid ON");
  // We check wateringScheduled because it could have been manually turned off at the web
  if (wateringScheduled == true)
  {
    // When time to water, check if battery has enough juice to do the job.  If not,
    // email the user and skip the watering for this day.  This will continue this way until
    // the battery reaches a safer charge level.  Battery will eventually shut off if it gets
    // too low
    if (power.isCritical() == false)
    {
      Log.Debug("Open the relay");
      digitalWrite(RELAY_SET_PIN, HIGH);        // Latching relay provides battery level power to power boost module
      Alarm.delay(10);
      digitalWrite(RELAY_SET_PIN, LOW);         // Latching relay should be latched ON now
      Alarm.delay(500);                         // Give the power supply a little time to spin up to power
      digitalWrite(SOLENOID_PIN, HIGH);         // Open the solenoid/water valvle
      ifttt.sendEmail("INFO", SENSOR_NAME, String("Water has been turned ON at: ") +
                                                  localTime.digitalClockDisplay());
      wateringNow = true;
    }
    else
    {
      ifttt.sendEmail("ERROR", SENSOR_NAME, String("Battery is now critical (less than 3.45V).  ") +
                                            String("System wants to water but will not until battery is recharged"));
    }
  }
}

void turnSolenoidOff()
{
  Log.Debug("Turning the Solenoid OFF");
  if (wateringNow == true)
  {
    Log.Debug("Turning solenoid OFF");
    digitalWrite(SOLENOID_PIN, LOW);          // Close the solenoid/water valve
    digitalWrite(RELAY_UNSET_PIN, HIGH);      // Latching relay is turned off - no power to power boost
    Alarm.delay(10);
    digitalWrite(RELAY_UNSET_PIN, LOW);       // Latching relay should be latched OFF now
    ifttt.sendEmail("INFO", SENSOR_NAME, String("Water has been turned OFF at: ") +
                                         localTime.digitalClockDisplay());
  }

  wateringScheduled = false;                // Can only water at most once per day
  wateringNow = false;                      // and we are done for today
  scheduleCount = 0;                        // so allow another scheduled watering to occur next day
  wateringOverride = false;                 // and reset the web override flag so system can schedule it automatically
}


// This method just makes sure that we are not watering past our expected time.  It's basically
// a check that if the timer at 5:00 am starts the watering process that the timer at 5:30 has
// stopped the watering
// NOTE: I've seen the time wander a minute or two within the course of a whole day so I've set
// the NTPTime class to reset the time twice a day for more accuracy and below I'll check for
// one minute after the scheduled stop time (e.g. 5:32am and beyond).
void waterCheck()
{
  if (wateringNow == true)
  {
    if (hour() == WATER_START_TIME and minute() > (WATER_DURATION + 1))
    {
      turnSolenoidOff();
      ifttt.sendEmail("WARNING", SENSOR_NAME, String("Had to manually turn off water at: ") +
                                              localTime.digitalClockDisplay() +
                                              String(".  Expected to turn off automatically at 5:30am"));
    }
  }
}


void scheduleWatering()
{
  Log.Debug("ScheduleWatering called, checking the scheduleCount: %d", scheduleCount);
  // Since you can turn watering on or off manually at the web site, need to make sure you do not
  // schedule this thing more than once in a day
  if (scheduleCount == 0)
  {
    Alarm.alarmOnce(WATER_START_TIME, 0, 0, turnSolenoidOn);
    Alarm.alarmOnce(WATER_START_TIME, WATER_DURATION, 0, turnSolenoidOff);
    scheduleCount += 1;
  }
  wateringScheduled = true;
}


void handleWaterOverride(AdafruitIO_Data *data)
{
  Log.Info("Received command from web to override current watering plan: %s", data->value());

  if (!strcmp(data->value(), "ON"))       // Turning watering on
  {
    if (wateringScheduled == false)
    {
      scheduleWatering();
    }
  }
  else
  {
    wateringScheduled = false;
  }
  wateringOverride = true;
}

pmezey
 
Posts: 23
Joined: Tue May 30, 2017 9:56 pm

Re: Arduino Library not calling onMessage() handler

by brubell on Tue Feb 12, 2019 5:16 pm

Is this an issue in the library or does this library require some sort of "always on" connection via a very short sleep time in the main loop() function?


The on_handler function uses the Adafruit MQTT library, so it needs to ping the service (via the io.run at the top of the loop). You should remove the delay after the loop runs.

You may want to add a ioWaterScheduled->get(); after ioWaterScheduled->onMessage(handleWaterOverride);, and place the onMessage handler setup after the IO connection is established.

brubell
 
Posts: 282
Joined: Fri Jul 17, 2015 10:33 pm

Re: Arduino Library not calling onMessage() handler

by pmezey on Wed Feb 13, 2019 12:12 am

Tried a couple things with mixed results:

- Tried second part of your suggestion first: call ioWaterScheduled->get() after ioWaterScheduled->onMessage() and move that after the IO connection is established and there was no change in behavior.
- Next I mostly removed the delay... I reduced it to 1 second so I could call my sensor reading functions every 60 iterations to basically achieve a 1 minute pause between readings but still call io.run() every second. This does work, although it was a little flakey (i.e. it worked about 95% of the time... sometimes a click on the web site did not register). The Dropped packet errors have mostly stopped, although they still happen occasionally. The loop() function looks like this:

Code: Select all | TOGGLE FULL SIZE
void loop()
{
  count += 1;
  io.run();
  if (count % 60 == 0)      // Just for testing, only get sensor data every minute
  {
    getStats();
    waterCheck();
  }
  Alarm.delay(1000);
}


- And finally, I tried another idea... going back to my long delay, I added a second call to io.run() after all my sensor readings and IO updates with another 1 second delay. This had very interesting results and looks like this:

Code: Select all | TOGGLE FULL SIZE
void loop()
{
  io.run();
  getStats();
  waterCheck();
  Alarm.delay(1000);
  io.run();
  Alarm.delay(delayTime);    // This is a "long" delay.  10 minutes or more
}


With this setup, the Dropped packet errors also mostly stopped and it works sometimes although far less regularly than with the above setup. If I change the toggle field on the web and I get one Dropped packet error, then it won't call my callback. If I get zero Dropped packet errors, it does call my callback. AhHAH. I think there is a correlation between Dropped packet errors and data feeds (1 dropped packet per data field saved).

My idea with this last try was that you mentioned the onMessage() call is really an MQTT call, so I thought that by calling io.run() twice it's like a ping to wake things up before moving data around and it almost works but is not nearly consistent enough to count on.

Soooo, your idea to remove the delay altogether is the best option from a functional perspective, but since my system is energy constrained I do not want the wifi pinging back and forth every second or more. If I leave in the long delay to conserve energy, am I basically out of luck with reading values that may have been altered up at the web site? Or for that one Toggle Field value, perhaps I should fashion my own REST call to check it's value independently?

Thanks again for your input!

Oh, let me know if you still need the full project uploaded. I was thinking to create a totally slimmed down test case for this without all my classes there if that is helpful.

pmezey
 
Posts: 23
Joined: Tue May 30, 2017 9:56 pm

Re: Arduino Library not calling onMessage() handler

by brubell on Thu Feb 14, 2019 11:15 am

Soooo, your idea to remove the delay altogether is the best option from a functional perspective, but since my system is energy constrained I do not want the wifi pinging back and forth every second or more. If I leave in the long delay to conserve energy, am I basically out of luck with reading values that may have been altered up at the web site? Or for that one Toggle Field value, perhaps I should fashion my own REST call to check it's value independently?


Nice troubleshooting! If you're energy-constrained, you could try the ESP8266's DeepSleep mode (an example of using this with Adafruit IO is here: https://github.com/adafruit/Adafruit_IO ... _deepsleep).

If you're really energy-constrained, you may want to switch to another transport - like LoRa (https://www.adafruit.com/product/3231). We have a guide for connecting these FeatherWings to IO: https://learn.adafruit.com/multi-device ... re-network

brubell
 
Posts: 282
Joined: Fri Jul 17, 2015 10:33 pm

Re: Arduino Library not calling onMessage() handler

by pmezey on Thu Feb 14, 2019 11:50 pm

Oh my god - you've given me so many ideas. And then I stumbled into your TPL5110 board to shut down power on a timer basis. So many options, so little time.

OK, for this project I'm sticking with what I have so far with the Feather M0. Plus I was tinkering last night and could make a direct REST call to the Adafruit IO with no issues to fetch the most recent value of my Toggle field. I think I will ditch the whole AIO library for this one and use direct REST calls to get and set my variables. That way I have no underlying keep-alive needed, no dropped packets, etc.

Was curious though if you have any sample code for putting the M0 CPU into deep sleep? I am putting the WiFi into LowPowerMode, but the CPU itself I have not yet figured out how to sleep. I know there are some articles on it I started to research but if you have any samples laying about you can share that would be awesome too.

I think the next project will definitely go with the LoRa angle.

pmezey
 
Posts: 23
Joined: Tue May 30, 2017 9:56 pm

Re: Arduino Library not calling onMessage() handler

by brubell on Fri Feb 15, 2019 10:47 am

h if you have any sample code for putting the M0 CPU into deep sleep?

The SAMD21 has a low-power mode, but it's not the same as the deep-sleep power mode on the ESP8266.

Check out Adafruit_Sleepydog, it works with the SAMD21: https://github.com/adafruit/Adafruit_SleepyDog

brubell
 
Posts: 282
Joined: Fri Jul 17, 2015 10:33 pm

Please be positive and constructive with your questions and comments.