// ha_mqtt.c #include "ha_mqtt.h" #include #include #include "lwip/apps/mqtt.h" #include "lwip/ip4_addr.h" #include "lwip/netif.h" #include "systick.h" // config #define MQTT_BROKER_IP_A 192 #define MQTT_BROKER_IP_B 168 #define MQTT_BROKER_IP_C 102 #define MQTT_BROKER_IP_D 1 #define MQTT_BROKER_PORT 1883 #define MQTT_CLIENT_ID "gxht30_sensor_node" #define MQTT_RECONNECT_INTERVAL_MS 5000 #define DEVICE_UNIQUE_ID "gxht30_board_01" #define DEVICE_NAME "Office Climate Sensor" #define DEVICE_MANUFACTURER "me" #define DEVICE_MODEL "GXHT30-ETH-v1" // home assistant topics #define HA_DISCOVERY_PREFIX "homeassistant" #define TEMP_UNIQUE_ID DEVICE_UNIQUE_ID "_temperature" #define TEMP_CONFIG_TOPIC \ HA_DISCOVERY_PREFIX "/sensor/" TEMP_UNIQUE_ID "/config" #define TEMP_STATE_TOPIC "devices/" DEVICE_UNIQUE_ID "/temperature/state" #define TEMP_NAME "Temperature" #define HUM_UNIQUE_ID DEVICE_UNIQUE_ID "_humidity" #define HUM_CONFIG_TOPIC HA_DISCOVERY_PREFIX "/sensor/" HUM_UNIQUE_ID "/config" #define HUM_STATE_TOPIC "devices/" DEVICE_UNIQUE_ID "/humidity/state" #define HUM_NAME "Humidity" #define MQTT_RECONNECT_INTERVAL_MS 5000 static mqtt_client_t* s_mqtt_client; static ip_addr_t s_mqtt_broker_ip; static uint8_t s_mqtt_connected = 0; static uint32_t s_last_mqtt_retry_time = 0; extern struct netif g_netif; static void publish_ha_discovery_configs(mqtt_client_t* client); static void mqtt_pub_request_cb(void* arg, err_t err); static void mqtt_connection_cb(mqtt_client_t* client, void* arg, mqtt_connection_status_t status); static void publish_sensor_value(const char* topic, int32_t value_x100); // public fns void ha_mqtt_init(void) { s_mqtt_client = mqtt_client_new(); if (s_mqtt_client == NULL) { printf("error: failed to create mqtt client\n"); return; } IP4_ADDR(&s_mqtt_broker_ip, MQTT_BROKER_IP_A, MQTT_BROKER_IP_B, MQTT_BROKER_IP_C, MQTT_BROKER_IP_D); ha_mqtt_check_and_reconnect(); } void ha_mqtt_publish_sensor_data(int32_t temp_x100, int32_t hum_x100) { if (!s_mqtt_connected) { return; } publish_sensor_value(TEMP_STATE_TOPIC, temp_x100); publish_sensor_value(HUM_STATE_TOPIC, hum_x100); } void ha_mqtt_check_and_reconnect(void) { if (s_mqtt_connected || !netif_is_up(&g_netif) || !netif_is_link_up(&g_netif) || netif_ip4_addr(&g_netif)->addr == 0) { return; } if (millis() - s_last_mqtt_retry_time > MQTT_RECONNECT_INTERVAL_MS) { printf("attempting to connect to mqtt broker\n"); const struct mqtt_connect_client_info_t client_info = { .client_id = MQTT_CLIENT_ID, .keep_alive = 60, // .client_user = "user", // authentication // .client_pass = "pass", }; err_t err = mqtt_client_connect(s_mqtt_client, &s_mqtt_broker_ip, MQTT_BROKER_PORT, mqtt_connection_cb, NULL, &client_info); if (err != ERR_OK) { printf("mqtt connect failed with immediate error: %d\n", err); } s_last_mqtt_retry_time = millis(); } } uint8_t ha_mqtt_is_connected(void) { return s_mqtt_connected; } // static fns /** * @brief Publishes a sensor value after formatting it from a fixed-point * int * @param topic The MQTT topic to publish to. * @param value_x100 The sensor value, scaled by 100. */ static void publish_sensor_value(const char* topic, int32_t value_x100) { char payload_buf[16]; // format the fixed-point value (e.g., 2345) into a decimal string ("23.45") int32_t val_int = value_x100 / 100; int32_t val_frac = value_x100 % 100; if (val_frac < 0) { val_frac = -val_frac; } snprintf(payload_buf, sizeof(payload_buf), "%ld.%02ld", val_int, val_frac); err_t err = mqtt_publish(s_mqtt_client, topic, payload_buf, strlen(payload_buf), 1, 0, NULL, NULL); if (err != ERR_OK) { printf("failed to publish to topic %s: %d\n", topic, err); } } static void publish_ha_discovery_configs(mqtt_client_t* client) { char payload[512]; err_t err; // publish temperature sensor config snprintf( payload, sizeof(payload), "{" "\"name\":\"%s\",\"unique_id\":\"%s\",\"stat_t\":\"%s\"," "\"dev_cla\":\"temperature\",\"unit_of_meas\":\"°C\"," "\"val_tpl\":\"{{ value|float(2) }}\"," "\"dev\":{\"ids\":[\"%s\"],\"name\":\"%s\",\"mf\":\"%s\",\"mdl\":\"%s\"}" "}", TEMP_NAME, TEMP_UNIQUE_ID, TEMP_STATE_TOPIC, DEVICE_UNIQUE_ID, DEVICE_NAME, DEVICE_MANUFACTURER, DEVICE_MODEL); printf("publishing ha discovery for temperature...\n"); err = mqtt_publish(client, TEMP_CONFIG_TOPIC, payload, strlen(payload), 1, 1, mqtt_pub_request_cb, NULL); if (err != ERR_OK) { printf("failed to publish temp config: %d\n", err); } // publish humidity sensor config snprintf( payload, sizeof(payload), "{" "\"name\":\"%s\",\"unique_id\":\"%s\",\"stat_t\":\"%s\"," "\"dev_cla\":\"humidity\",\"unit_of_meas\":\"%%\"," "\"val_tpl\":\"{{ value|float(2) }}\"," "\"dev\":{\"ids\":[\"%s\"],\"name\":\"%s\",\"mf\":\"%s\",\"mdl\":\"%s\"}" "}", HUM_NAME, HUM_UNIQUE_ID, HUM_STATE_TOPIC, DEVICE_UNIQUE_ID, DEVICE_NAME, DEVICE_MANUFACTURER, DEVICE_MODEL); printf("publishing ha discovery for humidity...\n"); err = mqtt_publish(client, HUM_CONFIG_TOPIC, payload, strlen(payload), 1, 1, mqtt_pub_request_cb, NULL); if (err != ERR_OK) { printf("failed to publish hum config: %d\n", err); } } static void mqtt_pub_request_cb(void* arg, err_t err) { (void)arg; if (err != ERR_OK) { printf("mqtt publish failed with error: %d\n", err); } } static void mqtt_connection_cb(mqtt_client_t* client, void* arg, mqtt_connection_status_t status) { (void)arg; if (status == MQTT_CONNECT_ACCEPTED) { printf("mqtt connection successful\n"); s_mqtt_connected = 1; // on connect, publish the discovery configuration messages publish_ha_discovery_configs(client); } else { printf("mqtt connection failed, status: %d. will retry.\n", status); s_mqtt_connected = 0; } }