β¦ and pulling my hair out. In fact, I spent all day on this
My projects are in /home/maxg/Workspaces/PlatformIO/Projects. My current project is in HotWaterTermperatures. I have a a shared directory for files I want to include in other projects, as below.
# [2025-04-21 19:33] maxg@x570 ~/Workspaces/PlatformIO/Projects/Arduino_Shared $
ls -la -R /home/maxg/Workspaces/PlatformIO/Projects/Arduino_Shared
/home/maxg/Workspaces/PlatformIO/Projects/Arduino_Shared:
total 20
drwxrwxr-x 4 maxg maxg 4096 Apr 21 19:07 .
drwxrwxrwx 110 maxg maxg 4096 Apr 20 19:20 ..
drwxrwxr-x 2 maxg maxg 4096 Apr 21 19:09 include
-rw-rw-r-- 1 maxg maxg 297 Apr 21 19:12 library.json
drwxrwxr-x 2 maxg maxg 4096 Apr 21 19:09 src
/home/maxg/Workspaces/PlatformIO/Projects/Arduino_Shared/include:
total 16
drwxrwxr-x 2 maxg maxg 4096 Apr 21 19:09 .
drwxrwxr-x 4 maxg maxg 4096 Apr 21 19:07 ..
-rw-rw-r-- 1 maxg maxg 310 Apr 19 22:56 debug_utils.h
-rw-rw-r-- 1 maxg maxg 569 Apr 21 18:50 mqtt_utils.h
/home/maxg/Workspaces/PlatformIO/Projects/Arduino_Shared/src:
total 16
drwxrwxr-x 2 maxg maxg 4096 Apr 21 19:09 .
drwxrwxr-x 4 maxg maxg 4096 Apr 21 19:07 ..
-rw-rw-r-- 1 maxg maxg 4251 Apr 21 18:50 mqtt_utils.cpp
The files in Arduino_Shared are currently within another 70 odd projects. Iβd like to take these out of /src/main.cpp; and then maintain one copy for all of them in ArduinoShared.
I have tried this a couple of years ago and gave up. While I can hack some code, I only push the button to compile, and am new to header files as well. So attention may need to be focused on my include directives, the use of <> and ββ and more importantly the platformio.ini. I started out without putting the full path in; e.g., β¦/β¦/Arduino_Shared. It seems to make no difference.
Any hints appreciated. Thanks.
It seem I cannot attach files, so here their contents; also to allow someone keen to replicate the project.
debug_utils.h
#ifndef DEBUG_UTILS_H
#define DEBUG_UTILS_H
#include <Arduino.h>
#ifdef DEBUG
#define debug(x, ...) do { Serial.print(x, ##__VA_ARGS__); } while (0)
#define debugln(x, ...) do { Serial.println(x, ##__VA_ARGS__); } while (0)
#else
#define debug(x, ...)
#define debugln(x, ...)
#endif
#endif // DEBUG_UTILS_H
mqtt_utils.cpp
#include <mqtt_utils.h>
// External dependencies
extern EthernetClient ethernet_client;
extern PubSubClient mqtt_client;
extern bool b_is_mqtt_connected_to_broker();
void publish_mac_address()
{
char mac_string[18] {0};
snprintf(mac_string, sizeof(mac_string), "%02X:%02X:%02X:%02X:%02X:%02X",
MAC_ADDRESS[0], MAC_ADDRESS[1], MAC_ADDRESS[2],
MAC_ADDRESS[3], MAC_ADDRESS[4], MAC_ADDRESS[5]);
mac_string[17] = '\0';
publish_message(MQTT_PUB_MAC, mac_string);
}
void publish_ip_address()
{
char ip_address[16] {0};
IPAddress ip = (uint32_t)Ethernet.localIP();
snprintf(ip_address, sizeof(ip_address), "%u.%u.%u.%u",
ip[0], ip[1], ip[2], ip[3]);
publish_message(MQTT_PUB_IP, ip_address);
}
void maintain_dhcp_connection()
{
static const uint32_t DHCP_MAINTAIN_TIMER {600000};
static uint32_t last_time_dhcp_maintained {0};
uint8_t dhcp_return_code {1};
if ((millis() - last_time_dhcp_maintained) > DHCP_MAINTAIN_TIMER)
{
last_time_dhcp_maintained = millis();
dhcp_return_code = Ethernet.maintain();
switch (dhcp_return_code)
{
case 0:
publish_error(INFO, (char *)"DHCP OK");
break;
case 1:
publish_error(ERROR, (char *)"DHCP renew failed");
break;
case 2:
publish_error(INFO, (char *)"DHCP renew OK");
break;
case 3:
publish_error(ERROR, (char *)"DHCP rebind failed");
break;
case 4:
publish_error(INFO, (char *)"DHCP rebind OK");
break;
default:
break;
}
}
}
void publish_message(const char *topic, const char *msg)
{
if (false == b_is_mqtt_connected_to_broker())
{
debugln(F("MQTT: client not connected"));
}
const uint8_t BUF_SIZE_TOPIC {80};
char topic_string[BUF_SIZE_TOPIC] {0};
topic_string[0] = '\0';
strncat(topic_string, MQTT_PUB_TOPIC_BASE, BUF_SIZE_TOPIC - strlen(topic_string) - 1);
strncat(topic_string, topic, BUF_SIZE_TOPIC - strlen(topic_string) - 1);
mqtt_client.publish(topic_string, msg);
}
void publish_error(uint8_t errorType, char *errorMessage)
{
const uint8_t BUF_SIZE {80};
char buf[BUF_SIZE] {0};
buf[0] = '\0';
strncpy(buf, MQTT_CONNECT_ID, BUF_SIZE - strlen(buf) - 1);
switch (errorType)
{
case 0:
strncat(buf, "|INFO|", BUF_SIZE - strlen(buf) - 1);
break;
case 1:
strncat(buf, "|WARN|", BUF_SIZE - strlen(buf) - 1);
break;
case 2:
strncat(buf, "|ERROR|", BUF_SIZE - strlen(buf) - 1);
break;
case 3:
strncat(buf, "|DEBUG|", BUF_SIZE - strlen(buf) - 1);
break;
default:
break;
}
strncat(buf, errorMessage, BUF_SIZE - strlen(buf) - 1);
publish_message(MQTT_PUB_NOTIFICATION, buf);
}
void send_post_boot_data()
{
const uint8_t MQTT_PUBLISH_DELAY {200};
delay(MQTT_PUBLISH_DELAY);
publish_mac_address();
delay(MQTT_PUBLISH_DELAY);
publish_ip_address();
delay(MQTT_PUBLISH_DELAY);
publish_message(MQTT_PUB_VERSION, CODE_VERSION);
delay(MQTT_PUBLISH_DELAY);
}
void reconnect_mqtt_client()
{
const uint32_t MQTT_RECONNECT_INTERVAL {10000};
static uint32_t last_mqtt_reconnect_attempt = 0;
static uint16_t reconnect_attempts = 0;
if (millis() - last_mqtt_reconnect_attempt > MQTT_RECONNECT_INTERVAL)
{
last_mqtt_reconnect_attempt = millis();
reconnect_attempts++;
debugln(F("MQTT: attempting to reconnect to broker ..."));
mqtt_client.connect(MQTT_CONNECT_ID);
if (true == b_is_mqtt_connected_to_broker())
{
reconnect_attempts = 0;
debugln(F("... success."));
}
else
{
debugln(F("... failed."));
if (reconnect_attempts % 10 == 0)
{
debugln(F("Warning: Prolonged MQTT disconnect"));
publish_error(WARN, (char *)F("Prolonged MQTT disconnect"));
}
}
}
}
mqtt_utils.h
#ifndef MQTT_UTILS_H
#define MQTT_UTILS_H
#include <Arduino.h>
#include <Ethernet.h>
#include <PubSubClient.h>
#include <debug_utils.h>
#include "config.h"
// Log message level enumeration
enum LogMsgLevel
{
INFO,
WARN,
ERROR,
DEBUG
};
// Function prototypes
void publish_mac_address();
void publish_ip_address();
void maintain_dhcp_connection();
void publish_message(const char *topic, const char *message);
void publish_error(uint8_t errorType, char *errorMessage);
void send_post_boot_data();
void reconnect_mqtt_client();
#endif // MQTT_UTILS_H
library.json
{
"name": "ArduinoShared",
"version": "1.0.0",
"description": "Shared utilities for Arduino projects",
"keywords": "mqtt, debug, utilities",
"authors": [
{
"name": "Max Grenkowitz"
}
],
"frameworks": "arduino",
"platforms": "atmelavr"
}
config and main are in HotWaterTemperatures/src
config.cpp
/*
Configuration parameter file
*/
#include "config.h"
const char MQTT_CONNECT_ID[] {"ShedHotWaterSystem"};
const char MQTT_PUB_TOPIC_BASE[] {"ArgyleCourt/Shed/Tower/ShedHotWaterSystem/"};
const char MQTT_PUB_MAC[] {"MAC"};
const char MQTT_PUB_IP[] {"IP"};
const char MQTT_PUB_VERSION[] {"Version"};
const char MQTT_PUB_NOTIFICATION[] {"Notification"};
const char CODE_VERSION[] {"0.1.0"};
uint8_t MAC_ADDRESS[] {0x45, 0x48, 0x57, 0x53, 0x30, 0x31};
config.h
#ifndef CONFIG_H
#define CONFIG_H
#include <Arduino.h>
extern const char MQTT_CONNECT_ID[];
extern const char MQTT_PUB_TOPIC_BASE[];
extern const char MQTT_PUB_MAC[];
extern const char MQTT_PUB_IP[];
extern const char MQTT_PUB_VERSION[];
extern const char MQTT_PUB_NOTIFICATION[];
extern const char CODE_VERSION[];
extern uint8_t MAC_ADDRESS[];
#endif // CONFIG_H
platformio.ini
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:uno]
platform = atmelavr
board = uno
framework = arduino
[env:uno_r4_minima]
platform = renesas-ra
board = uno_r4_minima
framework = arduino
[env]
lib_deps =
arduino-libraries/Ethernet@^2.0.0
knolleary/PubSubClient@^2.8
paulstoffregen/OneWire@^2.3.8
milesburton/DallasTemperature@^4.0.4
file:///home/maxg/Workspaces/PlatformIO/Projects/Arduino_Shared
monitor_speed = 115200
upload_port = /dev/ttyACM1
build_flags =
-Wall
-Werror
-Wunused-variable
-I /home/maxg/Workspaces/PlatformIO/Projects/Arduino_Shared/include
; -I ../../Arduino_Shared/include
;src_filter = +<*.cpp> +<../../Arduino_Shared/mqtt_utils.cpp>
;lib_extra_dirs = ../../Arduino_Shared
lib_extra_dirs = /home/maxg/Workspaces/PlatformIO/Projects/Arduino_Shared
and the main.cpp
/*
Hotwater system controller with Arduino UNO R3 (or R4)
Copyright 2025 Max G
@author Max Grenkowitz
20250419 v0.1.0 MaxG - externalising repeated code into include files
20250417 v0.0.2 MaxG - formatting, naming, added sensor loop;
20250416 v0.0.1 MaxG - PoC
*/
/*! --------------- Libraries ----------------------------------------------- */
#include <Arduino.h> // basic Arduino definitions
#include <stdbool.h> // boolean definition
#include <SPI.h> // Serial Peripheral Interface Bus
#include <Ethernet.h> // Ethernet library for EtherShield
#include <PubSubClient.h> // MQTT client library
#include <OneWire.h>
#include <DallasTemperature.h>
#include "config.h"
#include <debug_utils.h>
#include <mqtt_utils.h>
#define DEBUG
#define BOARD_UNO_R3 // can be R3 or R4
#ifdef BOARD_UNO_R4
#include "FspTimer.h"
#include <WDT.h> // watch dog
FspTimer background_timer;
unsigned long g_timeout_counter = 0;
#else
#include <avr/wdt.h> // watch dog
#endif
const uint8_t G_PIN_ETHERNET_SEL {10}; // chip select for EthernetShield
const uint8_t G_PIN_ONE_WIRE_BUS {2}; // Pin for DS18B20 sensors
// Maximum number of sensors supported
const uint8_t G_MAX_NUM_SENSORS {4};
// Sensor names for MQTT temperature topics
const char* sensor_names[G_MAX_NUM_SENSORS] {"Top", "Bottom", "BoilerSkin", "TowerAir"};
// Minimum temperature change to publish (in Β°C)
const float G_TEMP_CHANGE_THRESHOLD {0.1};
bool g_has_controller_rebooted {true};
// Store previous temperatures to detect changes; preset with DS18B20 invalid temp
float g_previous_temps[G_MAX_NUM_SENSORS]
{
DEVICE_DISCONNECTED_C,
DEVICE_DISCONNECTED_C,
DEVICE_DISCONNECTED_C,
DEVICE_DISCONNECTED_C
};
uint8_t g_num_of_detected_sensors = 0;
/*! --------------- Instantiate global objects ------------------------------ */
EthernetClient ethernet_client;
PubSubClient mqtt_client(ethernet_client);
OneWire oneWire(G_PIN_ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
LogMsgLevel g_log_msg_level;
// Hardcoded ROM addresses for known sensors
DeviceAddress sensor_addresses[G_MAX_NUM_SENSORS] {
{0x28, 0x19, 0xC6, 0x85, 0x00, 0x00, 0x00, 0x35}, // Boiler Top
{0x28, 0xD3, 0x94, 0x85, 0x00, 0x00, 0x00, 0x73}, // Boiler Bottom
{0x28, 0x9D, 0xB2, 0x85, 0x00, 0x00, 0x00, 0x71}, // Boiler Skin
{0x28, 0x3A, 0xAB, 0x83, 0x00, 0x00, 0x00, 0x2E} // Tower Air
};
/*! --------------- Prototypes ---------------------------------------------- */
// modify function(s) as required
bool b_is_mqtt_connected_to_broker();
// Program specific functions
void read_temp_sensors();
void format_temp(float temp, char* output, size_t size);
void get_temp_sensors();
#ifdef BOARD_UNO_R4
void watchdog_timer_callback(timer_callback_args_t __attribute((unused)) *p_args);
bool begin_watchdog_timer(float rate);
#endif
/*! --------------- Function declarations ----------------------------------- */
/*!
* -----------------------------------------------------------------------------
* @brief b_is_mqtt_connected_to_broker --- (re)connect to MQTT broker
* @param[out] MQTT broker connection state
* -----------------------------------------------------------------------------
*/
bool b_is_mqtt_connected_to_broker()
{
return mqtt_client.connected();
// add any broker topic subcriptions here
} // b_is_mqtt_connected_to_broker()
/*!
* -----------------------------------------------------------------------------
* @brief read_temp_sensors --- reads and publishes temperature sensor data
* -----------------------------------------------------------------------------
*/
void read_temp_sensors()
{
sensors.requestTemperatures();
for (uint8_t i = 0; i < g_num_of_detected_sensors; i++)
{
float temp = sensors.getTempC(sensor_addresses[i]);
debug(F("Sensor "));
debug(i);
debug(F(" ("));
debug(sensor_names[i]);
debug(F("): "));
debugln(temp);
const char MQTT_PUB_TEMP_PREFIX[] {"Temp"};
if (abs(temp - g_previous_temps[i]) >= G_TEMP_CHANGE_THRESHOLD && temp != -127.0)
{
char tempStr[8];
format_temp(temp, tempStr, sizeof(tempStr));
// Concatenate MQTT_PUB_TEMP_PREFIX with sensor_names[i]
char topic_string[16];
snprintf(topic_string, sizeof(topic_string), "%s%s", MQTT_PUB_TEMP_PREFIX, sensor_names[i]);
publish_message(topic_string, tempStr);
g_previous_temps[i] = temp;
debug(F("Published "));
debug(sensor_names[i]);
debug(F(": "));
debugln(tempStr);
}
}
} // read_temp_sensors()
/*!
* -----------------------------------------------------------------------------
* @brief format_temp --- trims spaces and formats temperature
* -----------------------------------------------------------------------------
*/
void format_temp(float temp, char* output, size_t size)
{
char tempStr[16];
// Format: 6 chars total, 2 decimal places
dtostrf(temp, 6, 2, tempStr);
// Trim leading spaces
char* ptr = tempStr;
while (*ptr == ' ')
{
ptr++;
}
strncpy(output, ptr, size);
output[size - 1] = '\0';
} // format_temp()
/*!
* -----------------------------------------------------------------------------
* @brief get_temp_sensors --- initialises and configures DS18B20 sensors
* -----------------------------------------------------------------------------
*/
void get_temp_sensors()
{
sensors.begin();
g_num_of_detected_sensors = sensors.getDeviceCount();
uint8_t valid_sensors = 0;
if (g_num_of_detected_sensors < 1 || g_num_of_detected_sensors > G_MAX_NUM_SENSORS)
{
debug(F("Error: Found "));
debug(g_num_of_detected_sensors);
debug(F(" sensors, expected 1 to "));
debugln(G_MAX_NUM_SENSORS);
char error_msg[32];
snprintf(error_msg, sizeof(error_msg), "Invalid sensor count: %d", g_num_of_detected_sensors);
publish_error(ERROR, error_msg);
g_num_of_detected_sensors = 0;
return;
}
debug(F("Found "));
debug(g_num_of_detected_sensors);
debugln(F(" sensors"));
// Verify and set resolution for known sensors
for (uint8_t i = 0; i < g_num_of_detected_sensors; i++)
{
// Use hardcoded addresses for known sensors
if (i < 2 || sensor_addresses[i][0] != 0x00)
{
if (false == sensors.isConnected(sensor_addresses[i]))
{
debug(F("Error: Sensor "));
debug(sensor_names[i]);
debugln(F(" not found"));
char error_msg[24];
snprintf(error_msg, sizeof(error_msg), "Sensor %s not found", sensor_names[i]);
publish_error(2, error_msg);
continue;
}
sensors.setResolution(sensor_addresses[i], 12);
valid_sensors++;
}
else // Discover new sensors
{
DeviceAddress temp_address;
if (sensors.getAddress(temp_address, i))
{
memcpy(sensor_addresses[i], temp_address, 8);
sensors.setResolution(sensor_addresses[i], 12);
debug(F("Discovered sensor "));
debug(sensor_names[i]);
debug(F(" address: "));
for (uint8_t j = 0; j < 8; j++)
{
debug("0x");
debug(sensor_addresses[i][j], HEX);
debug(" ");
}
debugln();
valid_sensors++;
}
else
{
char error_msg[24];
snprintf(error_msg, sizeof(error_msg), "No sensor at index %d", i);
publish_error(ERROR, error_msg);
debugln(error_msg);
continue;
}
}
}
g_num_of_detected_sensors = valid_sensors;
if (g_num_of_detected_sensors == 0)
{
debugln(F("Warning: No valid sensors configured"));
publish_error(WARN, (char *) "No valid sensors configured");
}
} // get_temp_sensors()
#ifdef BOARD_UNO_R4
bool begin_watchdog_timer(float rate)
{
uint8_t timer_type = GPT_TIMER;
int8_t tindex = FspTimer::get_available_timer(timer_type);
if (tindex < 0)
{
tindex = FspTimer::get_available_timer(timer_type, true);
}
if (tindex < 0)
{
return false;
}
FspTimer::force_use_of_pwm_reserved_timer();
if (false == background_timer.begin(TIMER_MODE_PERIODIC, timer_type, tindex, rate, 0.0f, watchdog_timer_callback))
{
return false;
}
if (false == background_timer.setup_overflow_irq())
{
return false;
}
if (false == background_timer.open())
{
return false;
}
if (false == background_timer.start())
{
return false;
}
return true;
}
void watchdog_timer_callback(timer_callback_args_t __attribute((unused)) *p_args)
{
WDT.refresh();
// If more than 5 minutes unresponsive...
if (millis() > (g_timeout_counter + (1000 * 60 * 5)))
{
NVIC_SystemReset();
}
}
#endif
/*!
* -----------------------------------------------------------------------------
* @brief Setup routine -- runs once
* -----------------------------------------------------------------------------
*/
void setup()
{
// To ensure the EthernetShield has reset too
delay(200);
g_has_controller_rebooted = true;
#ifdef DEBUG
const uint32_t BAUD_RATE_HW {115200};
Serial.begin(BAUD_RATE_HW);
delay(500);
#endif
debug(F("\nReady to query EHWS v"));
debugln(CODE_VERSION);
debug("Compiled at: ");
debug(__DATE__);
debug(", ");
debugln(__TIME__);
debugln(F("Trying DHCP..."));
Ethernet.init(G_PIN_ETHERNET_SEL);
while (0 == Ethernet.begin(MAC_ADDRESS))
{
debugln(F("Get DHCP failed, retry in 2 sec."));
delay(2000);
}
const uint16_t MQTT_BROKER_PORT {1883};
const char* MQTT_BROKER_IP {"192.168.1.5"};
mqtt_client.setServer(MQTT_BROKER_IP, MQTT_BROKER_PORT);
// Retry MQTT connection until successful
debugln(F("Trying to connect to MQTT broker..."));
while (false == mqtt_client.connect(MQTT_CONNECT_ID))
{
debugln(F("Broker connection failed, retry in 2 sec."));
delay(2000);
}
debugln(F("Broker connected"));
debug(F("Client IP..: "));
debugln(Ethernet.localIP());
debug(F("Subnet Mask: "));
debugln(Ethernet.subnetMask());
debug(F("Gateway IP.: "));
debugln(Ethernet.gatewayIP());
debug(F("DNS srvr IP: "));
debugln(Ethernet.dnsServerIP());
debug(F("BROKER_IP..: "));
debugln(MQTT_BROKER_IP);
debug(F("CONNECT_ID.: "));
debugln(MQTT_CONNECT_ID);
get_temp_sensors();
#ifdef BOARD_UNO_R4
if (WDT.begin(5000))
{
WDT.refresh();
}
else
{
debugln(F("Error initialising watchdog!"));
}
begin_watchdog_timer(1);
#else
wdt_enable(WDTO_8S); // set 8-second watch dog timer
#endif
debugln(F("-> exit setup"));
} // setup()
/*!
* -----------------------------------------------------------------------------
* @brief Loop -- runs continuously
* -----------------------------------------------------------------------------
*/
void loop()
{
#ifdef BOARD_UNO_R4
g_timeout_counter = millis(); // Kick the timeout counter to avoid reset.
#else
// Reset watch dog timer; must the first statement in the loop
wdt_reset();
#endif
maintain_dhcp_connection();
if (false == mqtt_client.connected())
{
// Try establishing a connection to the MQTT server
reconnect_mqtt_client();
}
else
{
// Maintain MQTT connection and check for incoming messages
mqtt_client.loop();
// Start any loop work here
if (true == g_has_controller_rebooted)
{
g_has_controller_rebooted = false;
debugln(F("--> Arduino rebooted!"));
publish_error(INFO, (char*) "has rebooted");
send_post_boot_data();
// Add the first rouund of data output; otherwise we have to wait
// for the first read interval to pass.
read_temp_sensors();
}
const uint16_t TEMP_READ_INTERVAL {30000}; // [ms]
static uint32_t temp_last_read = 0; // timestamp for last time CAN bus was read
if (millis() - temp_last_read > TEMP_READ_INTERVAL)
{
debug(millis());
debugln(F("Read/publish sensors..."));
temp_last_read = millis();
read_temp_sensors();
}
// Periodically reinitialise sensors (e.g., here, every 5 minutes)
const uint32_t SENSOR_REINIT_INTERVAL {300000}; // [ms]
static uint32_t last_sensor_reinit = 0;
if (millis() - last_sensor_reinit > SENSOR_REINIT_INTERVAL)
{
debugln(F("Re-init sensors..."));
last_sensor_reinit = millis();
get_temp_sensors();
}
}
}
// Memory usage, see docs/memory_usage.txt
// end of file