Rock solid sketch, using mbed OS features, wifi and mqtt

Here I present a template to program the "Arduino nano RP2040 connect". As there are very few examples still for this board, I hope they can serve others and I look forward to suggestions for improvement !.
It is a program that uses various properties of the "mbed OS" such as Threads, Ticker and Whatchdog.
It has been programmed with the Arduino IDE 1.8.15, using the wifinina, PubSubClient and ArduinoJson libraries.

Apart from setup () and loop (), three more tasks have been created, th1, th2 and th3. The th1 is responsible for blinking the led13 and resetting the watchdog. The th2 manages the connection to the router and the mqtt broker. The th3 resets the board every hour. A tck1 Ticker with an irrelevant function has been added with the intention of seeing if it worked.

The intention is to create a solid program like a rock.

Code in Github or here.

Regards

/*
Arduino Nano RP2040 template for mqtt projects
The intention is to create a rock solid if is possible.
Created from different sources.
May 30 2021
jnogues@gmail.com
@rprimTech
 
This example code is in the public domain
*/

#include <ArduinoJson.h>
#include <PubSubClient.h>

#include "mbed.h"
#include <rtos.h>
using namespace rtos;
using namespace mbed;

#include <SPI.h>
#include <WiFiNINA.h>
NinaPin ledR = LEDR;
NinaPin ledG = LEDG;
NinaPin ledB = LEDB;

///////please enter your sensitive data in the Secret tab/arduino_secrets.h
char ssid[] = "yourSSID";        // your network SSID (name)
char pass[] = "yourPASS";    // your network password (use for WPA, or use as key for WEP)
int status = WL_IDLE_STATUS;     // the WiFi radio's status

Thread th1;
Thread th2;
Thread th3;
Ticker tck1;

volatile bool ticTac=0;
volatile bool wifiOK = 0;
volatile bool mqttOK = 0;

WiFiClient mqttClient;
PubSubClient client(mqttClient);
const char* mqtt_server = "xx.xx.xx.xx";
unsigned long lastMsg = 0;

StaticJsonDocument<512> doc;

const uint32_t TIMEOUT_MS = 10000;//don't work the maximum is 8.3 seconds
const uint32_t TIME_BETWEEN_RESETS_MS = 3600000; //1 hour


void setup() 
{
  
  Serial.begin(115200);
  while (!Serial) {}; // wait for serial port to connect. Needed for native USB port only
  Serial.println("[BOOT] .............Starting............");
  pinMode(13,OUTPUT);
  digitalWrite(13, LOW);
  // Setup the 3 pins as OUTPUT
  pinMode(ledR, OUTPUT);
  pinMode(ledG, OUTPUT);
  pinMode(ledB, OUTPUT);
  digitalWrite(ledR, LOW);
  digitalWrite(ledG, LOW);
  digitalWrite(ledB, LOW);

  Serial.print("[WTHD] Max Timeout Whatchdog = ");
  Serial.print(Watchdog::get_instance().get_max_timeout());
  Serial.println(" uS");
  
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);

  Watchdog &watchdog = Watchdog::get_instance();
  watchdog.start();
  Watchdog::get_instance().kick();//clear whatchdog
   
  th1.start(task_blinkLed13);
  th2.start(task_reconnectingWIFI);
  th3.start(task_softwareReset);
  tck1.attach(&flipp, 1.0);//Using Ticker
}

void loop() 
{
    ThisThread::sleep_for(10000);//like delay(10000)
    publishState();
    Watchdog::get_instance().kick();
    
}


//************************** mbed OS Tasks *************************
void task_softwareReset()
{
  Serial.println("[TASK] Starting Task softwareReset");
  ThisThread::sleep_for(TIME_BETWEEN_RESETS_MS);
  Serial.println("[RST] *******Recurrent Reset*******");
  //NVIC_SystemReset();//works!
  system_reset();//works!
}

void task_blinkLed13()
{

 Serial.println("[TASK] Starting Task blinkLed13");
 while (true) 
    {
        Watchdog::get_instance().kick();
        digitalWrite(13, HIGH);
        ThisThread::sleep_for(500);  
        digitalWrite(13, LOW);
        ThisThread::sleep_for(500);
    }
}

void task_reconnectingWIFI()
{
  Serial.println("[TASK] Starting Task reconectingWIFI");
  while(true)
  {
        
          if (WiFi.status() == WL_CONNECTED)
          {
            //Serial.println("[WIFI] WiFi OK");
            wifiOK = 1;
            digitalWrite(ledG, HIGH);
            digitalWrite(ledR, LOW);
            //return;
          }
        
          if (WiFi.status() != WL_CONNECTED)
          {
            wifiOK = 0;
            mqttOK = 0;
            digitalWrite(ledG, LOW);
            digitalWrite(ledR, HIGH);
            // Start connection to WLAN router and print a status value
            Serial.println("[WIFI] Trying to connect to WLAN router");
            WiFi.disconnect();
            client.disconnect();
            ThisThread::sleep_for(1000);
        
            status = WiFi.begin(ssid, pass);
            // WL_IDLE_STATUS     = 0
            // WL_NO_SSID_AVAIL   = 1
            // WL_SCAN_COMPLETED  = 2
            // WL_CONNECTED       = 3
            // WL_CONNECT_FAILED  = 4
            // WL_CONNECTION_LOST = 5
            // WL_DISCONNECTED    = 6
            ThisThread::sleep_for(10000);
            Serial.print("[WIFI] Wifi status= ");
            Serial.println (status);
            if (status == WL_CONNECTED)
            {
                wifiOK = 1;
                Serial.println("[WIFI] Connection to WLAN router successful");
                printCurrentNet();
                printWifiData();
            }
          }

          CheckMQTT();
          ThisThread::sleep_for(10);     
  }
}

