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.

1 Like

Hi @Marios122

Taking a look at your initial question about low power modes, it looks to me like you may need to close your USB Serial connection before trying to enter into a low power state. For example:

void setup() {
  Serial.begin(9600);
}

void loop() {
  Serial.println("Hello, world!");
  Serial.end(); // Close the serial interface
  LowPower.shutdown(10); // Shutdown for 10 seconds
}

This is due to the USB using a different clock domain from the RTC used to manage low power modes. You must explicitly shut it down before entering low power.

Thanks,
Alex

1 Like

Hi both,

Thank you very much for your feedback. I have managed to get everything sorted and working. I now understand how the low-power mode works.

The only thing I am still struggling is how to get GPS location on demand. Do you have a sample code to help me out please?

Thanks

Marios

Hi @Marios122 ,

Glad to hear you resolved the low-power issues.

For your GPS, it’s worth reading through the docs relating to the GPS. There are some different use cases defined for calling the GPS and using card.location.

Be weary of trying to use the GPS and the Cellular radio in continuous mode as this is often a gotcha. You must pause one of the radios to use the other (see the warning in the docs link). If you need to use both in continuousmode, we would recommend using an external GPS.

Hi abucknall,

My radio is set to minimum so it will not operate unless i request it. I went through the docs and I still cant see how I can get a gps fix on demand.

Also, I am trying to get a confirmation from notecard that my sync with notehub was completed. I set my timeout to be 5 minutes and still its not enough. Do you have any suggestions? Here is my code:
void syncWithNotehub() {

if (DEBUG_MODE) Serial.println(“Triggering hub.sync…”);

notecard.sendRequest(notecard.newRequest(“hub.sync”));

bool syncComplete = false;

unsigned long startTime = millis();

while (millis() - startTime < 300000UL) { // 5-minute timeout

J\* statusReq = notecard.requestAndResponse(notecard.newRequest("hub.sync.status"));

if (statusReq != nullptr) {

  const char\* s = JGetString(statusReq, "status");

  if (s && strstr(s, "completed")) {  // matches "completed {sync-end}" and variants

    syncComplete = true;

    if (DEBUG_MODE) Serial.println("Sync completed successfully.");

    JDelete(statusReq);

    break;

  }

  JDelete(statusReq);

}



if (DEBUG_MODE) Serial.println("Waiting for sync to complete...");

delay(2000); // Wait 2 seconds before retry

}

if (!syncComplete) {

if (DEBUG_MODE) Serial.println("Sync did not complete within 300 seconds.");



// Report error to Notehub

J\* err = notecard.newRequest("note.add");

if (err != nullptr) {

  JAddStringToObject(err, "file", ERRORS_FILE);

  JAddBoolToObject(err, "sync", true);

  J\* body = JCreateObject();

  JAddStringToObject(body, "device", deviceID.c_str());

  JAddStringToObject(body, "error", "Notehub sync did not complete within timeout");

  JAddItemToObject(err, "body", body);

  notecard.sendRequest(err);

}

}

}

FYI, I sorted out the confirmation on the hub sync. I think your API Reference is outdated about this.

GPS fix on demand is still a mystery to me :slight_smile:

You will need to first put the GPS into either periodic or continuous mode using:

{ "req": "card.location.mode", "mode": "continuous"}

I would recommend continuous mode while testing so that you can see the location change. When you run the{"req": "card.location"}you should see the status of the GPS.

Could you share what issue you are seeing? Can you also confirm the version of firmware on your Notecard (using{"req": "card.version"})?