Swan MCU locking up?

I have been experimenting with multiple F style notecarriers with cellular card and a Swan mcu’s. My application is for backup remote access to a gate. I have set the notecard to continuous mode as periodic does not make sense in this application. In less than 24 hours from uploading firmware or rebooting the MCU, the device stops responding to .qi files. Notehub shows the notecard as online, but the mcu does not process the .qi file as it should. The only way I can get it respond is to press the reset button on the swan. After a reset, it will work, but then stop after a number of hours, which obviously can’t work for a remote install.

I tried to include code like

JAddNumberToObject(req, “inbound”, 15);
JAddNumberToObject(req, “outbound”, 60);

that I thought I understood it to force a connection, thinking that may be the issue, but I see nothing ever in notehub that tells me this has done anything and maybe I should expect to?

Perhaps I am doing something wrong with the code that is causing memory issues over time leading to a lock up? I don’t have the knowledge to know if I have.

Below is the mcu code I have. I would appreciate any advice on what I am doing wrong. Thanks.

Blockquote

type or paste code here
#include <Arduino.h>
#include <Notecard.h>

#define productUID “app:a1xxxxxxxxxxxxxxxxxxxxx”
#define RELAY_PIN D10

#define COMMAND_CHECK_INTERVAL 10000 // Check for commands every 10 seconds
#define SYNC_INTERVAL 600000 // Sync every 10 minutes (600,000 ms)
// Timeout after HUB_SET_TIMEOUT seconds of retrying hub.set.
#ifndef HUB_SET_TIMEOUT
#define HUB_SET_TIMEOUT 5 //is this minutes?
#endif

Notecard notecard;
unsigned long relayActivationTime = 5000; // Default to 5 seconds
unsigned long relayStartTime = 0;
unsigned long relayDeactivationTime = 0;
bool relayActive = false;

unsigned long lastSyncTime = 0;
unsigned long lastCommandCheckTime = 0;

bool holdActive = false;

void logMessage(const char* message) {
SerialUSB.print(millis());
SerialUSB.print(" | ");
SerialUSB.println(message);
}

//using the environment variable for relay activation time
void updateRelayActivationTime() {
J *req = notecard.newRequest(“env.get”);
JAddStringToObject(req, “name”, “relay_activation_time”);
J *rsp = notecard.requestAndResponse(req);

logMessage(“Updating relay activation time…”);

if (rsp != NULL) {
logMessage(“Response received from Notecard”);
if (!notecard.responseError(rsp)) {
const char *text = JGetString(rsp, “text”);
if (text != NULL) {
char buffer[100];
snprintf(buffer, sizeof(buffer), “Environment variable value (raw): %s”, text);
logMessage(buffer);

    double time = atof(text);  // Convert string to float
    snprintf(buffer, sizeof(buffer), "Converted time value: %.2f", time);
    logMessage(buffer);
    
    if (time > 0) {
      unsigned long oldTime = relayActivationTime;
      relayActivationTime = (unsigned long)(time * 1000); // Convert seconds to milliseconds
      snprintf(buffer, sizeof(buffer), "Updated relay activation time from %lu ms to %lu ms", oldTime, relayActivationTime);
      logMessage(buffer);
    } else {
      logMessage("Invalid time value in environment variable. Using previous value.");
    }
  } else {
    logMessage("No 'text' field in response. Using previous time.");
  }
} else {
  logMessage("Error in response. Using previous time.");
}
notecard.deleteResponse(rsp);

} else {
logMessage(“No response from Notecard. Using previous time.”);
}

char buffer[100];
snprintf(buffer, sizeof(buffer), “Current relay activation time: %lu ms”, relayActivationTime);
logMessage(buffer);
}