void CheckMQTT()
{
  if (client.connected() && wifiOK)
  {
    mqttOK = 1;
    client.loop();
    //Serial.println("MQTT OK");
  }

  if (client.connected() && !wifiOK)
  {
    mqttOK = 0;
  }

  if (!client.connected() && !wifiOK)
  {
    mqttOK = 0;
  }
  
  if (!client.connected() && wifiOK)
  {
    client.disconnect();
    Serial.print("[MQTT] Attempting MQTT connection...");
    // Create a random client ID
    String clientId = "yourClientName";
    if (client.connect(clientId.c_str())) 
    {
      Serial.println("connected");
      // Once connected, publish an announcement...
      client.publish("/RP2040/outTopic", "hello world");
      // ... and resubscribe
      client.subscribe("/RP2040/inTopic");
    } else 
    {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in  seconds");
      client.disconnect();
    }
  }
}

//*************** mqtt functions ******************************
void callback(char* topic, byte* payload, unsigned int length) 
{
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) 
  {
    Serial.print((char)payload[i]);
  }
  Serial.println();  
}

void publishState()
{
  char estat[256];
  memset(estat,0,256);//esborra estat
  unsigned long upTime = millis()/1000;
  
  doc["uT"] = upTime;
  
  serializeJson(doc, estat);
  if (mqttOK) client.publish("/RP2040/state", estat);
  Serial.print("[STAT] ");
  Serial.println(estat);
}

//************* other functions ***************************
void printWifiData() {
  // print your board's IP address:
  IPAddress ip = WiFi.localIP();
  Serial.print("[WIFI] IP Address: ");
  Serial.println(ip);

  // print your MAC address:
  byte mac[6];
  WiFi.macAddress(mac);
  Serial.print("[WIFI] MAC address: ");
  printMacAddress(mac);
}

void printCurrentNet() {
  // print the SSID of the network you're attached to:
  Serial.print("[WIFI] SSID: ");
  Serial.println(WiFi.SSID());

  // print the MAC address of the router you're attached to:
  byte bssid[6];
  WiFi.BSSID(bssid);
  Serial.print("[WIFI] BSSID: ");
  printMacAddress(bssid);

  // print the received signal strength:
  long rssi = WiFi.RSSI();
  Serial.print("[WIFI] signal strength (RSSI):");
  Serial.println(rssi);

  // print the encryption type:
  byte encryption = WiFi.encryptionType();
  Serial.print("[WIFI] Encryption Type:");
  Serial.println(encryption, HEX);
  //Serial.println();
}

void printMacAddress(byte mac[]) {
  for (int i = 5; i >= 0; i--) {
    if (mac[i] < 16) {
      Serial.print("0");
    }
    Serial.print(mac[i], HEX);
    if (i > 0) {
      Serial.print(":");
    }
  }
  Serial.println();
}


//******************* Ticker ******************************
void flipp()
{    
    ticTac = !ticTac;
}
2 Likes

If you want a rock-solid program, I would try to make it work without resetting it every hour. Reset should be a last resort to recover from an unpredictable situation. It should be up to the user to do that. Imaging your car, power station or any other utility product just resetting whenever it feels like it.

Regarding watchdog, sprinkling watchdog clear everywhere in your sketch makes it basically useless. For instance, if your blinkLED task is the only thing still working the watchdog will be sleeping happily while the rest of your system is broken. You want to make sure the watchdog is reset in a single location of your code (if possible) and depending on everything working as expected.

I would add the MQTT server to the arduino_secrets.h. Keeps it all in one place.

Remove unused variables e.g., lastMsg and comments e.g., // Setup the 3 pins as OUTPUT

Give all used pins a name instead of using the pin number e.g.,13

Use Tools -> Auto Format to ensure indentation is the same across the file.

Do not use non-English comments e.g., //esborra estat and variable names. Two languages are enough for one sketch. :slight_smile:

Use longer variables names to make the code easier to read. e.g., Ticker tck1; -> Ticker ticker1; or better tickerUsefulName.

Hi Klaus_K, thanks for your tips. I will change them and update the code. I am not very clear how to deal with the whatchdog, since I have some delays higher than the maximum 8 seconds. Do you know if it is possible to increase this value?

There is no single right answer to this as with many technical challenges. One approach that might be easy to understand would be to create a task that handles the watchdog. Then you look at which task you want to protect, and which ones do not need protection. All the tasks that need protection will send a value/flag/report to the watchdog task. The watchdog task will know which task have to send a "report" and how often. If all the reports come in on time the watchdog is cleared.

This will allow you to create a watchdog reset in a short amount of time in tasks where you know it needs to finish fast and other task could sleep for a long time.

It also stops one task from clearing the watchdog while other tasks stopped working.

The "report" can be anything from simple flags send every time a task has run through the code to checksums created during the run of a task to ensure every important part was executed in the right order.