First Application for SleepHelper - Garden Watering

I realize that the sleepHelper thread is getting long and I wanted to separate out an application of this library with the on-going dialog about the library itself. I know there has not been a lot of focus yet on the potential of @rickkas7 new library but, I believe it has huge potential for this community.

My hops is that by showing it in action, others will start to look for opportunities to test it out. The library is new and Rick has labeled it as (preliminary) but the only way to get it to production grade will be for many members of the community to test is in realistic settings over a period of time. My intent will be to focus questions on the library in the original thread and to refine this project here.

This project is fairly simple, I have a small garden next to my home and my wife and I enjoy growing vegetables each summer. As the NC summer can get hot, and we both travel a lot, I needed to find a way to keep these plants watered to ensure a good crop. Here is my setup:

Concept of operations - during the day, the device will monitor soil moisture levels every 15 minutes disconnected and sleeping in between samples. Then, once an hour, it will connect to Particle and send a webhook to Ubidots with the 4 samples. If the soil moisture drops below a threshold or the soil temperature gets too hot, a webhook will be sent to trigger a watering event of a specific duration. Obviously, this will only happen when the device is connected.

I used the sleep helper demo code as the starting point and modified each of the header files to implement this approach. Because of the modular nature of the code, no change was needed to the main source file (other than the project description):

/**
 * @brief Sleep Helper - Garden Watering 
 * @details This device sits in my flower bed and will test the soil for moisture content - if it is too high, it will call for water.
 * @author Chip McClelland based on Library and Example Code by @Rickkas
 * @link https://rickkas7.github.io/SleepHelper/index.html @endlink - Documentation
 * @link https://github.com/rickkas7/SleepHelper/ @endlink - Project Repository
 * @date 5 July 2022
 * 
 */

// Include needed Particle / Community libraries
#include "Particle.h"
#include "AB1805_RK.h"
#include "MB85RC256V-FRAM-RK.h"
#include "PublishQueuePosixRK.h"
#include "SleepHelper.h"

// Include headers that are part of this program's structure and called in this source file
#include "particle_fn.h"                            // Place where common Particle functions will go
#include "sleep_helper_config.h"                    // This is where we set the parameters for the Sleep Helper library

// Set logging level and Serial port (USB or Serial1)
SerialLogHandler logHandler(LOG_LEVEL_INFO);       //  Limit logging to information on program flow               

// Set the system modes
SYSTEM_THREAD(ENABLED);
SYSTEM_MODE(SEMI_AUTOMATIC);
STARTUP(System.enableFeature(FEATURE_RESET_INFO));  // So we know why the device reset

// Instantiate services and objects - all other references need to be external
AB1805 ab1805(Wire);                                // Rickkas' RTC / Watchdog library
MB85RC64 fram(Wire, 0);                             // Rickkas' FRAM library

// Support for Particle Products (changes coming in 4.x - https://docs.particle.io/cards/firmware/macros/product_id/)
PRODUCT_ID(PLATFORM_ID);                            // Device needs to be added to product ahead of time.  Remove once we go to deviceOS@4.x
PRODUCT_VERSION(0);
char currentPointRelease[6] ="0.03";

void setup() {

    initializePinModes();                           // Sets the pinModes

    initializePowerCfg();                           // Sets the power configuration for solar

    storageObjectStart();                           // Sets up the storage for system and current status in storage_objects.h

    particleInitialize();                           // Sets up all the Particle functions and variables defined in particle_fn.h


    {                                               // Initialize AB1805 Watchdog and RTC                                 
        ab1805.setup();

        ab1805.resetConfig();                       // Reset the AB1805 configuration to default values

        ab1805.setWDT(AB1805::WATCHDOG_MAX_SECONDS);// Enable watchdog
    }

	PublishQueuePosix::instance().setup();          // Initialize PublishQueuePosixRK

    sleepHelperConfig();                            // This is the function call to configure the sleep helper parameters in sleep_helper_config.h

    SleepHelper::instance().setup();                // This puts these parameters into action
}

void loop() {
    SleepHelper::instance().loop();                 // Monitor and manage the sleep helper workflow

    ab1805.loop();                                  // Monitor the real time clock and watchdog
    
    PublishQueuePosix::instance().loop();           // Monitor and manage the publish queue

    storageObjectLoop();                            // Compares current system and current objects and stores if the hash changes (once / second) in storage_objects.h
}

Notice that this file is a svelt 73 lines and by breaking out the implementation specifics into small / manageable header files, my hope is this approach will make it easier to share code between projects and to maintain the code base over time.

Since I did not want to change the structure of the sleepHelper webhook, I modified my webhook to extract the needed data and reformat it for the Ubidots bulk upload API call.

The JSON payload looks like this:

[
  {
    "device": "{{{PARTICLE_DEVICE_ID}}}",
    "values": [
      {
        "battery": "{{{soc}}}",
        "internaltemp": "{{{eh.0.c}}}",
        "soiltemp": "{{{eh.0.st}}}",
        "soilmoist": "{{{eh.0.sm}}}",
        "waterstate": "{{{eh.0.ws}}}",
        "connecttime": "{{{ttc}}}",
        "timestamp": "{{{eh.0.t}}}000"
      },
      {
        "battery": "{{{soc}}}",
        "internaltemp": "{{{eh.1.c}}}",
        "soiltemp": "{{{eh.1.st}}}",
        "soilmoist": "{{{eh.1.sm}}}",
        "waterstate": "{{{eh.1.ws}}}",
        "connecttime": "{{{ttc}}}",
        "timestamp": "{{{eh.1.t}}}000"
      },
      {
        "battery": "{{{soc}}}",
        "internaltemp": "{{{eh.2.c}}}",
        "soiltemp": "{{{eh.2.st}}}",
        "soilmoist": "{{{eh.2.sm}}}",
        "waterstate": "{{{eh.2.ws}}}",
        "connecttime": "{{{ttc}}}",
        "timestamp": "{{{eh.2.t}}}000"
      },
      {
        "battery": "{{{soc}}}",
        "internaltemp": "{{{eh.3.c}}}",
        "soiltemp": "{{{eh.3.st}}}",
        "soilmoist": "{{{eh.3.sm}}}",
        "waterstate": "{{{eh.3.ws}}}",
        "connecttime": "{{{ttc}}}",
        "timestamp": "{{{eh.3.t}}}000"
      }
    ]
  }
]

