From 6d7a8b99a706515d41bbafc170d26697f76d2a9b Mon Sep 17 00:00:00 2001 From: Travis Shears Date: Mon, 8 Sep 2025 09:20:24 +0200 Subject: [PATCH] convert mqtt_client from app to lib --- CmakeLists.txt | 70 +++++------- mqtt_client.c | 182 +++++++------------------------- mqtt_client.h | 32 ++++++ node1.c | 21 +++- node1.json => node1_config.json | 6 +- node1_simple_config.json | 38 +++++++ 6 files changed, 154 insertions(+), 195 deletions(-) create mode 100644 mqtt_client.h rename node1.json => node1_config.json (89%) create mode 100644 node1_simple_config.json diff --git a/CmakeLists.txt b/CmakeLists.txt index 6dce11d..5c9da52 100644 --- a/CmakeLists.txt +++ b/CmakeLists.txt @@ -20,36 +20,6 @@ pico_sdk_init() # == NODE 1 == -# the executable -add_executable( node1 node1.c bme280.c pms5003.c ) -pico_set_program_version(node1 "0.1") -pico_set_program_name(node1 "node_one") - -# pull in common dependencies -target_link_libraries( node1 pico_stdlib hardware_i2c) - -# create map/bin/hex file etc. -pico_add_extra_outputs( node1 ) - -# == WIFI SCAN == - -add_executable(wifi_scan wifi_scan.c) -target_include_directories(wifi_scan PRIVATE - ${CMAKE_CURRENT_LIST_DIR} - ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts - ) - -target_link_libraries(wifi_scan - pico_cyw43_arch_lwip_threadsafe_background - pico_stdlib - ) - -pico_add_extra_outputs(wifi_scan) - -# == MQTT TEST == - -# Define the host name of the MQTT server in an environment variable or pass it to cmake, -# e.g. cmake -DMQTT_SERVER=myserver .. if ((NOT MQTT_SERVER) OR (NOT MQTT_USERNAME) OR (NOT MQTT_PASSWORD)) message("Missing MQTT_SERVER or MQTT_USERNAME or MQTT_PASSWORD") @@ -72,25 +42,21 @@ if (NOT MQTT_CERT_INC) set(MQTT_CERT_INC mqtt_client.inc) endif() -set(TARGET_NAME mqtt_client) -add_executable(${TARGET_NAME} - mqtt_client.c - ) +# the executable +add_executable(node1 node1.c bme280.c pms5003.c mqtt_client.c) +pico_set_program_version(node1 "0.1") +pico_set_program_name(node1 "node_one") -target_link_libraries(${TARGET_NAME} - pico_stdlib - hardware_adc - pico_cyw43_arch_lwip_threadsafe_background - pico_lwip_mqtt - ) +# pull in common dependencies +target_link_libraries(node1 pico_stdlib hardware_i2c pico_cyw43_arch_lwip_threadsafe_background pico_lwip_mqtt) -target_include_directories(${TARGET_NAME} PRIVATE +target_include_directories(node1 PRIVATE ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required ) -target_compile_definitions(${TARGET_NAME} PRIVATE +target_compile_definitions(node1 PRIVATE WIFI_SSID=\"${WIFI_SSID}\" WIFI_PASSWORD=\"${WIFI_PASSWORD}\" MQTT_SERVER=\"${MQTT_SERVER}\" @@ -98,7 +64,25 @@ target_compile_definitions(${TARGET_NAME} PRIVATE MQTT_PASSWORD=\"${MQTT_PASSWORD}\" ) -pico_add_extra_outputs(${TARGET_NAME}) +# create map/bin/hex file etc. +pico_add_extra_outputs( node1 ) + +# == WIFI SCAN == + +add_executable(wifi_scan wifi_scan.c) +target_include_directories(wifi_scan PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts + ) + +target_link_libraries(wifi_scan + pico_cyw43_arch_lwip_threadsafe_background + pico_stdlib + ) + +pico_add_extra_outputs(wifi_scan) + + # == CAN BUS SENDER TEST == diff --git a/mqtt_client.c b/mqtt_client.c index 50a5781..8aa917f 100644 --- a/mqtt_client.c +++ b/mqtt_client.c @@ -4,59 +4,14 @@ * PICO W MQTT example: https://github.com/raspberrypi/pico-examples/blob/master/pico_w/wifi/mqtt/README */ +#include "mqtt_client.h" #include "pico/stdlib.h" #include "pico/cyw43_arch.h" -#include "hardware/adc.h" #include "lwip/apps/mqtt.h" +// #include +#include +#include -// Temperature -#ifndef TEMPERATURE_UNITS -#define TEMPERATURE_UNITS 'C' // Set to 'F' for Fahrenheit -#endif - -#ifndef MQTT_SERVER -#error Need to define MQTT_SERVER -#endif - -// This file includes your client certificate for client server authentication -#ifdef MQTT_CERT_INC -#include MQTT_CERT_INC -#endif - -#ifndef MQTT_TOPIC_LEN -#define MQTT_TOPIC_LEN 100 -#endif - -typedef struct { - mqtt_client_t* mqtt_client_inst; - struct mqtt_connect_client_info_t mqtt_client_info; - char data[MQTT_OUTPUT_RINGBUF_SIZE]; - char topic[MQTT_TOPIC_LEN]; - uint32_t len; - ip_addr_t mqtt_server_address; - bool connect_done; - int subscribe_count; - bool stop_client; -} MQTT_CLIENT_DATA_T; - -#ifndef DEBUG_printf -#ifndef NDEBUG -#define DEBUG_printf printf -#else -#define DEBUG_printf(...) -#endif -#endif - -#ifndef INFO_printf -#define INFO_printf printf -#endif - -#ifndef ERROR_printf -#define ERROR_printf printf -#endif - -// how often to measure our temperature -#define TEMP_WORKER_TIME_S 10 // keep alive in seconds #define MQTT_KEEP_ALIVE_S 60 @@ -67,88 +22,22 @@ typedef struct { // Exactly once (QoS 2) #define MQTT_PUBLISH_QOS 1 #define MQTT_PUBLISH_RETAIN 0 -#define MQTT_DEVICE_NAME "pico_test_001" - -// Set to 1 to add the client name to topics, to support multiple devices using the same server -// -#define MQTT_TOPIC "bws/test1/state" - -/* References for this implementation: - * raspberry-pi-pico-c-sdk.pdf, Section '4.1.1. hardware_adc' - * pico-examples/adc/adc_console/adc_console.c */ -static float read_onboard_temperature(const char unit) { - - /* 12-bit conversion, assume max value == ADC_VREF == 3.3 V */ - const float conversionFactor = 3.3f / (1 << 12); - - float adc = (float)adc_read() * conversionFactor; - float tempC = 27.0f - (adc - 0.706f) / 0.001721f; - - if (unit == 'C' || unit != 'F') { - return tempC; - } else if (unit == 'F') { - return tempC * 9 / 5 + 32; - } - - return -1.0f; -} static void pub_request_cb(__unused void *arg, err_t err) { if (err != 0) { - ERROR_printf("pub_request_cb failed %d", err); + printf("pub_request_cb failed %d", err); } } -static void publish_temperature(MQTT_CLIENT_DATA_T *state) { - static float old_temperature; - float temperature = read_onboard_temperature(TEMPERATURE_UNITS); - if (temperature != old_temperature) { - old_temperature = temperature; - // Publish temperature on /temperature topic - char temp_str[16]; - snprintf(temp_str, sizeof(temp_str), "%.2f", temperature); - INFO_printf("Publishing Temp %s\n", temp_str); - mqtt_publish(state->mqtt_client_inst, MQTT_TOPIC, temp_str, strlen(temp_str), MQTT_PUBLISH_QOS, MQTT_PUBLISH_RETAIN, pub_request_cb, state); - } -} +// static void publish_temperature(MQTT_CLIENT_DATA_T *state) { + // mqtt_publish(state->mqtt_client_inst, MQTT_TOPIC, temp_str, strlen(temp_str), MQTT_PUBLISH_QOS, MQTT_PUBLISH_RETAIN, pub_request_cb, state); -static void sub_request_cb(void *arg, err_t err) { - MQTT_CLIENT_DATA_T* state = (MQTT_CLIENT_DATA_T*)arg; - if (err != 0) { - panic("subscribe request failed %d", err); - } - state->subscribe_count++; -} - -static void unsub_request_cb(void *arg, err_t err) { - MQTT_CLIENT_DATA_T* state = (MQTT_CLIENT_DATA_T*)arg; - if (err != 0) { - panic("unsubscribe request failed %d", err); - } - state->subscribe_count--; - assert(state->subscribe_count >= 0); - - // Stop if requested - if (state->subscribe_count <= 0 && state->stop_client) { - mqtt_disconnect(state->mqtt_client_inst); - } -} - -static void temperature_worker_fn(async_context_t *context, async_at_time_worker_t *worker) { - MQTT_CLIENT_DATA_T* state = (MQTT_CLIENT_DATA_T*)worker->user_data; - publish_temperature(state); - async_context_add_at_time_worker_in_ms(context, worker, TEMP_WORKER_TIME_S * 1000); -} -static async_at_time_worker_t temperature_worker = { .do_work = temperature_worker_fn }; static void mqtt_connection_cb(mqtt_client_t *client, void *arg, mqtt_connection_status_t status) { MQTT_CLIENT_DATA_T* state = (MQTT_CLIENT_DATA_T*)arg; if (status == MQTT_CONNECT_ACCEPTED) { state->connect_done = true; - - // Publish temperature every 10 sec if it's changed - temperature_worker.user_data = state; - async_context_add_at_time_worker_in_ms(cyw43_arch_async_context(), &temperature_worker, 0); + printf("Connected to MQTT server\n"); } else if (status == MQTT_CONNECT_DISCONNECTED) { if (!state->connect_done) { panic("Failed to connect to mqtt server"); @@ -165,8 +54,8 @@ static void start_client(MQTT_CLIENT_DATA_T *state) { if (!state->mqtt_client_inst) { panic("MQTT client instance creation error"); } - INFO_printf("IP address of this device %s\n", ipaddr_ntoa(&(netif_list->ip_addr))); - INFO_printf("Connecting to mqtt server at %s\n", ipaddr_ntoa(&state->mqtt_server_address)); + printf("IP address of this device %s\n", ipaddr_ntoa(&(netif_list->ip_addr))); + printf("Connecting to mqtt server at %s\n", ipaddr_ntoa(&state->mqtt_server_address)); cyw43_arch_lwip_begin(); if (mqtt_client_connect(state->mqtt_client_inst, &state->mqtt_server_address, port, mqtt_connection_cb, state, &state->mqtt_client_info) != ERR_OK) { @@ -175,24 +64,26 @@ static void start_client(MQTT_CLIENT_DATA_T *state) { cyw43_arch_lwip_end(); } -int main(void) { - stdio_init_all(); - INFO_printf("mqtt client starting\n"); - - adc_init(); - adc_set_temp_sensor_enabled(true); - adc_select_input(4); - - static MQTT_CLIENT_DATA_T state; +void mqtt_client_pub_message(mqtt_client_config *config, const char *message) { + if (mqtt_publish(config->state.mqtt_client_inst, config->topic, message, strlen(message), MQTT_PUBLISH_QOS, MQTT_PUBLISH_RETAIN, pub_request_cb, NULL) != ERR_OK) { + panic("MQTT publish error"); + } +} +void mqtt_client_init(mqtt_client_config *config, const char *topic, const char *device_name) { + // Copy the topic string into the config struct, up to 200 characters + strncpy(config->topic, topic, 100); + // config->topic[100] = '\0'; // Ensure null-termination + strncpy(config->device_name, device_name, 50); + printf("mqtt client starting\n"); if (cyw43_arch_init()) { panic("Failed to inizialize CYW43"); } - state.mqtt_client_info.client_id = MQTT_DEVICE_NAME; - state.mqtt_client_info.keep_alive = MQTT_KEEP_ALIVE_S; // Keep alive in sec - state.mqtt_client_info.client_user = MQTT_USERNAME; - state.mqtt_client_info.client_pass = MQTT_PASSWORD; + config->state.mqtt_client_info.client_id = config->device_name; + config->state.mqtt_client_info.keep_alive = MQTT_KEEP_ALIVE_S; // Keep alive in sec + config->state.mqtt_client_info.client_user = MQTT_USERNAME; + config->state.mqtt_client_info.client_pass = MQTT_PASSWORD; ip_addr_t ip_addr; // Initialize the IP address if (ipaddr_aton(MQTT_SERVER, &ip_addr)) { @@ -200,21 +91,24 @@ int main(void) { } else { printf("Invalid IP address format.\n"); } - state.mqtt_server_address = ip_addr; + config->state.mqtt_server_address = ip_addr; cyw43_arch_enable_sta_mode(); if (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 30000)) { panic("Failed to connect"); } - INFO_printf("\nConnected to Wifi\n"); + printf("\nConnected to Wifi\n"); - start_client(&state); + start_client(&config->state); - while (!state.connect_done || mqtt_client_is_connected(state.mqtt_client_inst)) { - cyw43_arch_poll(); - cyw43_arch_wait_for_work_until(make_timeout_time_ms(10000)); - } - - INFO_printf("mqtt client exiting\n"); - return 0; + // while (!config->state.connect_done || mqtt_client_is_connected(config->state.mqtt_client_inst)) { + // cyw43_arch_poll(); + // cyw43_arch_wait_for_work_until(make_timeout_time_ms(10000)); + // } + // printf("mqtt client exiting\n"); + // return 0; +} + +void mqtt_client_do_network_stuff(mqtt_client_config *config) { + cyw43_arch_poll(); } diff --git a/mqtt_client.h b/mqtt_client.h new file mode 100644 index 0000000..9d150a1 --- /dev/null +++ b/mqtt_client.h @@ -0,0 +1,32 @@ +#ifndef MQTT_CLIENT_H +#define MQTT_CLIENT_H + +// #include "pico/cyw43_arch.h" +#include "lwip/apps/mqtt.h" + +#ifndef MQTT_SERVER +#error Need to define MQTT_SERVER +#endif + +typedef struct { + mqtt_client_t* mqtt_client_inst; + struct mqtt_connect_client_info_t mqtt_client_info; + char data[MQTT_OUTPUT_RINGBUF_SIZE]; + char topic[100]; + uint32_t len; + ip_addr_t mqtt_server_address; + bool connect_done; + int subscribe_count; + bool stop_client; +} MQTT_CLIENT_DATA_T; + +typedef struct { + char topic[201]; // max 200 chars + null terminator + char device_name[51]; // max 50 chars + null terminator + MQTT_CLIENT_DATA_T state; +} mqtt_client_config; + +void mqtt_client_init(mqtt_client_config *config, const char *topic, const char *device_name); +void mqtt_client_pub_message(mqtt_client_config *config, const char *message); +void mqtt_client_do_network_stuff(mqtt_client_config *config); +#endif /* MQTT_CLIENT_H */ diff --git a/node1.c b/node1.c index a26f7a4..136a807 100644 --- a/node1.c +++ b/node1.c @@ -1,6 +1,7 @@ #include "bme280.h" #include "pico/stdlib.h" #include "pms5003.h" +#include "mqtt_client.h" #include #include #include @@ -9,10 +10,11 @@ #include #include #include +#include -#define LOOP_INTERVAL_MS 5000 -// TODO: put loop back to 30 seconds when done testing -// #define LOOP_INTERVAL_MS 30000 +// 5 sec loop is for testing +// #define LOOP_INTERVAL_MS 5000 +#define LOOP_INTERVAL_MS 30000 /** * Balcony Weather Station Node 1 @@ -46,6 +48,8 @@ void comms_led_update() { } } +static mqtt_client_config mqtt_config; + static pms5003_config pms_config; static pms5003_reading current_pms5003_reading; @@ -88,9 +92,13 @@ static bool cb_30(__unused struct repeating_timer *t) { printf("PM1: %.2f\n", current_pms5003_reading.pm1); printf("PM2.5: %.2f\n", current_pms5003_reading.pm2_5); printf("PM10: %.2f\n", current_pms5003_reading.pm10); - + // char msg[100]; + char msg[200]; + snprintf(msg, sizeof(msg), "{\"temp\": %.2f, \"pressure\": %.2f, \"humidity\": %.2f, \"pm1\": %.2f, \"pm2_5\": %.2f, \"pm10\": %.2f}\n", + current_bem280_reading.temperature, current_bem280_reading.pressure, current_bem280_reading.humidity, + current_pms5003_reading.pm1, current_pms5003_reading.pm2_5, current_pms5003_reading.pm10); printf("Sending data to home assistant...\n"); - // TODO: Send data to home assistant + mqtt_client_pub_message(&mqtt_config, msg); return true; } @@ -124,6 +132,8 @@ int main() { // Initialize communication LED comms_led_init(); + mqtt_client_init(&mqtt_config, "homeassistant/sensor/bws/node1/state", "bws-node1"); + // Setup BME280 bme280_init(&bem_config, i2c1, 14, 15); @@ -136,5 +146,6 @@ int main() { comms_led_update(); sleep_us(100); tight_loop_contents(); + mqtt_client_do_network_stuff(&mqtt_config); } } diff --git a/node1.json b/node1_config.json similarity index 89% rename from node1.json rename to node1_config.json index f2febb4..4b5c482 100644 --- a/node1.json +++ b/node1_config.json @@ -11,7 +11,7 @@ "p": "sensor", "device_class": "temperature", "unit_of_measurement": "°C", - "value_template": "{{ value_json.temperature}}", + "value_template": "{{ value_json.temp}}", "unique_id": "bws_node1_temp_001" }, "node1_humidity": { @@ -39,7 +39,7 @@ "p": "sensor", "device_class": "pm25", "unit_of_measurement": "µg/m³", - "value_template": "{{ value_json.pm25}}", + "value_template": "{{ value_json.pm2_5}}", "unique_id": "bws_node1_pm25_001" }, "node1_pm10": { @@ -53,6 +53,6 @@ "o": { "name": "diy_pico_w" }, - "state_topic": "bws/node1/state", + "state_topic": "homeassistant/sensor/bws/node1/state", "qos": 1 } diff --git a/node1_simple_config.json b/node1_simple_config.json new file mode 100644 index 0000000..7fa12e1 --- /dev/null +++ b/node1_simple_config.json @@ -0,0 +1,38 @@ +{ + "dev": { + "ids": "bws_node1_001", + "name": "Node1", + "mf": "diy", + "sw": "1.0", + "hw": "1.0" + }, + "cmps": { + "node1_temp": { + "p": "sensor", + "device_class": "temperature", + "unit_of_measurement": "°C", + "value_template": "{{ value_json.temp}}", + "unique_id": "bws_node1_temp_001" + }, + "node1_humidity": { + "p": "sensor" + }, + "node1_pressure": { + "p": "sensor" + }, + "node1_pm1": { + "p": "sensor" + }, + "node1_pm25": { + "p": "sensor" + }, + "node1_pm10": { + "p": "sensor" + } + }, + "o": { + "name": "diy_pico_w" + }, + "state_topic": "homeassistant/sensor/bws/node1/state", + "qos": 1 +}