From fe6cc2ebb97f4427fc976ecab989b49c8d6e3f05 Mon Sep 17 00:00:00 2001 From: kuwoyuki Date: Mon, 10 Nov 2025 22:40:21 +0600 Subject: [PATCH] add mqtt, decouple i2c and sensor --- .vscode/settings.json | 7 +- Makefile | 1 + ha_mqtt.c | 199 ++++++++++++++++++++++++++++++++++++++ inc/gxht30_hw_i2c.h | 218 ++++++++---------------------------------- inc/ha_mqtt.h | 13 +++ inc/hw_i2c.h | 180 ++++++++++++++++++++++++++++++++++ main.c | 70 ++++---------- port/lwipopts.h | 9 +- 8 files changed, 466 insertions(+), 231 deletions(-) create mode 100644 ha_mqtt.c create mode 100644 inc/ha_mqtt.h create mode 100644 inc/hw_i2c.h diff --git a/.vscode/settings.json b/.vscode/settings.json index ac2942e..c707e13 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -42,6 +42,11 @@ "atomic": "c", "__bit_reference": "c", "err.h": "c", - "httpd.h": "c" + "httpd.h": "c", + "random": "c", + "limits": "c", + "tuple": "c", + "init.h": "c", + "hw_i2c.h": "c" } } \ No newline at end of file diff --git a/Makefile b/Makefile index 31a12ff..378bfdd 100644 --- a/Makefile +++ b/Makefile @@ -21,6 +21,7 @@ LWIP_C_FILES += $(COREFILES) LWIP_C_FILES += $(CORE4FILES) LWIP_C_FILES += $(NETIFFILES) LWIP_C_FILES += $(HTTPFILES) +LWIP_C_FILES += $(MQTTFILES) LWIP_C_FILES_WITH_PATH := $(LWIP_C_FILES) LWIP_PORT_FILES := $(wildcard $(PORT_DIR)/*.c $(PORT_DIR)/arch/*.c) diff --git a/ha_mqtt.c b/ha_mqtt.c new file mode 100644 index 0000000..4c29bb2 --- /dev/null +++ b/ha_mqtt.c @@ -0,0 +1,199 @@ +// 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; + } +} diff --git a/inc/gxht30_hw_i2c.h b/inc/gxht30_hw_i2c.h index 80c840a..6260068 100644 --- a/inc/gxht30_hw_i2c.h +++ b/inc/gxht30_hw_i2c.h @@ -1,22 +1,16 @@ -#ifndef _GXHT30_CH32_HW_I2C_H -#define _GXHT30_CH32_HW_I2C_H +#ifndef _GXHT30_HW_I2C_H +#define _GXHT30_HW_I2C_H #include #include -#include "ch32fun.h" -#include "ch32v20xhw.h" +#include "hw_i2c.h" -// I2C Configuration -#define GXHT30_I2C_CLKRATE 400000 -#define GXHT30_I2C_PRERATE 2000000 -#define GXHT30_I2C_TIMEOUT_MAX 250000 - -// GXHT30 I2C Addresses +// GXHT30 I2C addresses #define GXHT30_I2C_ADDR_DEFAULT 0x44 #define GXHT30_I2C_ADDR_ALT 0x45 -// Commands +// GXHT30 cmds #define GXHT30_CMD_MEAS_MSB 0x2C #define GXHT30_CMD_MEAS_LSB 0x06 #define GXHT30_CMD_SOFT_RESET_MSB 0x30 @@ -30,26 +24,18 @@ #define GXHT30_CMD_CLEAR_STATUS_MSB 0x30 #define GXHT30_CMD_CLEAR_STATUS_LSB 0x41 -// I2C Event Masks -#define GXHT30_I2C_EVT_MASTER_MODE_SELECT \ - ((uint32_t)0x00030001) // BUSY, MSL, SB -#define GXHT30_I2C_EVT_MASTER_TRANSMITTER_MODE \ - ((uint32_t)0x00070082) // BUSY, MSL, ADDR, TXE, TRA -#define GXHT30_I2C_EVT_MASTER_RECEIVER_MODE \ - ((uint32_t)0x00030002) // BUSY, MSL, ADDR -#define GXHT30_I2C_EVT_MASTER_BYTE_TRANSMITTED \ - ((uint32_t)0x00070084) // TRA, BUSY, MSL, TXE, BTF -#define GXHT30_I2C_EVT_MASTER_BYTE_RECEIVED \ - ((uint32_t)0x00030040) // BUSY, MSL, RXNE +// conversion constants +#define GXHT30_TEMP_MULTIPLIER 2670 +#define GXHT30_TEMP_OFFSET 45000000L +#define GXHT30_HUM_MULTIPLIER 1526 +#define GXHT30_SCALE_DIVISOR 10000 -// Sensor Data Structure typedef struct { - float temperature; // Temperature in Celsius - float humidity; // Relative humidity in % - uint8_t error; // Last error code + int32_t temperature_x100; // Temperature in hundredths of a degree C + int32_t humidity_x100; // Humidity in hundredths of a percent RH + uint8_t error; // Last error code } GXHT30_Data; -// Status Register Structure typedef struct { uint8_t alert_pending; // Bit 15: Alert status uint8_t heater_on; // Bit 13: Heater status @@ -61,76 +47,14 @@ typedef struct { uint16_t raw_status; // Raw 16-bit status value } GXHT30_Status; -// Error Codes enum GXHT30_Error { GXHT30_OK = 0, GXHT30_ERR_TIMEOUT, GXHT30_ERR_CRC, - GXHT30_ERR_I2C, - GXHT30_ERR_BUSY + GXHT30_ERR_I2C }; -static inline uint8_t _gxht30_i2c_check_event(uint32_t event_mask) { - uint32_t status = I2C1->STAR1 | (I2C1->STAR2 << 16); - return (status & event_mask) == event_mask; -} - -static inline uint8_t _gxht30_wait_event(uint32_t event_mask) { - int32_t timeout = GXHT30_I2C_TIMEOUT_MAX; - while (!_gxht30_i2c_check_event(event_mask) && (timeout-- > 0)); - return timeout > 0 ? GXHT30_OK : GXHT30_ERR_TIMEOUT; -} - -static inline uint8_t _gxht30_wait_flag(uint32_t flag) { - int32_t timeout = GXHT30_I2C_TIMEOUT_MAX; - while (!(I2C1->STAR1 & flag) && (timeout-- > 0)); - return timeout > 0 ? GXHT30_OK : GXHT30_ERR_TIMEOUT; -} - -static inline uint8_t _gxht30_i2c_start(uint8_t addr, uint8_t direction) { - // wait until bus is not busy - int32_t timeout = GXHT30_I2C_TIMEOUT_MAX; - while ((I2C1->STAR2 & I2C_STAR2_BUSY) && (timeout-- > 0)); - if (timeout <= 0) return GXHT30_ERR_TIMEOUT; - - // gen START - I2C1->CTLR1 |= I2C_CTLR1_START; - if (_gxht30_wait_event(GXHT30_I2C_EVT_MASTER_MODE_SELECT) != GXHT30_OK) - return GXHT30_ERR_TIMEOUT; - - // send addr - I2C1->DATAR = (addr << 1) | direction; - - uint32_t event = (direction == 0) ? GXHT30_I2C_EVT_MASTER_TRANSMITTER_MODE - : GXHT30_I2C_EVT_MASTER_RECEIVER_MODE; - - return _gxht30_wait_event(event); -} - -static inline uint8_t _gxht30_i2c_write_byte(uint8_t data) { - I2C1->DATAR = data; - return _gxht30_wait_flag(I2C_STAR1_TXE); -} - -static inline uint8_t _gxht30_i2c_read(uint8_t* buffer, uint8_t length) { - for (uint8_t i = 0; i < length; i++) { - if (i == length - 1) { - I2C1->CTLR1 &= ~I2C_CTLR1_ACK; // NACK last byte - } - - if (_gxht30_wait_flag(I2C_STAR1_RXNE) != GXHT30_OK) { - I2C1->CTLR1 |= I2C_CTLR1_ACK; - return GXHT30_ERR_TIMEOUT; - } - - buffer[i] = I2C1->DATAR; - } - - I2C1->CTLR1 |= I2C_CTLR1_ACK; // re-enable ACK - return GXHT30_OK; -} - -static inline bool _gxht30_crc8_check(uint8_t msb, uint8_t lsb, uint8_t crc) { +static inline bool gxht30_crc8_check(uint8_t msb, uint8_t lsb, uint8_t crc) { uint8_t calc_crc = 0xFF; uint8_t data[2] = {msb, lsb}; @@ -144,121 +68,61 @@ static inline bool _gxht30_crc8_check(uint8_t msb, uint8_t lsb, uint8_t crc) { return calc_crc == crc; } -// init I2C hw -static inline void gxht30_i2c_init(void) { - uint16_t tempreg; - - RCC->APB2PCENR |= RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO; - RCC->APB1PCENR |= RCC_APB1Periph_I2C1; - - // PB6 (SCL) and PB7 (SDA) - GPIOB->CFGLR &= ~(0xff << (4 * 6)); - GPIOB->CFGLR |= ((GPIO_Speed_10MHz | GPIO_CNF_OUT_OD_AF) << (4 * 6)) | - ((GPIO_Speed_10MHz | GPIO_CNF_OUT_OD_AF) << (4 * 7)); - - // rst I2C1 - RCC->APB1PRSTR |= RCC_APB1Periph_I2C1; - RCC->APB1PRSTR &= ~RCC_APB1Periph_I2C1; - - // i2c frequency - tempreg = I2C1->CTLR2; - tempreg &= ~I2C_CTLR2_FREQ; - tempreg |= (FUNCONF_SYSTEM_CORE_CLOCK / GXHT30_I2C_PRERATE) & I2C_CTLR2_FREQ; - I2C1->CTLR2 = tempreg; - - // Fast Mode 400kHz - tempreg = - (FUNCONF_SYSTEM_CORE_CLOCK / (3 * GXHT30_I2C_CLKRATE)) & I2C_CKCFGR_CCR; - tempreg |= I2C_CKCFGR_FS; - I2C1->CKCFGR = tempreg; - - // en I2C and ACK - I2C1->CTLR1 |= I2C_CTLR1_PE | I2C_CTLR1_ACK; -} - static inline uint8_t gxht30_send_command(uint8_t addr, uint8_t cmd_msb, uint8_t cmd_lsb) { - uint8_t err; + uint8_t cmd[2] = {cmd_msb, cmd_lsb}; + uint8_t err = i2c_write(addr, cmd, 2); - if ((err = _gxht30_i2c_start(addr, 0)) != GXHT30_OK) return err; - if ((err = _gxht30_i2c_write_byte(cmd_msb)) != GXHT30_OK) return err; - if ((err = _gxht30_i2c_write_byte(cmd_lsb)) != GXHT30_OK) return err; - if (_gxht30_wait_event(GXHT30_I2C_EVT_MASTER_BYTE_TRANSMITTED) != GXHT30_OK) - return GXHT30_ERR_TIMEOUT; - - I2C1->CTLR1 |= I2C_CTLR1_STOP; - return GXHT30_OK; + return (err == I2C_OK) ? GXHT30_OK : GXHT30_ERR_I2C; } -// read temp and humidity static inline uint8_t gxht30_read_data(uint8_t addr, GXHT30_Data* data) { + uint8_t cmd[2] = {GXHT30_CMD_MEAS_MSB, GXHT30_CMD_MEAS_LSB}; uint8_t rx_data[6]; uint8_t err; - if ((err = gxht30_send_command(addr, GXHT30_CMD_MEAS_MSB, - GXHT30_CMD_MEAS_LSB)) != GXHT30_OK) { - data->error = err; - return err; + // send measurement command and read + if ((err = i2c_write_read(addr, cmd, 2, rx_data, 6)) != I2C_OK) { + data->error = GXHT30_ERR_I2C; + return GXHT30_ERR_I2C; } - // read data - if ((err = _gxht30_i2c_start(addr, 1)) != GXHT30_OK) { - data->error = err; - return err; - } - - if ((err = _gxht30_i2c_read(rx_data, 6)) != GXHT30_OK) { - data->error = err; - return err; - } - - I2C1->CTLR1 |= I2C_CTLR1_STOP; - - // verify crc - if (!_gxht30_crc8_check(rx_data[0], rx_data[1], rx_data[2]) || - !_gxht30_crc8_check(rx_data[3], rx_data[4], rx_data[5])) { + // verify CRC for both temp and humidity + if (!gxht30_crc8_check(rx_data[0], rx_data[1], rx_data[2]) || + !gxht30_crc8_check(rx_data[3], rx_data[4], rx_data[5])) { data->error = GXHT30_ERR_CRC; return GXHT30_ERR_CRC; } - // calc values + // calc the values uint16_t temp_raw = (rx_data[0] << 8) | rx_data[1]; uint16_t hum_raw = (rx_data[3] << 8) | rx_data[4]; - data->temperature = (float)temp_raw * 0.00267033f - 45.0f; - data->humidity = (float)hum_raw * 0.0015259f; + data->temperature_x100 = + (((int64_t)temp_raw * GXHT30_TEMP_MULTIPLIER) - GXHT30_TEMP_OFFSET) / + GXHT30_SCALE_DIVISOR; + data->humidity_x100 = + ((int64_t)hum_raw * GXHT30_HUM_MULTIPLIER) / GXHT30_SCALE_DIVISOR; data->error = GXHT30_OK; return GXHT30_OK; } static inline uint8_t gxht30_read_status(uint8_t addr, GXHT30_Status* status) { + uint8_t cmd[2] = {GXHT30_CMD_STATUS_MSB, GXHT30_CMD_STATUS_LSB}; uint8_t rx_data[3]; uint8_t err; - if ((err = _gxht30_i2c_start(addr, 0)) != GXHT30_OK) return err; - if ((err = _gxht30_i2c_write_byte(GXHT30_CMD_STATUS_MSB)) != GXHT30_OK) - return err; - if ((err = _gxht30_i2c_write_byte(GXHT30_CMD_STATUS_LSB)) != GXHT30_OK) - return err; - if (_gxht30_wait_event(GXHT30_I2C_EVT_MASTER_BYTE_TRANSMITTED) != GXHT30_OK) - return GXHT30_ERR_TIMEOUT; + if ((err = i2c_write_read(addr, cmd, 2, rx_data, 3)) != I2C_OK) { + return GXHT30_ERR_I2C; + } - I2C1->CTLR1 |= I2C_CTLR1_START; - if (_gxht30_wait_event(GXHT30_I2C_EVT_MASTER_MODE_SELECT) != GXHT30_OK) - return GXHT30_ERR_TIMEOUT; - - I2C1->DATAR = (addr << 1) | 0x01; - if (_gxht30_wait_event(GXHT30_I2C_EVT_MASTER_RECEIVER_MODE) != GXHT30_OK) - return GXHT30_ERR_TIMEOUT; - - if ((err = _gxht30_i2c_read(rx_data, 3)) != GXHT30_OK) return err; - - I2C1->CTLR1 |= I2C_CTLR1_STOP; - - if (!_gxht30_crc8_check(rx_data[0], rx_data[1], rx_data[2])) + // verify CRC + if (!gxht30_crc8_check(rx_data[0], rx_data[1], rx_data[2])) { return GXHT30_ERR_CRC; + } + // parse status register uint16_t raw = (rx_data[0] << 8) | rx_data[1]; status->raw_status = raw; status->alert_pending = (raw >> 15) & 0x01; @@ -292,4 +156,4 @@ static inline uint8_t gxht30_clear_status(uint8_t addr) { GXHT30_CMD_CLEAR_STATUS_LSB); } -#endif // _GXHT30_CH32_HW_I2C_H \ No newline at end of file +#endif // _GXHT30_HW_I2C_H diff --git a/inc/ha_mqtt.h b/inc/ha_mqtt.h new file mode 100644 index 0000000..9e043ef --- /dev/null +++ b/inc/ha_mqtt.h @@ -0,0 +1,13 @@ +// ha_mqtt.h + +#ifndef HA_MQTT_H +#define HA_MQTT_H + +#include + +void ha_mqtt_init(void); +void ha_mqtt_publish_sensor_data(int32_t temp_x100, int32_t hum_x100); +void ha_mqtt_check_and_reconnect(void); +uint8_t ha_mqtt_is_connected(void); + +#endif // HA_MQTT_H \ No newline at end of file diff --git a/inc/hw_i2c.h b/inc/hw_i2c.h new file mode 100644 index 0000000..6b2f5b9 --- /dev/null +++ b/inc/hw_i2c.h @@ -0,0 +1,180 @@ +#ifndef _HW_I2C_H +#define _HW_I2C_H + +#include + +#include "ch32fun.h" +#include "ch32v20xhw.h" + +// config +#define I2C_CLKRATE 400000 +#define I2C_PRERATE 2000000 +#define I2C_TIMEOUT_MAX 250000 + +#define I2C_DIRECTION_TX 0 +#define I2C_DIRECTION_RX 1 + +enum I2C_Error { I2C_OK = 0, I2C_ERR_TIMEOUT, I2C_ERR_BUSY, I2C_ERR_NACK }; + +static inline uint8_t i2c_check_event(uint32_t event_mask) { + uint32_t status = I2C1->STAR1 | (I2C1->STAR2 << 16); + return (status & event_mask) == event_mask; +} + +static inline uint8_t i2c_wait_event(uint32_t event_mask) { + int32_t timeout = I2C_TIMEOUT_MAX; + while (!i2c_check_event(event_mask) && (timeout-- > 0)); + return timeout > 0 ? I2C_OK : I2C_ERR_TIMEOUT; +} + +static inline uint8_t i2c_wait_flag(uint32_t flag) { + int32_t timeout = I2C_TIMEOUT_MAX; + while (!(I2C1->STAR1 & flag) && (timeout-- > 0)); + return timeout > 0 ? I2C_OK : I2C_ERR_TIMEOUT; +} + +static inline uint8_t i2c_start(uint8_t addr, uint8_t direction) { + // wait until bus is not busy + int32_t timeout = I2C_TIMEOUT_MAX; + while ((I2C1->STAR2 & I2C_STAR2_BUSY) && (timeout-- > 0)); + if (timeout <= 0) return I2C_ERR_TIMEOUT; + + // gen START + I2C1->CTLR1 |= I2C_CTLR1_START; + if (i2c_wait_event(I2C_EVENT_MASTER_MODE_SELECT) != I2C_OK) + return I2C_ERR_TIMEOUT; + + // send address + I2C1->DATAR = (addr << 1) | direction; + + uint32_t event = (direction == I2C_DIRECTION_TX) + ? I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED + : I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED; + + return i2c_wait_event(event); +} + +static inline void i2c_stop(void) { I2C1->CTLR1 |= I2C_CTLR1_STOP; } + +static inline uint8_t i2c_write_byte(uint8_t data) { + I2C1->DATAR = data; + return i2c_wait_flag(I2C_STAR1_TXE); +} + +static inline uint8_t i2c_write(uint8_t addr, const uint8_t* data, + uint8_t length) { + uint8_t err; + + if ((err = i2c_start(addr, I2C_DIRECTION_TX)) != I2C_OK) return err; + + for (uint8_t i = 0; i < length; i++) { + if ((err = i2c_write_byte(data[i])) != I2C_OK) return err; + } + + if (i2c_wait_event(I2C_EVENT_MASTER_BYTE_TRANSMITTED) != I2C_OK) + return I2C_ERR_TIMEOUT; + + i2c_stop(); + return I2C_OK; +} + +static inline uint8_t i2c_read(uint8_t addr, uint8_t* buffer, uint8_t length) { + uint8_t err; + + if ((err = i2c_start(addr, I2C_DIRECTION_RX)) != I2C_OK) return err; + + for (uint8_t i = 0; i < length; i++) { + if (i == length - 1) { + I2C1->CTLR1 &= ~I2C_CTLR1_ACK; // NACK last byte + } + + if (i2c_wait_flag(I2C_STAR1_RXNE) != I2C_OK) { + I2C1->CTLR1 |= I2C_CTLR1_ACK; + return I2C_ERR_TIMEOUT; + } + + buffer[i] = I2C1->DATAR; + } + + I2C1->CTLR1 |= I2C_CTLR1_ACK; // re-enable ACK + i2c_stop(); + return I2C_OK; +} + +static inline uint8_t i2c_write_read(uint8_t addr, const uint8_t* tx_data, + uint8_t tx_len, uint8_t* rx_data, + uint8_t rx_len) { + uint8_t err; + + // Write phase + if ((err = i2c_start(addr, I2C_DIRECTION_TX)) != I2C_OK) return err; + + for (uint8_t i = 0; i < tx_len; i++) { + if ((err = i2c_write_byte(tx_data[i])) != I2C_OK) return err; + } + + if (i2c_wait_event(I2C_EVENT_MASTER_BYTE_TRANSMITTED) != I2C_OK) + return I2C_ERR_TIMEOUT; + + // repeated START for read phase + I2C1->CTLR1 |= I2C_CTLR1_START; + if (i2c_wait_event(I2C_EVENT_MASTER_MODE_SELECT) != I2C_OK) + return I2C_ERR_TIMEOUT; + + I2C1->DATAR = (addr << 1) | I2C_DIRECTION_RX; + if (i2c_wait_event(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) != I2C_OK) + return I2C_ERR_TIMEOUT; + + // read phase + for (uint8_t i = 0; i < rx_len; i++) { + if (i == rx_len - 1) { + I2C1->CTLR1 &= ~I2C_CTLR1_ACK; // NACK last byte + } + + if (i2c_wait_flag(I2C_STAR1_RXNE) != I2C_OK) { + I2C1->CTLR1 |= I2C_CTLR1_ACK; + return I2C_ERR_TIMEOUT; + } + + rx_data[i] = I2C1->DATAR; + } + + I2C1->CTLR1 |= I2C_CTLR1_ACK; // re-enable ACK + i2c_stop(); + return I2C_OK; +} + +static inline void i2c_init(void) { + uint16_t tempreg; + + RCC->APB2PCENR |= RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO; + RCC->APB1PCENR |= RCC_APB1Periph_I2C1; + + // PB6 (SCL) and PB7 (SDA) + GPIOB->CFGLR &= ~(0xFF << (4 * 6)); + GPIOB->CFGLR |= ((GPIO_Speed_10MHz | GPIO_CNF_OUT_OD_AF) << (4 * 6)) | + ((GPIO_Speed_10MHz | GPIO_CNF_OUT_OD_AF) << (4 * 7)); + + // Reset I2C1 to init all regs + RCC->APB1PRSTR |= RCC_APB1Periph_I2C1; + RCC->APB1PRSTR &= ~RCC_APB1Periph_I2C1; + + I2C1->CTLR1 |= I2C_CTLR1_SWRST; + I2C1->CTLR1 &= ~I2C_CTLR1_SWRST; + + // set I2C freq + tempreg = I2C1->CTLR2; + tempreg &= ~I2C_CTLR2_FREQ; + tempreg |= (FUNCONF_SYSTEM_CORE_CLOCK / I2C_PRERATE) & I2C_CTLR2_FREQ; + I2C1->CTLR2 = tempreg; + + // Fast Mode 400kHz + tempreg = (FUNCONF_SYSTEM_CORE_CLOCK / (3 * I2C_CLKRATE)) & I2C_CKCFGR_CCR; + tempreg |= I2C_CKCFGR_FS; + I2C1->CKCFGR = tempreg; + + // en I2C and ACK + I2C1->CTLR1 |= I2C_CTLR1_PE | I2C_CTLR1_ACK; +} + +#endif // _HW_I2C_H \ No newline at end of file diff --git a/main.c b/main.c index 656b296..64c0a2b 100644 --- a/main.c +++ b/main.c @@ -4,6 +4,8 @@ #include "ch32v20xhw.h" #include "ethernetif.h" #include "gxht30_hw_i2c.h" +#include "ha_mqtt.h" +#include "hw_i2c.h" #include "lwip/apps/httpd.h" #include "lwip/dhcp.h" #include "lwip/init.h" @@ -26,8 +28,7 @@ #define PLL_MULTIPLIER 15 #define STATS_PRINT_INTERVAL_MS 10000 -#define SENSOR_READ_INTERVAL_MS 5000 -#define STATUS_READ_INTERVAL_MS 30000 +#define SENSOR_READ_INTERVAL_MS 60000 struct netif g_netif; static volatile int g_httpd_is_initialized = 0; @@ -181,17 +182,17 @@ void ethernetif_print_stats(void) { int main() { SystemInit(); set_sysclk_to_120mhz_from_hse(); - print_clock_registers(); systick_init(); led_init(); lwip_stack_init(); - - gxht30_i2c_init(); + i2c_init(); gxht30_soft_reset(GXHT30_I2C_ADDR_DEFAULT); Delay_Ms(10); + ha_mqtt_init(); + uint32_t last_led_toggle_time = 0; uint32_t last_link_poll_time = 0; #if LWIP_STATS @@ -203,9 +204,8 @@ int main() { uint32_t last_status_read_time = 0; GXHT30_Data sensor_data = {0}; - GXHT30_Status sensor_status = {0}; - printf("GXHT30 Sensor initialized\n"); + printf("System initialized. Main loop starting.\n"); while (1) { ethernetif_input(&g_netif); @@ -223,60 +223,30 @@ int main() { } #endif - // Read sensor data periodically + ha_mqtt_check_and_reconnect(); + if (millis() - last_sensor_read_time > SENSOR_READ_INTERVAL_MS) { if (gxht30_read_data(GXHT30_I2C_ADDR_DEFAULT, &sensor_data) == GXHT30_OK) { - int16_t temp_int = (int16_t)(sensor_data.temperature * 100); - int16_t hum_int = (int16_t)(sensor_data.humidity * 100); + // int32_t temp_int = sensor_data.temperature_x100 / 100; + // int32_t temp_frac = sensor_data.temperature_x100 % 100; + // if (temp_frac < 0) temp_frac = -temp_frac; - int16_t temp_whole = temp_int / 100; - int16_t temp_decimal = temp_int % 100; - if (temp_decimal < 0) temp_decimal = -temp_decimal; + // int32_t hum_int = sensor_data.humidity_x100 / 100; + // int32_t hum_frac = sensor_data.humidity_x100 % 100; - int16_t hum_whole = hum_int / 100; - int16_t hum_decimal = hum_int % 100; + // printf("Read Sensor -> Temp: %ld.%02ld C, Hum: %ld.%02ld %%\n", + // temp_int, temp_frac, hum_int, hum_frac); + ha_mqtt_publish_sensor_data(sensor_data.temperature_x100, + sensor_data.humidity_x100); - printf("Temperature: %d.%02d C | Humidity: %d.%02d %%\n", temp_whole, - temp_decimal, hum_whole, hum_decimal); } else { - printf("Sensor error: %d\n", sensor_data.error); + printf("Failed to read sensor. Error: %d\n", sensor_data.error); } + last_sensor_read_time = millis(); } - // Read status register periodically - if (millis() - last_status_read_time > STATUS_READ_INTERVAL_MS) { - if (gxht30_read_status(GXHT30_I2C_ADDR_DEFAULT, &sensor_status) == - GXHT30_OK) { - printf("Status: "); - - if (sensor_status.alert_pending) printf("ALERT "); - if (sensor_status.heater_on) printf("HEATER_ON "); - if (sensor_status.humidity_alert) printf("HUM_ALERT "); - if (sensor_status.temperature_alert) printf("TEMP_ALERT "); - if (sensor_status.reset_detected) printf("RESET "); - if (sensor_status.command_status) printf("CMD_ERR "); - if (sensor_status.crc_status) printf("CRC_ERR "); - - if (sensor_status.raw_status == 0x0010) { - printf("OK (reset detected on startup)"); - } else if (sensor_status.raw_status == 0x0000) { - printf("OK"); - } - - printf("\n"); - - // Clear reset flag after first read if you want - // if (sensor_status.reset_detected) { - // gxht30_clear_status(GXHT30_I2C_ADDR_DEFAULT); - // } - } else { - printf("Status read error\n"); - } - last_status_read_time = millis(); - } - // uint32_t now = millis(); // if (now - last_led_toggle_time > LED_TOGGLE_INTERVAL_MS) { // if (led_state) { diff --git a/port/lwipopts.h b/port/lwipopts.h index e07648b..7a57d25 100644 --- a/port/lwipopts.h +++ b/port/lwipopts.h @@ -20,11 +20,9 @@ #define MEM_SIZE (4 * 1024) // Pbuf options -#define PBUF_POOL_SIZE 6 +#define PBUF_POOL_SIZE 16 #define PBUF_POOL_BUFSIZE 590 -#define MEMP_NUM_PBUF 6 // default 16 - // TCP options #define LWIP_TCP 1 #define TCP_MSS 536 @@ -34,6 +32,7 @@ #define LWIP_UDP 1 #define MEMP_NUM_UDP_PCB 3 // # of concurrent UDP "connections" +#define MEMP_NUM_SYS_TIMEOUT 8 #define LWIP_ICMP 1 #define LWIP_DHCP 1 @@ -41,6 +40,10 @@ #define LWIP_HTTPD_FS_SUPPORT 1 #define HTTPD_USE_CUSTOM_FSDATA 1 +// MQTT +#define LWIP_MQTT_CLIENT 1 +#define MQTT_OUTPUT_RINGBUF_SIZE 512 + #define LWIP_NETIF_HOSTNAME 1 #define LWIP_NETIF_LINK_CALLBACK 1 #define LWIP_NETIF_STATUS_CALLBACK 1