// Function to get the Notecard status variables
J *getNotecardStatus() {
J *status = JCreateObject();
char logBuffer[150];

// Get temperature
J *tempReq = notecard.newRequest(“card.temp”);
J *tempRsp = notecard.requestAndResponse(tempReq);
if (tempRsp != NULL) {
double temp = JGetNumber(tempRsp, “value”);
JAddNumberToObject(status, “temperature”, temp);
snprintf(logBuffer, sizeof(logBuffer), “SMN Temperature: %.2f°C”, temp);
logMessage(logBuffer);
notecard.deleteResponse(tempRsp);
} else {
logMessage(“SMN Failed to get temperature”);
}

// Get voltage
J *voltReq = notecard.newRequest(“card.voltage”);
J *voltRsp = notecard.requestAndResponse(voltReq);
if (voltRsp != NULL) {
double voltage = JGetNumber(voltRsp, “value”);
JAddNumberToObject(status, “voltage”, voltage);
snprintf(logBuffer, sizeof(logBuffer), “SMN Voltage: %.2fV”, voltage);
logMessage(logBuffer);
notecard.deleteResponse(voltRsp);
} else {
logMessage(“SMN Failed to get voltage”);
}

// Get wireless info
logMessage(“SMN Requesting wireless information…”);
J *wirelessReq = notecard.newRequest(“card.wireless”);
J *wirelessRsp = notecard.requestAndResponse(wirelessReq);
if (wirelessRsp != NULL) {
char *json = JPrint(wirelessRsp);
if (json) {
logMessage(“SMN Raw wireless response:”);
logMessage(json);
free(json);
}

const char* wirelessStatus = JGetString(wirelessRsp, "status");
if (wirelessStatus) {
  JAddStringToObject(status, "wireless_status", wirelessStatus);
  snprintf(logBuffer, sizeof(logBuffer), "SMN Wireless Status: %s", wirelessStatus);
  logMessage(logBuffer);
}

J *net = JGetObject(wirelessRsp, "net");
if (net != NULL) {
  int bars = JGetInt(net, "bars");
  const char* rat = JGetString(net, "rat");
  int rssi = JGetInt(net, "rssi");
  const char* band = JGetString(net, "band");

  JAddNumberToObject(status, "bars", bars);
  if (rat) JAddStringToObject(status, "rat", rat);
  JAddNumberToObject(status, "rssi", rssi);
  if (band) JAddStringToObject(status, "band", band);

  snprintf(logBuffer, sizeof(logBuffer), "SMN Wireless: Bars: %d, RAT: %s, RSSI: %d, Band: %s",
           bars, rat ? rat : "N/A", rssi, band ? band : "N/A");
  logMessage(logBuffer);
} else {
  logMessage("SMN Wireless: No net object in response");
}
notecard.deleteResponse(wirelessRsp);

} else {
logMessage(“SMN Failed to get wireless info”);
}

// Get motion info
logMessage(“SMN Requesting motion information…”);
J *motionReq = notecard.newRequest(“card.motion”);
J *motionRsp = notecard.requestAndResponse(motionReq);
if (motionRsp != NULL) {
char json = JPrint(motionRsp);
if (json) {
logMessage(“SMN Raw motion response:”);
logMessage(json);
free(json);
}
const char
orientation = JGetString(motionRsp, “status”);
if (orientation != NULL) {
JAddStringToObject(status, “orientation”, orientation);
snprintf(logBuffer, sizeof(logBuffer), “SMN Orientation: %s”, orientation);
logMessage(logBuffer);
} else {
logMessage(“SMN Orientation: Invalid reading”);
}
notecard.deleteResponse(motionRsp);
} else {
logMessage(“SMN Failed to get motion info”);
}

return status;
}

// Function to send Notecard status to Notehub
void sendNotecardStatusToNotehub() {
logMessage(“Preparing to send Notecard status to Notehub…”);
J *req = notecard.newRequest(“note.add”);
if (req != NULL) {
JAddStringToObject(req, “file”, “notecard_status.qo”);
JAddBoolToObject(req, “sync”, true);

    J *body = JCreateObject();
    if (body != NULL) {
        // Add additional status information
        J *additionalStatus = getNotecardStatus();
        if (additionalStatus != NULL) {
            JAddItemToObject(body, "notecard_status", additionalStatus);
            logMessage("Added Notecard status to the request body.");
        } else {
            logMessage("Failed to get additional status information.");
        }

        JAddItemToObject(req, "body", body);
        logMessage("Request body created successfully.");
    } else {
        logMessage("Failed to create request body.");
    }

    notecard.sendRequest(req);
    logMessage("Sent Notecard status update to Notehub.");
} else {
    logMessage("Failed to create Notecard request.");
}

}

// Function to send relay status to Notehub
void sendRelayStatusToNotehub(const char* relayState) {
J *req = notecard.newRequest(“note.add”);
if (req != NULL) {
JAddStringToObject(req, “file”, “relay_status.qo”);
JAddBoolToObject(req, “sync”, true);

    J *body = JCreateObject();
    if (body != NULL) {
        JAddStringToObject(body, "relay_output_state", relayState);
        JAddItemToObject(req, "body", body);
    }

    notecard.sendRequest(req);
    char buffer[100];
    snprintf(buffer, sizeof(buffer), "Sent relay status update to Notehub: %s", relayState);
    logMessage(buffer);
}

}

// Function to get the last command
typedef struct {
char command[30];
char source[30];
} Command;