My hope is to generalize this to accommodate different numbers of samples but the challenges with this are in the other thread. The Ubidots folks - particularly David - have been very helpful in this work.

My plan is to let this code run over the coming weeks and evaluate the stability and performance of the library. If I identify issue, I will put them under the sleep helper entry. If I enhance or extend this code, I will make the changes here.

I hope this demonstration code helps others see the power in this new approach. Please let me know what you think of my approach, the solution and anything I could do better.

Thanks, Chip

7 Likes

This is SO COOL! Thanks for sharing, Chip!

1 Like

Thank you @Colleen ,

Made some progress with help from @Ubidots . The Webhook structure is now a lot simpler and will work no matter how few or many samples you take per reporting period. Here is the Webhook that will automatically iterate over the number of samples you send and put it into a bulk upload API format that Ubidots can consume.

[
 {
  "device": "{{{PARTICLE_DEVICE_ID}}}",
  "values": [
   {{#eh}}
    {
        "battery": {
          "value": "{{bc}}",
          "context": {
            "key1": "{{bs}}"
          }
        },
        "internaltemp": "{{{c}}}",
        "soiltemp": "{{{st}}}",
        "soilmoist": "{{{sm}}}",
        "waterstate": "{{{ws}}}",
        "connecttime": "{{{ttc}}}",
        "timestamp": "{{{t}}}000"
    },
   {{/eh}}
   {}
  ]
 }
]

This builds on the Intermediate Webhooks tutorial and shows the power of Particle’s web hooks and the use of Mustache.

Here is the Ubidots documentation of the bulk upload API:

I will post updates as I get more experience in running this application and monitoring its stability. So far it has performed perfectly and my wife and I have already started to harvest peppers from the garden.

Chip

2 Likes

@all,

An update. Thinks were running like clock work for the whole week. Then, this morning, the device failed to connect for several hours. Then, when it did connect, it sent all the missing data.

The thing is that it is hard to tell what happened as sleepHelper abstracts us from the underlying connection state machines. The header looked like this:

{
  "soc": 93.5,
  "ttc": 216675,
  "rr": 0
}

This underscores one of the issues raised on the other thread, we need some way of capturing diagnostics events. For example, here are the diagnostic events I capture today:

/* Alert Code Definitions
* 0 = Normal Operations - No Alert
// device alerts
* 10 = Battery temp too high / low to charge
* 11 = PMIC Reset required
* 12 = Initialization error (likely FRAM)
* 13 = Excessive resets
* 14 = Out of memory
* 15 = Particle disconnect or Modem Power Down Failure
// deviceOS or Firmware alerts
* 20 = Firmware update completed
* 21 = Firmware update timed out
* 22 = Firmware update failed
* 23 = Update attempt limit reached - done for the day
// Connectivity alerts
* 30 = Particle connection timed out but Cellular connection completed
* 31 = Failed to connect to Particle or cellular - skipping to next hour
* 32 = Failed to connect quickly - resetting to keep trying
// Particle cloud alerts
* 40 = Failed to get Webhook response when connected
*/

These events can be collected while the device is off-line and sent once it connects. They have their own webhook and can be aggregated and analyzed across the fleet using the back-end database.

@Colleen, perhaps we could add something like this to the library.

Thanks, Chip

3 Likes

Yeah, I sure like the idea of capturing diagnostic events like this. I wonder if you’d capture the diagnostic code like you have listed and then a timestamp and could publish an array of diagnostic events. That way, when it was offline for any period of time, you’d have the sequence of diagnostic events that happened during that duration of time. The structure could be similar to the JSON used for sensor data with a timestamp and then alert code.

On a related note… do you know if your AB1805 watchdog kicked in and did a hard 30 second power down during this failure to connect for several hours event? Did the watchdog not do it’s job for some reason? Particle.Connected() returned true so the AB1805 deep power down wasn’t executed but it truly wasn’t connected or able to publish data?

1 Like

@jgskarda ,

I am thinking it might be a great feature for the sleepHelper library - a .withErrorCaptured callback that would construct a JSON of errors just as you outlined.

It might also be helpful if there was some standard taxonomy of error codes. I tried to organize mine by decades but they could have escalating consequences (codes 10 -40 results in a reset while 40-xx result in a power cycle) for example.

Finally, no, I don’t think the watchdog ever fired as it is only triggered now based on transiting the main loop. What I think is needed are the ability to add other events such as .withPowerCycleAfterNotConnectingForTooLong. which would trigger a power cycle.

My hope is to let this device run and, over time, compile a list of feature requests for the library (or examples how to implement them without changing the library) so this can be the basis of production code.

Today, observed the device connecting a weird times. It is almost as if the AB1805 integration is getting corrupted and resets and power-cycles are not bringing it back.

Thanks for your comments.

Chip

1 Like

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.