Skip to content

Instantly share code, notes, and snippets.

@rdlauer
Last active May 12, 2025 17:41
Show Gist options
  • Save rdlauer/a1bbe3eda1bb4800ab7b5de4921685ca to your computer and use it in GitHub Desktop.
Save rdlauer/a1bbe3eda1bb4800ab7b5de4921685ca to your computer and use it in GitHub Desktop.
Testing Notecard Power Consumption with Mojo
#include <Arduino.h>
#include <Notecard.h>
#include <stdio.h>
#include <string.h>
#define usbSerial Serial
#define txRxPinsSerial Serial1
#define PRODUCT_UID "your-product-uid"
#define DEBUG 1
#define NOTECARD_SKU "NOTE-XXX" // enter the appropriate Notecard SKU
#define USE_WIFI 1
#define WIFI_SSID "your-ssid"
#define WIFI_PASSWORD "your-password"
#define RAT "-" // "m" for LTE-M, "nb" for NB-IoT, "gprs" for 2G (EDGE)
#define CTX D5 // used for NOTE-ESP
#define RTX D6 // used for NOTE-ESP
Notecard notecard;
//---------------------------------------------------------------------
// Global variables for state machine management
//---------------------------------------------------------------------
enum Test
{
TEST0, // Cold network connection test (10 iterations)
TEST1, // Warm network connection test (10 iterations)
TEST2, // Idle mode (1 hr delay) test (5 iterations)
TEST3, // Single note sync test (10 iterations)
TEST4, // "Aggressive" continuous mode for 12 hours with 5-min syncs
TEST5, // Minimum mode for 12 hours with 30-min syncs
TEST6, // DFU firmware update test
DONE // Final state
};
Test currentTest = TEST0;
int phase = 0; // Sub-state within a given test
int iteration = 0; // For tests with multiple iterations
unsigned long stateStartTime = 0; // For timing waits within phases
unsigned long testStartTime = 0; // For overall test durations (e.g. 12 hours)
unsigned long intervalTime = 0; // For periodic intervals (e.g. 5 or 30 minutes)
// For hub.sync non‑blocking polling
bool syncInProgress = false;
unsigned long syncStartTime = 0;
unsigned long lastSyncPollTime = 0;
//---------------------------------------------------------------------
// Forward declarations of helper functions
//---------------------------------------------------------------------
void sendRestartNotecard();
void sendResetMojo();
void sendHubSetHelper(const char *mode);
void sendHubSync();
bool pollHubSync();
void sendMojoReading(const char *notefileName);
void sendCreateGenericNote();
//---------------------------------------------------------------------
// setup() – perform initialization and Notecard configuration
//---------------------------------------------------------------------
void setup()
{
if (DEBUG)
{
delay(2000);
usbSerial.begin(115200);
while (!usbSerial)
;
}
if (strcmp(NOTECARD_SKU, "NOTE-ESP") == 0)
{
notecard.setTransactionPins(CTX, RTX);
notecard.begin(txRxPinsSerial, 9600);
}
else
{
notecard.begin();
}
if (DEBUG)
{
notecard.setDebugOutputStream(usbSerial);
}
// Factory reset Notecard with blocking delay for restart
{
J *cmd = notecard.newCommand("card.restore");
if (cmd)
{
JAddBoolToObject(cmd, "delete", true);
notecard.sendRequest(cmd);
if (DEBUG)
{
usbSerial.println("Factory resetting notecard with card.restore request");
}
}
delay(60000); // Blocking delay: wait 60 sec for the notecard to restart
}
// Reset Wi‑Fi credentials regardless of Notecard in use
{
J *req = notecard.newRequest("card.wifi");
if (req)
{
JAddStringToObject(req, "ssid", "-");
JAddStringToObject(req, "password", "-");
notecard.sendRequest(req);
if (DEBUG)
{
usbSerial.println("Wi-Fi credentials reset");
}
}
}
// Set new Wi‑Fi credentials and force wifi mode if using a Wi-Fi capable Notecard
if (USE_WIFI)
{
{
J *req = notecard.newRequest("card.wifi");
if (req)
{
JAddStringToObject(req, "ssid", WIFI_SSID);
JAddStringToObject(req, "password", WIFI_PASSWORD);
notecard.sendRequest(req);
if (DEBUG)
{
usbSerial.println("Wi-Fi credentials set");
}
}
}
{
J *req = notecard.newRequest("card.transport");
if (req)
{
JAddStringToObject(req, "method", "wifi");
notecard.sendRequest(req);
if (DEBUG)
{
usbSerial.println("card.transport set to method:wifi");
}
}
}
}
else
{
J *req = notecard.newRequest("card.transport");
if (req)
{
JAddStringToObject(req, "method", "cell");
notecard.sendRequest(req);
if (DEBUG)
{
usbSerial.println("card.transport set to method:cell");
}
}
}
// Temporarily block DFU actions
{
J *req = notecard.newRequest("dfu.status");
if (req)
{
JAddBoolToObject(req, "off", true);
notecard.sendRequest(req);
if (DEBUG)
{
usbSerial.println("dfu.status set to off:true");
}
}
}
// Set hub.set mode to "off" to start
sendHubSetHelper("off");
// Disable accelerometer
{
J *req = notecard.newRequest("card.motion.mode");
if (req)
{
JAddBoolToObject(req, "stop", true);
notecard.sendRequest(req);
if (DEBUG)
{
usbSerial.println("Accelerometer disabled");
}
}
}
// Disable GPS
{
J *req = notecard.newRequest("card.location.mode");
if (req)
{
JAddStringToObject(req, "mode", "off");
notecard.sendRequest(req);
if (DEBUG)
{
usbSerial.println("GPS disabled");
}
}
}
// Disable attn pin processing
{
J *req = notecard.newRequest("card.attn");
if (req)
{
JAddBoolToObject(req, "off", true);
notecard.sendRequest(req);
if (DEBUG)
{
usbSerial.println("Attn pin processing disabled");
}
}
}
// Disable aux mode
{
J *req = notecard.newRequest("card.aux");
if (req)
{
JAddStringToObject(req, "mode", "off");
notecard.sendRequest(req);
if (DEBUG)
{
usbSerial.println("Aux mode disabled");
}
}
}
// set the network technology to be used (default is "-" to reset)
{
J *req = notecard.newRequest("card.wireless");
if (req)
{
JAddStringToObject(req, "mode", RAT);
notecard.sendRequest(req);
if (DEBUG)
{
usbSerial.println("network technology set");
}
}
}
// allow Notecard WiFi v2 to fall back to a low current draw when idle
if (strcmp(NOTECARD_SKU, "NOTE-ESP") == 0)
{
J *req = notecard.newRequest("card.sleep");
if (req != NULL)
{
JAddBoolToObject(req, "on", true);
notecard.sendRequest(req);
if (DEBUG)
{
usbSerial.println("card.sleep set to on:true");
}
}
}
delay(2000); // a short delay after initialization
}
//---------------------------------------------------------------------
// loop() – main non-blocking state machine for sequencing the tests
//---------------------------------------------------------------------
void loop()
{
switch (currentTest)
{
// ---------------------------
// TEST 0: Cold Network Test (10 iterations)
// ---------------------------
case TEST0:
{
switch (phase)
{
case 0: // For iterations > 0, restart notecard; skip for iteration 0, but document it as a true cold boot test.
if (iteration > 0)
{
sendRestartNotecard();
stateStartTime = millis();
if (DEBUG)
{
usbSerial.println("TEST0: Restart command sent; waiting 60 secs");
}
phase = 1;
}
else
{
phase = 2;
}
break;
case 1: // Wait for restart.
if (millis() - stateStartTime >= 60000UL)
{
phase = 2;
}
break;
case 2: // Reset Mojo and set hub.set to "periodic" mode.
sendResetMojo();
sendHubSetHelper("periodic");
phase = 3;
break;
case 3: // Start hub.sync.
sendHubSync();
phase = 4;
break;
case 4: // Wait for sync completion.
if (pollHubSync())
{
phase = 5;
}
break;
case 5: // Send mojo reading.
sendMojoReading("power-test0.qo");
phase = 6;
break;
case 6: // Wait for sync completion.
if (pollHubSync())
{
phase = 7;
}
break;
case 7: // Set hub.set to "off".
sendHubSetHelper("off");
stateStartTime = millis();
phase = 8;
break;
case 8: // Wait 20 seconds before next iteration.
if (millis() - stateStartTime >= 20000UL)
{
iteration++;
if (iteration >= 10)
{
currentTest = TEST1;
phase = 0;
iteration = 0;
if (DEBUG)
{
usbSerial.println("TEST0 complete; moving to TEST1");
}
}
else
{
phase = 0;
if (DEBUG)
{
usbSerial.println("One iteration of TEST0 complete; moving to next iteration");
}
}
}
break;
}
break;
}
// ---------------------------
// TEST 1: Warm Network Test (10 iterations)
// ---------------------------
case TEST1:
{
switch (phase)
{
case 0: // Reset Mojo.
if (DEBUG)
{
usbSerial.println("TEST1: Starting");
}
sendResetMojo();
phase = 1;
break;
case 1: // Set hub.set to "periodic".
sendHubSetHelper("periodic");
phase = 2;
break;
case 2: // Start hub.sync.
sendHubSync();
phase = 3;
break;
case 3: // Wait for sync.
if (pollHubSync())
{
phase = 4;
}
break;
case 4: // Send mojo reading.
sendMojoReading("power-test1.qo");
phase = 5;
break;
case 5: // Wait for sync.
if (pollHubSync())
{
phase = 6;
}
break;
case 6: // Set hub.set to "off".
sendHubSetHelper("off");
stateStartTime = millis();
phase = 7;
break;
case 7: // Wait 20 seconds before next iteration.
if (millis() - stateStartTime >= 20000UL)
{
iteration++;
if (iteration >= 10)
{
currentTest = TEST2;
phase = 0;
iteration = 0;
if (DEBUG)
{
usbSerial.println("TEST1 complete; moving to TEST2");
}
}
else
{
phase = 0;
if (DEBUG)
{
usbSerial.println("One iteration of TEST1 complete; moving to next iteration");
}
}
}
break;
}
break;
}
// ---------------------------
// TEST 2: Idle Mode (1 Hour Delay, 5 iterations)
// ---------------------------
case TEST2:
{
switch (phase)
{
case 0: // Set hub.set to "minimum" and sync to clear notefiles just in case.
sendHubSetHelper("minimum");
sendHubSync();
phase = 1;
break;
case 1: // Wait for sync to complete.
if (pollHubSync())
{
stateStartTime = millis();
phase = 2;
}
break;
case 2: // Wait for 30 secs to ensure the connection is terminated.
if (millis() - stateStartTime >= 30000UL)
{
phase = 3;
}
break;
case 3: // Reset Mojo and start 1-hour wait.
if (DEBUG)
{
usbSerial.println("TEST2: Starting 1-hour wait");
}
sendResetMojo();
stateStartTime = millis();
phase = 4;
break;
case 4: // Wait for 1 hour.
if (millis() - stateStartTime >= 3600000UL)
{
phase = 5;
}
break;
case 5: // Send mojo reading.
sendMojoReading("power-test2.qo");
phase = 6;
break;
case 6: // Wait for sync.
if (pollHubSync())
{
stateStartTime = millis();
phase = 7;
}
break;
case 7: // Wait 30 seconds before next iteration/test.
if (millis() - stateStartTime >= 30000UL)
{
iteration++;
if (iteration >= 5)
{
currentTest = TEST3;
phase = 0;
iteration = 0;
if (DEBUG)
{
usbSerial.println("TEST2 complete; moving to TEST3");
}
}
else
{
phase = 3;
if (DEBUG)
{
usbSerial.println("One iteration of TEST2 complete; moving to next iteration");
}
}
}
break;
}
break;
}
// ---------------------------
// TEST 3: Single Note Sync (10 iterations)
// ---------------------------
case TEST3:
{
switch (phase)
{
case 0: // Reset Mojo.
if (DEBUG)
{
usbSerial.println("TEST3: Starting");
}
sendResetMojo();
phase = 1;
break;
case 1: // Create generic note.
sendCreateGenericNote();
phase = 2;
break;
case 2: // Initiate sync.
sendHubSync();
phase = 3;
break;
case 3: // Wait for sync.
if (pollHubSync())
{
phase = 4;
}
break;
case 4: // Send mojo reading.
sendMojoReading("power-test3.qo");
phase = 5;
break;
case 5: // Wait for sync.
if (pollHubSync())
{
stateStartTime = millis();
phase = 6;
}
break;
case 6: // Wait 30 seconds before next iteration/test.
if (millis() - stateStartTime >= 30000UL)
{
iteration++;
if (iteration >= 10)
{
currentTest = TEST4;
phase = 0;
iteration = 0;
if (DEBUG)
{
usbSerial.println("TEST3 complete; moving to TEST4");
}
}
else
{
phase = 0;
if (DEBUG)
{
usbSerial.println("One iteration of TEST3 complete; moving to next iteration");
}
}
}
break;
}
break;
}
// ---------------------------
// TEST 4: "Aggressive" continuous Mode for 12 Hours with 5-min Syncs
// ---------------------------
case TEST4:
{
switch (phase)
{
case 0: // Set hub.set to "continuous" and sync.
if (DEBUG)
{
usbSerial.println("TEST4: Starting");
}
sendHubSetHelper("continuous");
sendHubSync();
phase = 1;
break;
case 1: // Wait for sync.
if (pollHubSync())
{
phase = 2;
}
break;
case 2: // Reset Mojo and initialize 12-hr continuous test.
sendResetMojo();
testStartTime = millis();
intervalTime = 5UL * 60UL * 1000UL; // 5 minutes
stateStartTime = millis();
phase = 3;
break;
case 3: // During the 12 hours, every 5 minutes create a note and sync.
if (millis() - stateStartTime >= intervalTime)
{
sendCreateGenericNote();
sendHubSync(); // don't have to wait for sync to complete here
stateStartTime = millis();
}
if (millis() - testStartTime >= 12UL * 60UL * 60UL * 1000UL)
{
phase = 4;
}
break;
case 4: // After 12 hours, send mojo reading.
sendMojoReading("power-test4.qo");
phase = 5;
break;
case 5: // Wait for sync.
if (pollHubSync())
{
stateStartTime = millis();
phase = 6;
}
break;
case 6: // Wait 30 seconds before moving to next test.
if (millis() - stateStartTime >= 30000UL)
{
currentTest = TEST5;
phase = 0;
if (DEBUG)
{
usbSerial.println("TEST4 complete; moving to TEST5");
}
}
break;
}
break;
}
// ---------------------------
// TEST 5: Minimum Mode for 12 Hours with 30-min Syncs
// ---------------------------
case TEST5:
{
switch (phase)
{
case 0: // Set hub.set to "minimum" and sync.
if (DEBUG)
{
usbSerial.println("TEST5: Starting");
}
sendHubSetHelper("minimum");
sendHubSync();
phase = 1;
break;
case 1: // Wait for sync.
if (pollHubSync())
{
phase = 2;
}
break;
case 2: // Reset Mojo and initialize 12-hr test.
sendResetMojo();
testStartTime = millis();
intervalTime = 30UL * 60UL * 1000UL; // 30 minutes
stateStartTime = millis();
phase = 3;
break;
case 3: // Every 30 minutes, create note and sync.
if (millis() - stateStartTime >= intervalTime)
{
sendCreateGenericNote();
sendHubSync(); // don't have to wait for sync
stateStartTime = millis();
}
if (millis() - testStartTime >= 12UL * 60UL * 60UL * 1000UL)
{
phase = 4;
}
break;
case 4: // After 12 hours, send mojo reading.
sendMojoReading("power-test5.qo");
phase = 5;
break;
case 5: // Wait for sync.
if (pollHubSync())
{
stateStartTime = millis();
phase = 6;
}
break;
case 6: // Wait 30 seconds then move to next test.
if (millis() - stateStartTime >= 30000UL)
{
currentTest = TEST6;
phase = 0;
if (DEBUG)
{
usbSerial.println("TEST5 complete; moving to TEST6");
}
}
break;
}
break;
}
// ---------------------------
// TEST 6: Firmware Update (DFU) in "continous" mode
// ---------------------------
case TEST6:
{
switch (phase)
{
case 0: // Set hub.set to "continuous" and sync.
if (DEBUG)
{
usbSerial.println("TEST6: Starting");
}
sendHubSetHelper("continuous");
sendHubSync();
phase = 1;
break;
case 1: // Wait for sync.
if (pollHubSync())
{
phase = 2;
}
break;
case 2: // Reset Mojo.
sendResetMojo();
phase = 3;
break;
case 3: // Re-enable DFU.
{
J *req = notecard.newRequest("dfu.status");
if (req)
{
JAddBoolToObject(req, "on", true);
notecard.sendRequest(req);
phase = 4;
if (DEBUG)
{
usbSerial.println("DFU re-enabled");
}
}
}
break;
case 4: // Initiate sync to start DFU.
sendHubSync();
// at this point we have to check _health.qo files for dfu
// completion status and read power usage values!
currentTest = DONE;
phase = 0;
break;
}
break;
}
// ---------------------------
// DONE: Remain here indefinitely.
// ---------------------------
case DONE:
{
switch (phase)
{
case 0: // The tests are complete!
if (DEBUG)
{
usbSerial.println("ALL TESTS COMPLETE!!!");
}
phase = 1;
break;
case 1: // Infinite loop...
break;
}
}
}
}
//---------------------------------------------------------------------
// Non-blocking helper functions implementation
//---------------------------------------------------------------------
// Sends the card.restart request
void sendRestartNotecard()
{
J *cmd = notecard.newCommand("card.restart");
if (cmd)
{
notecard.sendRequest(cmd);
if (DEBUG)
{
usbSerial.println("Restart command sent");
}
}
else
{
if (DEBUG)
{
usbSerial.println("Failed to create card.restart command");
}
}
}
// Sends the card.power reset command
void sendResetMojo()
{
J *req = notecard.newRequest("card.power");
if (req)
{
JAddBoolToObject(req, "reset", true);
notecard.sendRequest(req);
if (DEBUG)
{
usbSerial.println("ResetMojo command sent");
}
}
else
{
if (DEBUG)
{
usbSerial.println("Failed to create card.power request");
}
}
}
// Sends a hub.set command with the provided mode
void sendHubSetHelper(const char *mode)
{
J *req = notecard.newRequest("hub.set");
if (req)
{
if (strcmp(mode, "off") == 0)
{
JAddStringToObject(req, "product", "-");
}
else
{
JAddStringToObject(req, "product", PRODUCT_UID);
}
JAddStringToObject(req, "mode", mode);
JAddStringToObject(req, "sn", NOTECARD_SKU);
if (strcmp(mode, "continuous") == 0)
{
JAddBoolToObject(req, "sync", true);
}
else
{
JAddBoolToObject(req, "sync", false);
}
notecard.sendRequest(req);
if (DEBUG)
{
usbSerial.print("hub.set command sent with mode: ");
usbSerial.println(mode);
}
}
else
{
if (DEBUG)
{
usbSerial.println("Failed to create hub.set request");
}
}
}
// Starts a hub.sync
void sendHubSync()
{
J *req = notecard.newRequest("hub.sync");
if (req)
{
notecard.sendRequest(req);
syncInProgress = true;
syncStartTime = millis();
lastSyncPollTime = millis();
if (DEBUG)
{
usbSerial.println("hub.sync command sent");
}
}
else
{
if (DEBUG)
{
usbSerial.println("Failed to create hub.sync request");
}
}
}
// Polls hub.sync.status every 1 sec until complete (or until timeout of 60 seconds)
bool pollHubSync()
{
if (!syncInProgress)
return true;
if (millis() - lastSyncPollTime >= 1000UL)
{
lastSyncPollTime = millis();
J *req = notecard.newRequest("hub.sync.status");
if (req)
{
J *rsp = notecard.requestAndResponse(req);
if (rsp)
{
int completed = JGetNumber(rsp, "completed");
if (completed > 0)
{
syncInProgress = false;
if (DEBUG)
{
usbSerial.print("hub.sync complete with completed value: ");
usbSerial.println(completed);
}
}
notecard.deleteResponse(rsp);
}
else
{
if (DEBUG)
{
usbSerial.println("hub.sync.status response object failed");
}
}
}
else
{
if (DEBUG)
{
usbSerial.println("Failed to create hub.sync.status request");
}
}
}
if (syncInProgress && (millis() - syncStartTime >= 60000UL))
{
syncInProgress = false;
if (DEBUG)
{
usbSerial.println("hub.sync timeout");
}
}
return !syncInProgress;
}
// Reads the power reading and sends a note with the specified filename
void sendMojoReading(const char *notefileName)
{
float mah = 0.0;
J *req = notecard.newRequest("card.power");
if (req)
{
J *rsp = notecard.requestAndResponse(req);
if (rsp)
{
mah = JGetNumber(rsp, "milliamp_hours");
if (DEBUG)
{
usbSerial.print("Mojo reading (mah): ");
usbSerial.println(mah);
}
notecard.deleteResponse(rsp);
}
else
{
if (DEBUG)
{
usbSerial.println("Creation of card.power response object failed for reading Mojo");
}
}
}
else
{
if (DEBUG)
{
usbSerial.println("Failed to create card.power request to read Mojo");
}
}
if (mah > 0.0)
{
J *reqNote = notecard.newRequest("note.add");
if (reqNote)
{
JAddStringToObject(reqNote, "file", notefileName);
J *body = JAddObjectToObject(reqNote, "body");
if (body)
{
JAddNumberToObject(body, "mah", mah);
JAddStringToObject(body, "sku", NOTECARD_SKU);
}
notecard.sendRequest(reqNote);
if (DEBUG)
{
usbSerial.print("note.add command sent for file: ");
usbSerial.println(notefileName);
}
// Trigger a sync after adding a note.
sendHubSync();
}
else
{
if (DEBUG)
{
usbSerial.println("Failed to create note.add request for reading Mojo");
}
}
}
}
// Creates a generic note (data.qo) with fixed values.
void sendCreateGenericNote()
{
J *req = notecard.newRequest("note.add");
if (req)
{
JAddStringToObject(req, "file", "data.qo");
J *body = JAddObjectToObject(req, "body");
if (body)
{
JAddNumberToObject(body, "temp", 12.3);
JAddBoolToObject(body, "alert", true);
}
notecard.sendRequest(req);
if (DEBUG)
{
usbSerial.println("Generic note created (data.qo)");
}
}
else
{
if (DEBUG)
{
usbSerial.println("Failed to create note.add request for generic note");
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment