Cygnet - Wake-up after shutdown

Hello,

I am using my cygnet with notecarrief F v1.3 and notecard cell+wifi. I am trying to put it in shutdown mode so that every time it wakes up, setup() runs again. If I only use Lowpower.shutdown(10) and its connected to USB, it wakes up properly. When I connect it to the battery, it never wakes up.

Any ideas?

1 Like

Hi @Marios122

Could you please share your code? It will give us a better idea of what might be happening.

Thanks,
Alex

Hi Alex,

Here it is.

I also have some questions:

  1. I am treating my system to have the mcu as the master controller not the notecard. Is this the right way or shall the notecard be treated as the master?
  2. the switch on the notecarrier F v1.3 FEATHER_EN, should be switched to the ON or the F_ATTN for low power applications?
  3. In general, the system behaves differently when on battery and when on usb. For example, the below firmware when on USB, any changes I make on the env vars on notehub, they are properly implemented during run time. Once it goes on battery, I can see from notehub that env vars are fetched but are never implemented.

#include <Notecard.h>
#include <STM32LowPower.h>
#include <ModbusMaster.h>
#include <Arduino.h>

// =================== CONFIGURATION FLAGS ======================
#define MAX_ENV_FETCH_RETRIES 3
#define SENSOR_ENABLE_PIN 5
#define RS485_BAUD 9600
#define SENSOR_MODBUS_ID 1
#define PRODUCT_UID “xxx”
#define FIRMWARE_VERSION “0.0.1”
#define DATA_FILE “data.qo”

// =================== GLOBAL VARIABLES ==========================
Notecard notecard;
ModbusMaster node;
unsigned long loopIntervalMs = 300000;
uint32_t sensorWarmupMs = 5000;
uint32_t readDelayMs = 5000;
uint8_t totalReadings = 3;
uint8_t syncFailCount = 0;
uint32_t loopStart = 0;
String deviceID = “xxx”;
bool DEBUG_MODE = false;
String wifiSSID = “xxx”;
String wifiPassword = “xxx”;

// =================== ENV VAR FETCH FUNCTIONS ====================
int getEnvInt(J* body, const char* key, int defaultVal) {
if (body && JGetObjectItem(body, key)) {
return atoi(JGetString(body, key));
}
return defaultVal;
}

const char* getEnvStr(J* body, const char* key, const char* defaultVal) {
if (body && JGetObjectItem(body, key)) {
return JGetString(body, key);
}
return defaultVal;
}

void fetchEnvVarsWithRetries() {
bool success = false;
for (int attempt = 0; attempt < MAX_ENV_FETCH_RETRIES; attempt++) {
if (DEBUG_MODE) {
Serial.print(“:counterclockwise_arrows_button: Fetching env vars (Attempt “);
Serial.print(attempt + 1);
Serial.println(”)…”);
}

J* req = notecard.newRequest("env.get");
J* env = notecard.requestAndResponse(req);
if (env) {
  J* body = JGetObjectItem(env, "body");
  loopIntervalMs = getEnvInt(body, "interval_sec", loopIntervalMs / 1000) * 1000UL;
  sensorWarmupMs = getEnvInt(body, "sensor_warmup_ms", sensorWarmupMs);
  readDelayMs = getEnvInt(body, "read_delay_ms", readDelayMs);
  totalReadings = getEnvInt(body, "num_readings", totalReadings);
  wifiSSID = getEnvStr(body, "wifi_ssid", wifiSSID.c_str());
  wifiPassword = getEnvStr(body, "wifi_pass", wifiPassword.c_str());
  JDelete(env);
  success = true;
  break;
}
delay(2000);

}

if (DEBUG_MODE) {
if (success) {
Serial.println(“:white_check_mark: Env vars fetched successfully:”);
Serial.print(" interval_sec: “); Serial.println(loopIntervalMs / 1000);
Serial.print(” sensorWarmupMs: “); Serial.println(sensorWarmupMs);
Serial.print(” readDelayMs: “); Serial.println(readDelayMs);
Serial.print(” totalReadings: “); Serial.println(totalReadings);
Serial.print(” WiFi SSID: "); Serial.println(wifiSSID);
} else {
Serial.println(“:warning: Failed to fetch env vars after max retries. Using defaults.”);
}
}
}

// =================== TIME FORMAT HELPER ====================
String formatTimestamp(uint32_t ts) {
time_t rawTime = (time_t) ts;
struct tm* timeinfo = gmtime(&rawTime);
char buf[25];
sprintf(buf, “%02d/%02d/%02d %02d:%02d:%02d GMT”,
timeinfo->tm_mday,
timeinfo->tm_mon + 1,
timeinfo->tm_year % 100,
timeinfo->tm_hour,
timeinfo->tm_min,
timeinfo->tm_sec);
return String(buf);
}

uint32_t getCurrentTimeSec() {
J* timeReq = notecard.requestAndResponse(notecard.newRequest(“card.time”));
if (timeReq && JGetObjectItem(timeReq, “time”)) {
uint32_t ts = (uint32_t) JGetNumber(timeReq, “time”);
JDelete(timeReq);
if (DEBUG_MODE) {
Serial.print(“:stopwatch: getCurrentTimeSec(): “);
Serial.print(ts);
Serial.print(” (”);
Serial.print(formatTimestamp(ts));
Serial.println(“)”);
}
return ts;
}
JDelete(timeReq);
return 0;
}

// =================== PERIPHERALS ====================
void initializePeripherals() {
pinMode(SENSOR_ENABLE_PIN, OUTPUT);
digitalWrite(SENSOR_ENABLE_PIN, LOW);
Serial.begin(115200);
delay(10000);
if (Serial) {
DEBUG_MODE = true;
Serial.println(“:electric_plug: USB connected: Debug mode ON”);
} else {
DEBUG_MODE = false;
}

if (DEBUG_MODE) Serial.println(“:brain: Initializing Notecard and peripherals…”);

notecard.begin();
if (DEBUG_MODE) notecard.setDebugOutputStream(Serial);

LowPower.begin();
if (DEBUG_MODE) Serial.println(“:white_check_mark: Low power mode initialized”);

Serial1.begin(RS485_BAUD);
node.begin(SENSOR_MODBUS_ID, Serial1);
if (DEBUG_MODE) Serial.println(“:white_check_mark: RS485 and Modbus initialized”);
}

// =================== NOTECARD CONFIG ====================
void configureNotecard() {
if (DEBUG_MODE) Serial.println(“:antenna_bars: Configuring Notecard Wi-Fi and connection…”);
J *wifi = notecard.newRequest(“card.wifi”);
if (wifi) {
JAddStringToObject(wifi, “ssid”, wifiSSID.c_str());
JAddStringToObject(wifi, “password”, wifiPassword.c_str());
notecard.sendRequest(wifi);
}

J *wireless = notecard.newRequest(“card.wireless”);
if (wireless) {
JAddStringToObject(wireless, “mode”, “wifi”);
JAddBoolToObject(wireless, “wifi”, true);
JAddBoolToObject(wireless, “cell”, true);
notecard.sendRequest(wireless);
}

J *hub = notecard.newRequest(“hub.set”);
if (hub) {
JAddStringToObject(hub, “product”, PRODUCT_UID);
JAddStringToObject(hub, “mode”, “minimum”);
notecard.sendRequest(hub);
}
}

void syncWithNotehub() {
if (DEBUG_MODE) Serial.println(“:counterclockwise_arrows_button: Syncing with Notehub…”);
notecard.sendRequest(notecard.newRequest(“hub.sync”));
bool completed = false;
for (int i = 0; i < 40; i++) {
delay(500);
J* statusReq = notecard.requestAndResponse(notecard.newRequest(“hub.status”));
if (statusReq) {
if (JGetBool(statusReq, “completed”)) {
completed = true;
JDelete(statusReq);
break;
}
JDelete(statusReq);
}
}
if (DEBUG_MODE) Serial.println(completed ? “:white_check_mark: Notehub sync completed” : “:warning: Notehub sync timeout”);
}

void putNotecardToSleep() {
if (DEBUG_MODE) Serial.println(“:electric_plug: Putting Notecard to sleep…”);
J* sleepReq = notecard.newRequest(“card.sleep”);
if (sleepReq) {
JAddStringToObject(sleepReq, “mode”, “off”);
J* resp = notecard.requestAndResponse(sleepReq);
if (resp) JDelete(resp);
}
if (DEBUG_MODE) Serial.println(“:white_check_mark: Notecard sleep requested”);
}

// =================== SENSOR LOGIC ====================
bool readJXBSData(float &temp, float &humidity, float &ec, float &ph, float &n, float &p, float &k) {
bool success = true;
if (node.readHoldingRegisters(0x0012, 2) == node.ku8MBSuccess) {
humidity = node.getResponseBuffer(0) / 10.0;
temp = node.getResponseBuffer(1) / 10.0;
} else success = false;

if (node.readHoldingRegisters(0x0006, 1) == node.ku8MBSuccess) {
ph = node.getResponseBuffer(0) / 100.0;
} else success = false;

if (node.readHoldingRegisters(0x0015, 1) == node.ku8MBSuccess) {
ec = node.getResponseBuffer(0);
} else success = false;

if (node.readHoldingRegisters(0x001E, 3) == node.ku8MBSuccess) {
n = node.getResponseBuffer(0);
p = node.getResponseBuffer(1);
k = node.getResponseBuffer(2);
} else success = false;

if (success && DEBUG_MODE) {
Serial.println(“:bar_chart: Sensor Data:”);
Serial.print(":thermometer: Temp: “); Serial.print(temp); Serial.print(” °C, ");
Serial.print(":droplet: Humidity: “); Serial.print(humidity); Serial.print(” %, ");
Serial.print(":high_voltage: EC: “); Serial.print(ec); Serial.print(” µS/cm, ");
Serial.print(":test_tube: pH: “); Serial.print(ph); Serial.print(”, ");
Serial.print(":dna: N: “); Serial.print(n); Serial.print(” mg/kg, ");
Serial.print("P: “); Serial.print(p); Serial.print(” mg/kg, ");
Serial.print(“K: “); Serial.print(k); Serial.println(” mg/kg”);
}

return success;
}

void powerOnSensor() {
if (DEBUG_MODE) Serial.println(“:high_voltage: Powering ON sensor…”);
digitalWrite(SENSOR_ENABLE_PIN, HIGH);
delay(sensorWarmupMs);
if (DEBUG_MODE) Serial.println(“:white_check_mark: Sensor powered ON”);
}

void powerOffSensor() {
if (DEBUG_MODE) Serial.println(“:stop_sign: Powering OFF sensor…”);
digitalWrite(SENSOR_ENABLE_PIN, LOW);
if (DEBUG_MODE) Serial.println(“:white_check_mark: Sensor powered OFF”);
}

float getBatteryVoltage() {
float battery = -1.0;
J* volt = notecard.requestAndResponse(notecard.newRequest(“card.voltage”));
if (volt && JGetObjectItem(volt, “value”))
battery = JGetNumber(volt, “value”);
JDelete(volt);
if (DEBUG_MODE) {
Serial.print(":battery: Battery voltage: ");
Serial.println(battery);
}
return battery;
}

void getLocation(double &lat, double &lon, String &source) {
lat = lon = 0.0;
source = “unknown”;

J* start = notecard.newRequest(“card.location.track”);
if (start) {
JAddBoolToObject(start, “start”, true);
notecard.sendRequest(start);
delay(5000); // allow GPS time to get a fix
}

J* loc = notecard.requestAndResponse(notecard.newRequest(“card.location”));
if (loc) {
if (JGetObjectItem(loc, “lat”)) lat = JGetNumber(loc, “lat”);
if (JGetObjectItem(loc, “lon”)) lon = JGetNumber(loc, “lon”);

const char* mode = JGetString(loc, "mode");     // "gps", "off", etc.
const char* status = JGetString(loc, "status"); // "locked", "gps-inactive", etc.

if (mode && strcmp(mode, "gps") == 0 && status && strcmp(status, "locked") == 0) {
  source = "gps";
} else if (status && strstr(status, "gps-inactive")) {
  source = "cached";
}

JDelete(loc);

}

J* stop = notecard.newRequest(“card.location.track”);
if (stop) {
JAddBoolToObject(stop, “stop”, true);
notecard.sendRequest(stop);
}

if (DEBUG_MODE) {
Serial.print(":round_pushpin: Location: “);
Serial.print(lat);
Serial.print(”, “);
Serial.print(lon);
Serial.print(” | Source: ");
Serial.println(source);
}
}

void collectAndSendSensorData() {
powerOnSensor();
J* note = notecard.newRequest(“note.add”);
if (note) {
JAddStringToObject(note, “file”, DATA_FILE);
JAddBoolToObject(note, “sync”, true);
J* body = JCreateObject();
JAddStringToObject(body, “device”, deviceID.c_str());
JAddStringToObject(body, “fw_ver”, FIRMWARE_VERSION);
float battery = getBatteryVoltage();
double lat, lon;
String locSource;
getLocation(lat, lon, locSource);
JAddNumberToObject(body, “battery (V)”, battery);
JAddNumberToObject(body, “lat”, lat);
JAddNumberToObject(body, “lon”, lon);
JAddStringToObject(body, “location_source”, locSource.c_str());

J* samples = JCreateArray();

for (uint8_t i = 0; i < totalReadings; i++) {
  float temp, hum, ec, ph, n, p, k;
  if (readJXBSData(temp, hum, ec, ph, n, p, k)) {
    uint32_t ts = getCurrentTimeSec();
    J* entry = JCreateObject();
    JAddNumberToObject(entry, "ts", ts);
    JAddStringToObject(entry, "ts_str", formatTimestamp(ts).c_str());
    JAddNumberToObject(entry, "temp (°C)", temp);
    JAddNumberToObject(entry, "hum (%)", hum);
    JAddNumberToObject(entry, "ec (ÎĽS/cm)", ec);
    JAddNumberToObject(entry, "ph", ph);
    JAddNumberToObject(entry, "n (mg/kg)", n);
    JAddNumberToObject(entry, "p (mg/kg)", p);
    JAddNumberToObject(entry, "k (mg/kg)", k);
    JAddItemToArray(samples, entry);

    if (DEBUG_MODE) {
      Serial.print("⏱️ Sample Time: ");
      Serial.println(formatTimestamp(ts));
    }
  }
  delay(readDelayMs);
}

if (JGetArraySize(samples) > 0) {
  JAddItemToObject(body, "samples", samples);
  JAddItemToObject(note, "body", body);
  notecard.sendRequest(note);
  if (DEBUG_MODE) Serial.println("âś… Sensor data sent");
} else {
  JDelete(samples);
  JDelete(body);
  JDelete(note);
  if (DEBUG_MODE) Serial.println("⚠️ No valid sensor data, nothing sent");
}

}
powerOffSensor();
}

// =================== SETUP & LOOP ====================
void setup() {
loopStart = millis();
initializePeripherals();
configureNotecard();
syncWithNotehub();
fetchEnvVarsWithRetries();
collectAndSendSensorData();
putNotecardToSleep();

uint32_t duration = millis() - loopStart;
uint32_t sleepMs = (loopIntervalMs > duration) ? loopIntervalMs - duration : 1;

if (DEBUG_MODE) {
Serial.print(“:bed: Entering deep sleep for “);
Serial.print(sleepMs / 1000);
Serial.println(” seconds”);
Serial.flush();
delay(100);
}
LowPower.shutdown(sleepMs);
}

void loop() {
}

Hi @Marios122,

We are still looking into the issue you mentioned re: Cygnet. In the meantime, let’s see if we can answer some of your other questions and point out a couple of other potential issues:

I am treating my system to have the mcu as the master controller not the notecard. Is this the right way or shall the notecard be treated as the master?

Yes that is the correct architecture to use in terms of the MCU controlling the application as a whole. Notecard can be used to power down the host though, which I think you are aware of.

the switch on the notecarrier F v1.3 FEATHER_EN, should be switched to the ON or the F_ATTN for low power applications?

When switched to ON the Feather is continuously powered, so you would want it switched to F_ATTN and be sure to consult our low power design guide for specifics.

In general, the system behaves differently when on battery and when on usb. For example, the below firmware when on USB, any changes I make on the env vars on notehub, they are properly implemented during run time. Once it goes on battery, I can see from notehub that env vars are fetched but are never implemented.

There is nothing in your syncWithNotehub method that ensures a connection was successful before you then move on to reading env vars. For example, this is basically saying give it 20 secs, which may not be enough time:

for (int i = 0; i < 40; i++) {
delay(500);

Lastly, be wary of LLM-generated code as there are usages in here of card.sleep (that API is only valid when used with Notecard WiFi v2) and card.wireless (I think you want card.transport instead). You’re never going to get a valid location reading either via GPS as your getLocation method needs to be refactored. I suggest looking at this guide to better understand how GPS works on Notecard.