I am working on a low power pressure sensor reading application and followed the guide to shut off the Swan MCU between readings. Everything appears to be working correctly with the code (haven’t tested the timestamp stuff yet) but I was wondering if there is a way to batch multiple readings as one event in this style of setup since the host is powering down in between readings. I am relatively new to Blues so any help is appreciated. I have attached my code as a reference point. Thanks!
#include <Arduino.h>
#include <Notecard.h>
#define productUID "***********:pressure*****"
#define SENSOR_PIN A1 // Analog pin for pressure sensor input
#define VREF 3.3f // Reference voltage for ADC
#define OUTBOUND_TIME_MINS 5
#define SLEEP_TIME_SECONDS 60
#define BATCH_SIZE (OUTBOUND_TIME_MINS * 60 / SLEEP_TIME_SECONDS)
// Define the sensor parameters --> 0-100 PSI, 0.5-2.5 V Range, 0.02 V per PSI
static const float pressureOffset = 0.5f; // 0.5V offset for 0 PSI
static const float pressureScale = 0.02f; // 0.02V/PSI scale factor
Notecard notecard;
void setup() {
Serial.begin(115200);
pinMode(SENSOR_PIN, INPUT);
analogReadResolution(12); // Set ADC resolution to 12 bits for STM32
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);
//Establish a connection to Notehub every 5 minutes to sync data --> change outbound time to however long needed
notecard.begin();
{
J *req = notecard.newRequest("hub.set");
JAddStringToObject(req, "mode", "periodic");
JAddStringToObject(req, "product", productUID);
JAddNumberToObject(req, "outbound", OUTBOUND_TIME_MINS);
notecard.sendRequest(req);
}
time_t nowEpoch = 0;
{
J *req = notecard.newRequest("card.time");
J *rsp = notecard.requestAndResponse(req);
if (rsp && JHasObjectItem(rsp, "time")) {
J *val = JGetObjectItem(rsp, "time");
if (JIsNumber(val)) {
nowEpoch = (time_t)JNumberValue(val);
}
}
notecard.deleteResponse(rsp);
}
uint32_t sensorValue = analogRead(SENSOR_PIN); // Read the analog value from the sensor pin
float voltage = sensorValue * (VREF / 4095.0f); // Convert ADC value to voltage (12-bit resolution)
float pressure = (voltage - pressureOffset) / pressureScale; // Convert voltage to pressure in PSI
J *req = notecard.newRequest("note.add");
if(req != NULL) {
JAddStringToObject(req, "file", "pressureInducer.qo");
J *body = JAddObjectToObject(req, "body");
if (body) {
JAddNumberToObject(body, "pressure", pressure); // Add pressure value to the request body
JAddNumberToObject(body, "timestamp", nowEpoch); // Add timestamp to the request body
}
notecard.sendRequest(req);
}
}
void loop() {
J *req = notecard.newCommand("card.attn");
JAddStringToObject(req, "mode", "sleep");
JAddNumberToObject(req, "seconds", SLEEP_TIME_SECONDS); //Turn back on after 60s (AKA Sleep for 60s)
notecard.sendRequest(req);
delay(1000); //Ensure device is sleeping
}
Hey @jcorcoran,
Yes, but doing so will introduce complexity, as you’ll need to store the state of your accumulated readings in a memory location that won’t be wiped when the host sleeps.
The easiest way to do that would be with the card.attn
request’s payload
argument, which allows you to store a base64-encoded string, and then retrieve it from the host when it wakes back up. There are some examples of that workflow in this section of our documentation: Low Power Design - IoT Connectivity at Blues
You can also accomplish this sort of thing with the STM32LowPower library (GitHub - stm32duino/STM32LowPower: Arduino Low Power library for STM32), which we have some docs on here Feather MCU Low Power Management - IoT Connectivity at Blues. I personally haven’t tried using the library to retain state, but it should be possible. @abucknall might have some more ideas on this one.
To take a step back, are you just looking to put multiple readings in a single Note to reduce event ingress costs? Your life will certainly be easier if you keep one reading per Note (you can even remove the timestamp code altogether).
TJ
1 Like
Thank you for the response TJ. Yes the end goal is to have a few dozen of these sensors set up in low power mode to take a pressure reading every 5 minutes for around a year. This will get expensive however and I would like to go the batching route to avoid this and process all of the data later on. I was able to setup batching using the LowPower library originally but I don’t think it was setup correctly (readings from an ammeter were at least 16 - 80 mA if not higher at all times).
Notecard notecard;
// how many samples to batch up
static const int BATCH_SIZE = 10;
// storage for the batch
float pressureBuffer[BATCH_SIZE];
time_t timestampBuffer[BATCH_SIZE];
// current index into the buffer
int bufferIndex = 0;
void setup() {
delay(1000);
usbSerial.begin(115200);
analogReadResolution(12); // STM32: set ADC to 12-bit
pinMode(A1, INPUT);
notecard.begin();
//notecard.setDebugOutputStream(usbSerial);
// register the product with the hub
{
J *req = notecard.newRequest("hub.set");
if (req != NULL) {
JAddStringToObject(req, "product", productID);
JAddStringToObject(req, "mode", "periodic");
//JAddStringToObject(req, "req", "card.restore"); // restore card state on hub to factory defaults
JAddNumberToObject(req, "interval", 250); // 250 s interval
notecard.sendRequest(req);
}
}
}
void loop() {
// sample for 5 s and compute running average
float sumP = 0.0f;
int cnt = 0;
unsigned long start = millis();
while (millis() - start < 5000) {
float v = analogRead(A1) * (3.3f / 4095.0f);
float p = (v - 0.5f) / 0.02f;
sumP += p;
cnt++;
delay(100); // ~50 samples over 5 s
}
float avgPressure = sumP / cnt;
// get a timestamp from the Notecard
time_t nowEpoch = 0;
{
J *req = notecard.newRequest("card.time");
J *rsp = notecard.requestAndResponse(req);
if (rsp && JHasObjectItem(rsp, "time")) {
J *val = JGetObjectItem(rsp, "time");
if (JIsNumber(val)) {
nowEpoch = (time_t)JNumberValue(val);
}
}
notecard.deleteResponse(rsp);
}
// save into our buffers
pressureBuffer[bufferIndex] = avgPressure;
timestampBuffer[bufferIndex] = nowEpoch;
bufferIndex++;
if(usbSerial) {
usbSerial.print(F("Sample #"));
usbSerial.print(bufferIndex);
usbSerial.print(F(": "));
usbSerial.print(avgPressure, 3);
usbSerial.print(F(" PSI @ "));
usbSerial.println(nowEpoch);
}
// if buffer full, send all 10 at once
if (bufferIndex >= BATCH_SIZE) {
J *req = notecard.newRequest("note.add");
if (req != NULL) {
JAddStringToObject(req, "file", "sensors.qo");
JAddBoolToObject(req, "sync", true);
J *body = JAddObjectToObject(req, "body");
if (body) {
J *arr = JAddArrayToObject(body, "samples");
for (int i = 0; i < BATCH_SIZE; i++) {
// format timestamp as MM:DD:YYYY HH:MM:SS
struct tm tminfo;
gmtime_r(×tampBuffer[i], &tminfo);
char tsbuf[20];
strftime(tsbuf, sizeof(tsbuf), "%m:%d:%Y %H:%M:%S", &tminfo);
// build one JSON object per sample
J *pt = JCreateObject();
JAddStringToObject(pt, "timestamp", tsbuf);
JAddNumberToObject(pt, "pressure", pressureBuffer[i]);
JAddItemToArray(arr, pt);
}
}
notecard.sendRequest(req);
usbSerial.println(F("→ Sent batch of 10 samples"));
}
bufferIndex = 0;
}
// wait 20 s before next 5 s sampling window
LowPower.deepSleep(20000);
}
I will look into the base64 documentation though to see if that can help me unless you can see something inherently wrong in my original code.
Nothing jumps out to me as obviously wrong with what you’ve got there, unfortunately. I do think the card.attn
approach will be a little cleaner, and I think you can migrate your code above over without too much hassle.
fyi note-c (and note-arduino) have some built-in helper functions for working with base64 strings that could be useful. Here’s an example: Example of using note-c helper methods to generate a base64-encoded string. This example generates a JSON object to use as the payload argument of the card.location.track request. · GitHub
TJ
Thank you for the info with base64 strings. I got the idle current down to 7-8 mA with my current batching code but this results in an average current of ~30 mA. I am powering the notecarrier using a 5V 3A battery pack and was measuring across a 10 ohm resistor with a digilent analog discovery board. I will try again once the Mojo I ordered comes in since I wasn’t able to get any sends to Notehub the earlier way. I did initially forget to add LowPower.begin() in void setup() but even after adding it I didn’t get any change. My understanding is deepSleep does not wipe memory but setting card.attn and using the EN pin will?
Yeah I can confirm on that last part. If you’re wiring ATTN
to EN
and using card.attn
, you’re effectively pulling the power to the host—so everything will be gone. The payload
argument lets you store data on Notecard before you pull the plug, and to retrieve that data when the host powers back up.
TJ
2 Likes