I highly expect that my code is failing at some point - hence the reason I'd like to add a watchdog reset.
My code:
/* Resol VBus Decoder and MQTT WiFi Transmitter for Resol DeltaSol BS/4 (https://www.resol.de/Produktdokumente/48005962_DeltaSol_BS4_V2.monen.pdf)
For: Raspberry Pi Pico W
Decodes temperature data from a Resol Solar Hot Water System Controller then transmits the decoded temperatures by MQTT using wireless.
Written using Arduino IDE 2.2.1
Version 1.0 October 2023
Author: ER
Based on: RESOL VBus Protocol Specification Date: 11.10.2007 (RESOL – Elektronische Regelungen GmbH)
VBus Protocol Specification Last updated: 27.01.2011
http://danielwippermann.github.io/resol-vbus/#/md/docs/vbus-specification
RS-485-TTL logic translator using an 817 optocoupler (https://esphome.io/components/vbus.html)
The Resol VBus signal is a modified RS-485 signal, level shifted via logic translator.
The VBus message 'packet' comprises a START (170), Header (10 bytes long), and data Frames (each 6 bytes long).
The 1st byte of a 'packet' includes the START byte (Header is 10 bytes). The last byte of every packet is a checksum (byte 9 of the Header, byte 6 for each Frame).*/
#include "definitions.h" // Definitions.h contains general information often repeated in my projects
#include <WiFi.h>
#include <AsyncMqtt_Generic.h>
#include <Ticker.h> // https://github.com/sstaub/Ticker
const char *PubTopic = "SHWS"; // Topic to publish
int status = WL_IDLE_STATUS;
bool connectedWiFi = false;
bool connectedMQTT = false;
unsigned long heartBeatTime = millis(); // Rate for onboard LED flash when running
bool heartBeatState = true; // Onboard LED state
byte packets[packetsLength]; // VBus raw data array
float sensors[numberOfSensors]; // Sensors temperature data array
//char temp[3];
AsyncMqttClient mqttClient;
void connectToMqtt();
void connectToMqttLoop();
Ticker connectToMqttTicker(connectToMqttLoop, MQTT_CHECK_INTERVAL_MS, 0, MILLIS); // Repeat forever, millis() resolution
void setup()
{
memset(packets, 0, packetsLength); // Zeroise the 'packets' array.
memset(sensors, 0, numberOfSensors*4); // Set the 'sensor' array to a non-physical number (is a float so needs 4 bytes).
Serial.begin(115200); // Start the Serial1 bus (for basic information to aid debugging)
while (!Serial && millis() < 5000); // Wait a bit...
Serial.print(String(SEPARATIONLINE) + "\n\rStarting FullyFeature_RP2040W on " + String(BOARD_NAME) + " using " + String(ASYNC_MQTT_GENERIC_VERSION));
Serial1.setFIFOSize(packetsLength);
Serial1.begin(9600); // Start the Serial bus (for receiving translated VBus data)
pinMode(LED_BUILTIN, OUTPUT); // For heartBeat()
connectToWifi();
mqttClient.setServer(MQTT_HOST, MQTT_PORT);
mqttClient.setCredentials(MQTT_USER, MQTT_PASSWORD);
mqttClient.onConnect(onMqttConnect);
mqttClient.onDisconnect(onMqttDisconnect);
mqttClient.onPublish(onMqttPublish);
connectToMqttTicker.start(); // Start the ticker.
connectToMqtt();
}
void loop()
{
bool progress = false; // Flag for setting success of each step
byte reportedFrames = 0; // This will provide the ability to process the reported frames rather than the entire 'packets' array.
progress = readVBusData(); // Attempt to see if valid data exists on Serial1 bus
if (progress) // Valid data has been received, so attempt to decode it
{
reportedFrames = packets[8]; // Extract the number of Frames (data elements) as reported by the Resol controller.
if (reportedFrames > 5) // This sketch only obtains temperatures from sensors S1, S2, and S3 (frames 1 and 2)
{
reportedFrames = 5;
}
}
if (progress)
{
progress = verifyData(reportedFrames); // Attempt to verify the received data
}
if (progress) // Attempt to unpack the received data
{
progress = unpackData(reportedFrames);
}
if (progress)
{
progress = buildTempData(); // Attempt to interpret the data into temperatures
}
connectToMqttTicker.update(); // Update the ticker
heartBeat(); // Flash the onboard LED
}
bool readVBusData() // Read the VBus data
{
bool readVBusDataSuccess = false;
byte chars = Serial1.available(); // Find how many characters are in the Serial1 buffer
if (chars > (packetsLength-20)) // If we have enough characters in the buffer...
{
if (Serial1.peek() == 170) // Look for the START of the frame (170)...
{
packets[0] = Serial1.read(); // ... and store the START frame.
if (Serial1.peek() == 16) // We have the 170...16 indicating a START and frames of data, so read the buffer.
{
for (int i = 1; i < chars; i++)
{
packets[i] = Serial1.read();
}
readVBusDataSuccess = true; // Data has been read from the Serial1 buffer successfully.
}
else Serial1.read(); // A 170 but not 170...16, so read on.
}
else Serial1.read(); // Not a 170 so read on.
}
return readVBusDataSuccess;
}
bool verifyData(byte NumDataBlocks) // Based on "vBus_CalcCrc" from "VBus Protocol Specification"
{
bool verifyDataSuccess = false;
for (byte thisBlock = 0; thisBlock < NumDataBlocks; thisBlock++)
{
byte offset = 4 + (thisBlock * 6);
byte blockLength = 5;
if (thisBlock == 0)
{
offset = 1;
blockLength = 8;
}
byte checkSumLocation = 9 + (thisBlock * 6); // Determine the location of the checkSum for this block of this packet
byte Crc = 0x7F;
for (byte blockElement = 0; blockElement < blockLength; blockElement++)
{
Crc = (Crc - packets[offset + blockElement]) & 0x7F;
}
if (Crc == packets[checkSumLocation])
{
verifyDataSuccess = true;
}
}
return verifyDataSuccess;
}
bool unpackData(byte numberOfFrames)
{
bool unpackDataSuccess = false;
byte startOfThisFrame = 0;
byte septettLocation = 0;
byte Septett = 0;
for (byte thisFrame = 1; thisFrame < numberOfFrames; thisFrame++)
{
startOfThisFrame = (4 + (thisFrame * 6));
septettLocation = 8 + (thisFrame * 6);
Septett = packets[septettLocation];
for (byte element = 0; element < 4; element++)
{
if (Septett & (1 << element))
{
packets[startOfThisFrame + element] |= 0x80;
unpackDataSuccess = true;
}
}
}
return unpackDataSuccess;
}
bool buildTempData()
{
bool buildTempDataSuccess = false;
sensors[0] = ((packets[10] + (packets[11] * 256))/10.0); // Rooftop manifold temperature
sensors[1] = ((packets[12] + (packets[13] * 256))/10.0); // Lower tank temperature
sensors[2] = ((packets[16] + (packets[17] * 256))/10.0); // Upper tank temperature
for (byte i = 0; i < 3; i++) // Check for valid temparature ranges
{
if (sensors[i] < 0 || sensors[i] > 250) // Valid temperature range should be greater than 4°C (use 0°C here) and less than 250°C (rooftop collector gets above 175°C).
{
buildTempDataSuccess = false;
return buildTempDataSuccess; // In the event of any temperature out of bounds, end the data checking and return a failure.
}
else buildTempDataSuccess = true;
}
return buildTempDataSuccess;
}
void printWifiStatus()
{
Serial.print("\n\rConnected to SSID: "); // Print the SSID of the connected network.
Serial.print(WiFi.SSID());
IPAddress ip = WiFi.localIP(); // Print board's IP address.
Serial.print("\n\rLocal IP Address: ");
Serial.print(ip);
long rssi = WiFi.RSSI(); // Print received signal strength.
Serial.print("\n\rSignal strength (RSSI): ");
Serial.print(rssi);
Serial.print(" dBm");
}
void heartBeat() // Flash the onboard LED (this approach doesn't use any delay)
{
digitalWrite(LED_BUILTIN, heartBeatState);
if (heartBeatTime < millis())
{
heartBeatState = !heartBeatState; // Change state
heartBeatTime = millis() + 1000; // Update to 1 second from now
}
}
bool connectToWifi()
{
if (WiFi.status() == WL_NO_MODULE) // check for the WiFi module:
{
Serial.print("\n\rCommunication with WiFi module failed!");
while (true); // don't continue unitl WiFi reconnects
}
Serial.print("\n\rConnecting to SSID: " + String(WIFI_SSID));
uint8_t numWiFiConnectTries = 0;
while ((status != WL_CONNECTED) && (numWiFiConnectTries++ < MAX_NUM_WIFI_CONNECT_TRIES_PER_LOOP)) // attempt to connect to WiFi network
{
status = WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
delay(500);
}
if (status != WL_CONNECTED) // Restart for Portenta as something is very wrong
{
Serial.println("Resetting. Can't connect to any WiFi");
NVIC_SystemReset();
}
printWifiStatus();
connectedWiFi = (status == WL_CONNECTED);
return (connectedWiFi);
}
bool isWiFiConnected()
{
static uint8_t theTTL = 10; // You can change longer or shorter depending on your network response; Shorter => more responsive, but more ping traffic
if (WiFi.ping(WiFi.gatewayIP(), theTTL) == theTTL) // Use ping() to test TCP connections
{
return true;
}
return false;
}
void connectToMqttLoop()
{
if (isWiFiConnected())
{
if (!connectedMQTT)
{
mqttClient.connect();
}
if (!connectedWiFi)
{
Serial.println("WiFi reconnected");
connectedWiFi = true;
}
}
else
{
if (connectedWiFi)
{
Serial.print("\n\rWiFi disconnected. Reconnecting");
connectedWiFi = false;
connectToWifi();
}
}
}
void connectToMqtt()
{
Serial.print("\n\rConnecting to MQTT...");
mqttClient.connect();
}
void onMqttConnect(bool sessionPresent)
{
connectedMQTT = true;
Serial.print("\n\rTempSensor1 = " + String(sensors[0]));
mqttClient.publish("SHWS/TempSensor1", 0, true, String(sensors[0]).c_str());
Serial.print("\n\rTempSensor2 = " + String(sensors[1]));
mqttClient.publish("SHWS/TempSensor2", 0, true, String(sensors[1]).c_str());
Serial.print("\n\rTempSensor3 = " + String(sensors[2]));
mqttClient.publish("SHWS/TempSensor3", 0, true, String(sensors[2]).c_str());
Serial.print(SEPARATIONLINE);
memset(sensors, 0, numberOfSensors*4);
}
void onMqttDisconnect(AsyncMqttClientDisconnectReason reason)
{
(void) reason;
connectedMQTT = false;
Serial.print("\n\r **** Disconnected from MQTT! ****");
}
void onMqttPublish(const uint16_t& packetId)
{
Serial.print("\n\rPublish acknowledged - packetId: " + String(packetId));
}
/***************************************************************************************************************************************
definitions.h
***************************************************************************************************************************************/
#ifndef definitions_h
#define definitions_h
#if !( defined(ARDUINO_RASPBERRY_PI_PICO_W) )
#error For RASPBERRY_PI_PICO_W only
#endif
// Debug Level from 0 to 4
#define _ASYNCTCP_RP2040W_LOGLEVEL_ 1
#define _ASYNC_MQTT_LOGLEVEL_ 1
#if (_ASYNC_MQTT_LOGLEVEL_ > 3)
#warning Using RASPBERRY_PI_PICO_W with CYC43439 WiFi
#endif
#define MQTT_CHECK_INTERVAL_MS 300000
#define MAX_NUM_WIFI_CONNECT_TRIES_PER_LOOP 20
#define WIFI_SSID "SSID" // your network SSID (name)
#define WIFI_PASSWORD "Password" // your network password (use for WPA, or use as key for WEP), length must be 8+
#define MQTT_HOST "192.168.188.102" // Broker address
#define MQTT_PORT 1883
#define MQTT_USER "mqtt-user"
#define MQTT_PASSWORD "mqtt-password"
#define SEPARATIONLINE "\n\r_______________________________________________________________"
#define packetsLength 128 // Maximum length of valid message is less than 64 bytes
#define numberOfSensors 3 // There are 4 Temperature Sensors (S1, S2, S3 and S4) although only S1, S2, and S3 are used here.
#endif