Command getLastCommand(const char* file) {
Command cmd;
cmd.command[0] = ‘\0’;
cmd.source[0] = ‘\0’;

J *req = notecard.newRequest("note.get");
JAddStringToObject(req, "file", file);
JAddBoolToObject(req, "delete", true);
J *rsp = notecard.requestAndResponse(req);

if (!notecard.responseError(rsp)) {
    logMessage("Command note retrieved");
    J *body = JGetObject(rsp, "body");
    if (body != NULL) {
        const char *cmdValue = NULL;
        if (strcmp(file, "relay_change_request.qi") == 0) {
            cmdValue = JGetString(body, "relay");  // For relay commands
        } else if (strcmp(file, "notecard_status_request.qi") == 0) {
            cmdValue = JGetString(body, "notecard");  // For Notecard status commands
        }

        if (cmdValue != NULL) {
            strncpy(cmd.command, cmdValue, sizeof(cmd.command) - 1);
            cmd.command[sizeof(cmd.command) - 1] = '\0';  // Ensure null-termination
            strncpy(cmd.source, file, sizeof(cmd.source) - 1);
            cmd.source[sizeof(cmd.source) - 1] = '\0';
            notecard.deleteResponse(rsp);
            return cmd;
        }
    }
    notecard.deleteResponse(rsp);
}

logMessage("No commands available in file");
return cmd;

}

// Function to process Notecard status commands
void processNotecardStatusCommands() {
logMessage(“Checking for Notecard status commands…”);
Command cmd = getLastCommand(“notecard_status_request.qi”);

if (cmd.command[0] != '\0') {  // Only process if a command was received
    char buffer[100];
    snprintf(buffer, sizeof(buffer), "Notecard status command received: '%s' from '%s'", cmd.command, cmd.source);
    logMessage(buffer);

    if (strcmp(cmd.command, "update_card_status") == 0) {
        logMessage("COMMAND: Processing Update Card Status command");
        sendNotecardStatusToNotehub();
    } else {
        snprintf(buffer, sizeof(buffer), "Unknown Notecard status command received: '%s'", cmd.command);
        logMessage(buffer);
    }
} else {
    logMessage("No Notecard status command received");
}

}

// send status to notehub - this is the old generic - not notecard or relay specific
void sendStatusToNotehub(const char* status) {
J *req = notecard.newRequest(“note.add”);
if (req != NULL) {
JAddStringToObject(req, “file”, “status.qo”);
JAddBoolToObject(req, “sync”, true);

J *body = JCreateObject();
if (body != NULL) {
  JAddStringToObject(body, "status", status);
  
  // Add additional status information
  J *additionalStatus = getNotecardStatus();
  if (additionalStatus != NULL) {
    JAddItemToObject(body, "notecard_status", additionalStatus);
  }

  JAddItemToObject(req, "body", body);
}

notecard.sendRequest(req);
logMessage("Sent comprehensive status update to Notehub");

}
}
// end send status to notehub

// Function to process relay commands
void processRelayCommands() {
logMessage(“Checking for relay commands…”);
Command cmd = getLastCommand(“relay_change_request.qi”);

if (cmd.command[0] != '\0') {  // Only process if a command was received
    char buffer[100];
    snprintf(buffer, sizeof(buffer), "Relay command received: '%s' from '%s'", cmd.command, cmd.source);
    logMessage(buffer);

    if (strcmp(cmd.command, "open") == 0) {
        if (!relayActive && !holdActive) {
          // Initialize and fetch the relay activation time
            updateRelayActivationTime();  // added this to check if environment variable for the relay on time has changed
            logMessage("COMMAND: Processing Open command");
            snprintf(buffer, sizeof(buffer), "ACTION: Activating relay for %lu ms", relayActivationTime);
            logMessage(buffer);
            digitalWrite(LED_BUILTIN, HIGH);
            digitalWrite(RELAY_PIN, HIGH);
            sendRelayStatusToNotehub("open");
            relayStartTime = millis();
            relayDeactivationTime = relayStartTime + relayActivationTime;
            snprintf(buffer, sizeof(buffer), "Relay deactivation time set to: %lu", relayDeactivationTime);
            logMessage(buffer);
            relayActive = true;
        } else {
            logMessage("Relay already active or on hold, ignoring open command");
        }
    } else if (strcmp(cmd.command, "hold_open") == 0) {
        if (!holdActive) {
            logMessage("COMMAND: Processing Hold Open command");
            digitalWrite(LED_BUILTIN, HIGH);
            digitalWrite(RELAY_PIN, HIGH);
            holdActive = true;
            relayActive = false;  // We're not using the timed activation in this case
            sendRelayStatusToNotehub("held_open");
        } else {
            logMessage("Hold already active, ignoring hold_open command");
        }
    } else if (strcmp(cmd.command, "close_held_open_gate") == 0) {
        if (holdActive) {
            logMessage("COMMAND: Processing Release Hold command");
            digitalWrite(LED_BUILTIN, LOW);
            digitalWrite(RELAY_PIN, LOW);
            holdActive = false;
            sendRelayStatusToNotehub("closed");
        } else {
            logMessage("Hold not active, ignoring close_held_open_gate");
        }
    } else {
        snprintf(buffer, sizeof(buffer), "Unknown relay command received: '%s'", cmd.command);
        logMessage(buffer);
    }
} else {
    logMessage("No relay command received");
}

}

