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:
- Boron on my carrier board for outdoor applications:
Boron / Xenon / Argon Carrier for Outdoor Applications - Enclosed in @phillycheeseman fantastic 3D printed enlcosure:
Outdoor Project Case - #7 by phillycheeseman - Attached to a very cool soil moisture sensor from @micromet at CSU's soil and crop sciences department:
https://catalog.colostate.edu/general-catalog/colleges/agricultural-sciences/soil-crop-sciences/
You can also use the SoilWatch 10 - https://www.tindie.com/products/pinotech/soilwatch-10-soil-moisture-sensor/ - Controlling a zone for my garden with a Rachio controller and their developer API:
https://rachio.readme.io/v1.0/docs
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