// Function to process all commands
void processCommands() {
// Process relay commands
processRelayCommands();

// Process Notecard status commands
processNotecardStatusCommands();

}

/////////////////////////////////////////////////////////////////////////////////////////////////
// Just setup and loop functions below this line
///////////////////////////////////////////////////////////////////////////////////////////////

void setup() {
pinMode(RELAY_PIN, OUTPUT);
digitalWrite(RELAY_PIN, LOW);
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW);

SerialUSB.begin(115200);
delay(15000); // Give it some time to initialize - 15 seconds
logMessage(“Starting the setup loop”);

// Begin communication with the Notecard
notecard.begin();
notecard.setDebugOutputStream(SerialUSB);

// Configure the Notecard for continuous mode
J *req = notecard.newRequest(“hub.set”);
JAddStringToObject(req, “product”, productUID);
JAddStringToObject(req, “mode”, “continuous”);
JAddBoolToObject(req, “sync”, true);
JAddNumberToObject(req, “inbound”, 15); //amount of time in Mimutes between a definite (forced) check for inboud notes via a hub.sync
JAddNumberToObject(req, “outbound”, 60); //amount of time in Minutes between outboud sync (forced) via a hub.sync - others are supposed to happen as the MCU code dictates
// // The hub.set request may fail if it’s sent shortly after power up. We use
// // sendRequestWithRetry to give it a chance to succeed.
// if (!notecard.sendRequestWithRetry(req, HUB_SET_TIMEOUT)) {
// logMessage(“notecard not responding\n”);
// return false;
// }
// notecard.sendRequest(req);
notecard.sendRequestWithRetry(req, 8); //a .sendrequest with an automatic retry after 8 (seconds?) if it fails the first time?

// Initialize and fetch the relay activation time
updateRelayActivationTime();

logMessage(“Setup complete”);
}

void loop() {
unsigned long currentTime = millis();

// Check for commands frequently
if (currentTime - lastCommandCheckTime >= COMMAND_CHECK_INTERVAL) {
char buffer[100];
snprintf(buffer, sizeof(buffer), “Checking commands after %lu ms”, currentTime - lastCommandCheckTime);
logMessage(buffer);
lastCommandCheckTime = currentTime;
processCommands();
processNotecardStatusCommands();
}

// Sync with Notehub less frequently
if (currentTime - lastSyncTime >= SYNC_INTERVAL) {
logMessage(“START SYNC”);

J *rsp = notecard.newRequest("hub.sync");
notecard.sendRequest(rsp);
updateRelayActivationTime();
lastSyncTime = currentTime;

logMessage("END SYNC");

}

// Check relay status and deactivate if necessary (only if not in hold mode)
if (relayActive && !holdActive && currentTime >= relayDeactivationTime) {
logMessage(“ACTION: Deactivating relay”);
digitalWrite(RELAY_PIN, LOW);
digitalWrite(LED_BUILTIN, LOW);
relayActive = false;
sendRelayStatusToNotehub(“inactive output”);
}

// Short delay to prevent tight looping
delay(100);
}

And apparently I don’t know how to include the code properly in this forum either…

Hi @sn13TX,

This is pretty tough to debug remotely. I would start by making sure you’re on the latest version of note-arduino (which as of right now is v1.6.0) and the latest Notecard firmware, but I’m skeptical either of those is the core issue.

If it were me, I’d get this running on my desk with an STLINK attached to the Swan to properly debug/monitor output, but in the EXACT configuration you have deployed (e.g. using the same power source etc with no extra peripherals attached). You can then tweak your firmware to output debug messages via the STLINK like in this sketch.

Rob

Hey @sn13TX,

I’d recommend pasting your code into https://gist.github.com/ and then including a link here, as it should format better there and we can check it out.

My guess is the problem is memory-related, especially since the reset button seems to fix the problem. When you paste the code somewhere with better formatting I can take a look and see if I notice anything weird.

TJ

Thanks guys. I was using Blues Wireless Notecard@^1.5.4, but have now updated that to 1.6.0.

This should be a link to the code with GitHub Gist

Hey @sn13TX,

I looked through your example code and nothing super obvious is jumping out to me. I do have a few suggestions:

  1. I’d recommend adding a boolean in your code that gives you the ability to turn off serial debugging for when you deploy your device. I don’t think this would be causing memory problems, but at the very least it’s unnecessary processing.

  2. I’d try the following trick to see if you can track down the memory problem.

#include <malloc.h>

void logMemoryUsage (const char *ctx_, bool enter_ = false) {
  struct mallinfo mi = mallinfo();
  Serial.print("[MEMORY][");
  Serial.print((enter_ ? ">>>>" : "<<<<"));
  Serial.print(ctx_);
  Serial.print("] Allocated: ");
  Serial.print(mi.uordblks, DEC);
  Serial.println(" bytes");
}

The idea is you can call this function at the start and end of certain processes to see whether your memory allocation is going up unexpectedly.

To start I’d do something like…

void loop() {
  logMemoryUsage(__FUNCTION__, true);

  // everything

  logMemoryUsage(__FUNCTION__, false);
}

When I add this code to our sensor tutorial, for example, I see the following output:

[MEMORY][<<<<loop] Allocated: 924 bytes
[MEMORY][>>>>loop] Allocated: 924 bytes
[INFO] {"req":"card.temp","crc":"0046:CE0D7EDD"}
[INFO] {"value":26.9375,"calibration":-1}
Temperature = 26.94 *C
Humidity = 48.29 %
[INFO] {"req":"note.add","file":"sensors.qo","sync":true,"body":{"temp":26.9375,"humidity":48.28680038452148},"crc":"0047:98CEE59A"}
[INFO] {"total":1}
[INFO] {"req":"env.get","name":"reading_interval","crc":"0048:B7D4A244"}
[INFO] {}
Delaying 15 seconds
[MEMORY][<<<<loop] Allocated: 924 bytes
[MEMORY][>>>>loop] Allocated: 924 bytes
[INFO] {"req":"card.temp","crc":"0049:CE0D7EDD"}
[INFO] {"value":26.9375,"calibration":-1}
Temperature = 26.94 *C
Humidity = 45.70 %
[INFO] {"req":"note.add","file":"sensors.qo","sync":true,"body":{"temp":26.9375,"humidity":45.69800186157227},"crc":"004A:172139B0"}
[INFO] {"total":1}
[INFO] {"req":"env.get","name":"reading_interval","crc":"004B:B7D4A244"}
[INFO] {}
Delaying 15 seconds
[MEMORY][<<<<loop] Allocated: 924 bytes
[MEMORY][>>>>loop] Allocated: 924 bytes
[INFO] {"req":"card.temp","crc":"004C:CE0D7EDD"}
[INFO] {"value":26.9375,"calibration":-1}
Temperature = 26.94 *C
Humidity = 45.62 %
[INFO] {"req":"note.add","file":"sensors.qo","sync":true,"body":{"temp":26.9375,"humidity":45.62229919433594},"crc":"004D:A8BE5351"}
[INFO] {"total":1}
[INFO] {"req":"env.get","name":"reading_interval","crc":"004E:B7D4A244"}
[INFO] {}
Delaying 15 seconds

If you add this debugging and see your memory allocation going up, you know you have a memory problem somewhere. From there you can add the logging in individual functions to try to track down specifically where the problem is at.

If you want a more sophisticated way of figuring this out, our firmware team recommends using unit tests based on Valgrind. Tools like that go above and beyond my understand of C, but if you have questions I can rope people who use it into this thread.

TJ

2 Likes

Thanks for the information TJ. I will give that a try.

I have not yet tried this code to check on the allocated memory. The reason is that I found Rob’s ‘jump scare skeleton’ project and realized that I may not need the MCU for a very basic implementation. I have indeed been able to get a simple system working in this manner. As I was reviewing various documents, I see that the aux GPIO pins have something called a “Internet Button Mode”, but I could find no information about this anywhere else on the website or in the forum. Can anyone tell me more about this and provide any additional documentation? Thanks.

Hey @sn13TX,

Yeah if you’re able to get it working without a host that’s a great approach. I’m actually not 100% sure what “Internet Button Mode” means, but I think it’s referring to monitor mode, as monitor mode lets you monitor a Notecard’s connectivity: Working with the Notecard AUX Pins - Blues Developers.

TJ