diff --git a/.vscode/settings.json b/.vscode/settings.json index 45b4a6e..bf83ac5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -18,6 +18,13 @@ "systick.h": "c", "timer.h": "c", "stdint.h": "c", - "mqtt_interface.h": "c" + "mqtt_interface.h": "c", + "gpio.h": "c", + "modbus_handler.h": "c", + "rs485.h": "c", + "system_init.h": "c", + "mqtt_handler.h": "c", + "cstdlib": "c", + "modbus_master.h": "c" } } \ No newline at end of file diff --git a/include/config.h b/include/config.h index 318cd51..3768cb2 100644 --- a/include/config.h +++ b/include/config.h @@ -3,23 +3,74 @@ #include +// Debug flags #define DEBUG_MODE 1 -#define DNS_RUN_INTERVAL_MS 100 +// Device Bus Types +typedef enum { BUS_RS485, BUS_ONEWIRE } bus_type_t; -// #define LOCAL_PORT 5000 +// Device Type Definitions +typedef enum { + DEVICE_RELAY = 1, + DEVICE_SOIL_SENSOR = 2, + DEVICE_THERMOMETER = 3 +} device_type_t; -// MQTT configuration -#define CLIENT_ID "ch32_node" +// Node Configuration +typedef struct { + const char* id; // Unique identifier for the node + const char* name; // Human readable name + const char* location; // Optional location description +} node_config_t; -#define MQTT_TARGET_IP {192, 168, 102, 100} -#define MQTT_TARGET_PORT 1883 +// RS485 Device Configuration +typedef struct { + uint8_t slave_id; // Modbus slave ID + device_type_t type; // Type of device + const char* name; // Device name (used in MQTT topics) +} rs485_device_t; +// OneWire naming scheme configuration +typedef struct { + const char* location; // Location prefix for the sensor + const char* name_prefix; // Prefix for auto-generated names +} onewire_naming_t; + +// Network Configuration +#define MQTT_SERVER_IP {192, 168, 102, 100} +#define MQTT_PORT 1883 + +// MQTT Configuration #define MQTT_KEEP_ALIVE_INTERVAL 60 #define MQTT_TX_BUFFER_SIZE 128 #define MQTT_RX_BUFFER_SIZE 128 #define MQTT_COMMAND_TIMEOUT_MS 1000 -#define SUB_TOPIC "listen/world" -#define PUB_TOPIC "hello/world" + +// Node Specific Configuration +static const node_config_t NODE_CONFIG = { + .id = "ch32-node1", .name = "CH32 Node 1", .location = "somewhere"}; + +// RS485 Devices Configuration +#define RS485_DEVICE_COUNT 2 + +static const rs485_device_t RS485_DEVICES[RS485_DEVICE_COUNT] = { + {.slave_id = 0x01, .type = DEVICE_RELAY, .name = "relay-1"}, + {.slave_id = 0x02, .type = DEVICE_SOIL_SENSOR, .name = "soil-monitor-1"}}; + +// OneWire Naming Configuration +#define MAX_ONEWIRE_DEVICES 8 + +static const onewire_naming_t ONEWIRE_NAMING[] = { + {.location = "tank", .name_prefix = "water-temp"}, + {.location = "ambient", .name_prefix = "air-temp"}, + {.location = "soil", .name_prefix = "soil-temp"}}; + +// Structure to store discovered OneWire devices +typedef struct { + uint8_t address[8]; // OneWire address + uint8_t location_index; // Index into ONEWIRE_NAMING array + char name[32]; // Generated name (e.g., "tank-water-temp-1") + uint8_t sequence; // Sequence number within its location +} onewire_device_t; #endif // CONFIG_H diff --git a/include/modbus_handler.h b/include/modbus_handler.h new file mode 100644 index 0000000..83a66ac --- /dev/null +++ b/include/modbus_handler.h @@ -0,0 +1,17 @@ +#ifndef MODBUS_HANDLER_H +#define MODBUS_HANDLER_H + +#include + +#include "config.h" +#include "modbus_master.h" + +typedef void (*modbus_value_cb)(uint8_t device_idx, const char* property, + uint16_t value); + +void modbus_handler_init(modbus_context_t* ctx, modbus_value_cb value_callback); +void modbus_handler_process(void); +uint8_t modbus_handler_send_request(uint8_t device_idx, const char* property, + uint8_t is_write, uint16_t value); + +#endif \ No newline at end of file diff --git a/include/modbus_master.h b/include/modbus_master.h new file mode 100644 index 0000000..acea880 --- /dev/null +++ b/include/modbus_master.h @@ -0,0 +1,56 @@ +#ifndef __MODBUS_MASTER_H +#define __MODBUS_MASTER_H + +#include + +// Function codes +#define MODBUS_FC_READ_HOLDING_REGISTERS 0x03 +#define MODBUS_FC_WRITE_SINGLE_REGISTER 0x06 +#define MODBUS_FC_WRITE_MULTIPLE_REGISTERS 0x10 + +// Error codes +#define MODBUS_ERROR_NONE 0x00 +#define MODBUS_ERROR_FUNCTION 0x01 +#define MODBUS_ERROR_ADDRESS 0x02 +#define MODBUS_ERROR_VALUE 0x03 +#define MODBUS_ERROR_TIMEOUT 0x04 + +// Frame length +#define MB_MIN_LEN 4 +#define MB_CRC_LEN 2 +#define MB_WREG_LEN 8 +#define MB_MAX_BUFFER 32 + +// State machine states +typedef enum { + MODBUS_IDLE, + MODBUS_WAITING_RESPONSE, + MODBUS_PROCESS_RESPONSE +} modbus_state_t; + +// Modbus context structure +typedef struct { + modbus_state_t state; + uint32_t last_send_time; + uint32_t response_timeout; + uint8_t buffer[MB_MAX_BUFFER]; + uint16_t rx_len; + uint16_t current_bit; + uint16_t last_value; + void (*on_response)(uint8_t* buf, uint16_t len, + uint16_t value); // Response callback + void (*on_error)(uint8_t error_code); // Error callback +} modbus_context_t; + +uint16_t modbus_create_request(uint8_t* req, uint8_t slave_addr, + uint8_t function, uint16_t address, + uint16_t value); +uint8_t modbus_process_response(uint8_t* buf, uint16_t len, uint16_t* value); +void modbus_init(modbus_context_t* ctx, + void (*response_callback)(uint8_t*, uint16_t, uint16_t), + void (*error_callback)(uint8_t)); +void modbus_set_timeout(modbus_context_t* ctx, uint32_t timeout_ms); +void modbus_process(modbus_context_t* ctx); +uint8_t modbus_send_request(modbus_context_t* ctx, uint8_t slave_addr, + uint8_t function, uint16_t address, uint16_t value); +#endif // __MODBUS_MASTER_H diff --git a/include/mqtt_handler.h b/include/mqtt_handler.h new file mode 100644 index 0000000..993409c --- /dev/null +++ b/include/mqtt_handler.h @@ -0,0 +1,25 @@ +#ifndef MQTT_HANDLER_H +#define MQTT_HANDLER_H + +#include + +#include "ch32v003fun.h" +#include "w5500.h" + +typedef struct { + Network network; + MQTTClient client; + ch32_mqtt_options_t opts; + uint32_t last_reconnect; + uint32_t last_yield; + uint8_t is_connected; + char base_topic[64]; +} mqtt_state_t; + +void mqtt_init(mqtt_state_t* state); +void mqtt_process(mqtt_state_t* state); +void message_arrived(MessageData* md); +void publish_value(MQTTClient* client, const char* device_name, + const char* property, uint16_t value); + +#endif \ No newline at end of file diff --git a/include/rs485.h b/include/rs485.h new file mode 100644 index 0000000..e962bdb --- /dev/null +++ b/include/rs485.h @@ -0,0 +1,11 @@ +#ifndef RS485_H +#define RS485_H + +#include + +void rs485_init(int uart_brr); +void rs485_send(uint8_t *buf, uint16_t len); +uint8_t rs485_available(void); +uint8_t rs485_read(void); + +#endif // RS485_H diff --git a/include/system_init.h b/include/system_init.h new file mode 100644 index 0000000..6da8f3c --- /dev/null +++ b/include/system_init.h @@ -0,0 +1,9 @@ +#ifndef SYSTEM_INIT_H +#define SYSTEM_INIT_H + +#define W5500_INIT_DELAY_MS 55 + +void init_system(void); +int wait_for_dhcp(void); + +#endif diff --git a/include/uart.h b/include/uart.h index 58de5a5..c69c9a7 100644 --- a/include/uart.h +++ b/include/uart.h @@ -3,13 +3,15 @@ #include +#define UART1_BAUD_RATE 9600 + // Macro definitions #define APB1_CLOCK (FUNCONF_SYSTEM_CORE_CLOCK / 2) // APB1 is divided by 2 #define UART_BRR_APB1 \ (((APB1_CLOCK) + (UART_BAUD_RATE / 2)) / (UART_BAUD_RATE)) // USART2 #define UART_BRR_APB2 \ - (((FUNCONF_SYSTEM_CORE_CLOCK) + (UART_BAUD_RATE / 2)) / \ - (UART_BAUD_RATE)) // USART1 + (((FUNCONF_SYSTEM_CORE_CLOCK) + (UART1_BAUD_RATE / 2)) / \ + (UART1_BAUD_RATE)) // USART1 // Function prototypes void init_uart(int uart_brr); diff --git a/include/w5500.h b/include/w5500.h index 9f6d344..37b36ee 100644 --- a/include/w5500.h +++ b/include/w5500.h @@ -19,8 +19,6 @@ typedef struct { extern volatile int ip_assigned; -void handle_ip_assigned(void); - // Initializes the W5500 chip void configure_network(void); @@ -29,17 +27,6 @@ void dhcp_init(void); void dhcp_process(void); // resolves a domain name -void resolve_domain_name(const char* domain_name); - -// init and connect the MQTT client -int setup_mqtt_client(Network* network, ch32_mqtt_options_t* opts, - MQTTClient* client); - -int subscribe_to_topic(MQTTClient* client, const char* topic, enum QoS qos, - messageHandler message_callback); - -// publish a message to a topic -void publish_message(MQTTClient* client, const char* payload, - const char* topic); +// void resolve_domain_name(const char* domain_name); #endif // W5500_H diff --git a/lib/ioLibrary_Driver/DHCP/dhcp.h b/lib/ioLibrary_Driver/DHCP/dhcp.h index 658703c..f5c36f6 100644 --- a/lib/ioLibrary_Driver/DHCP/dhcp.h +++ b/lib/ioLibrary_Driver/DHCP/dhcp.h @@ -57,7 +57,7 @@ extern "C" { * @details If you want to display debug & processing message, Define _DHCP_DEBUG_ * @note If defined, it depends on */ -#define _DHCP_DEBUG_ +// #define _DHCP_DEBUG_ /* Retry to processing DHCP */ diff --git a/lib/ioLibrary_Driver/MQTT/MQTTClient.c b/lib/ioLibrary_Driver/MQTT/MQTTClient.c index 6580aa1..5fdfd39 100644 --- a/lib/ioLibrary_Driver/MQTT/MQTTClient.c +++ b/lib/ioLibrary_Driver/MQTT/MQTTClient.c @@ -14,6 +14,7 @@ * Allan Stockdill-Mander/Ian Craggs - initial API and implementation and/or initial documentation *******************************************************************************/ #include "MQTTClient.h" +#include static void NewMessageData(MessageData* md, MQTTString* aTopicName, MQTTMessage* aMessage) { md->topicName = aTopicName; @@ -56,7 +57,8 @@ void MQTTClientInit(MQTTClient* c, Network* network, unsigned int command_timeou c->ipstack = network; for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i) - c->messageHandlers[i].topicFilter = 0; + c->messageHandlers[i].topicFilter[0] = '\0'; + c->messageHandlers[i].fp = NULL; c->command_timeout_ms = command_timeout_ms; c->buf = sendbuf; c->buf_size = sendbuf_size; @@ -108,12 +110,12 @@ static int readPacket(MQTTClient* c, Timer* timer) int len = 0; int rem_len = 0; - /* 1. read the header byte. This has the packet type in it */ + /* 1. read the header byte. This has the packet type in it */ if (c->ipstack->mqttread(c->ipstack, c->readbuf, 1, TimerLeftMS(timer)) != 1) goto exit; len = 1; - /* 2. read the remaining length. This is variable in itself */ + /* 2. read the remaining length. This is variable in itself */ decodePacket(c, &rem_len, TimerLeftMS(timer)); len += MQTTPacket_encode(c->readbuf + 1, rem_len); /* put the original remaining length back into the buffer */ @@ -167,8 +169,8 @@ int deliverMessage(MQTTClient* c, MQTTString* topicName, MQTTMessage* message) // we have to find the right message handler - indexed by topic for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i) { - if (c->messageHandlers[i].topicFilter != 0 && (MQTTPacket_equals(topicName, (char*)c->messageHandlers[i].topicFilter) || - isTopicMatched((char*)c->messageHandlers[i].topicFilter, topicName))) + if (c->messageHandlers[i].topicFilter[0] != '\0' && (MQTTPacket_equals(topicName, c->messageHandlers[i].topicFilter) || + isTopicMatched(c->messageHandlers[i].topicFilter, topicName))) { if (c->messageHandlers[i].fp != NULL) { @@ -194,11 +196,14 @@ int deliverMessage(MQTTClient* c, MQTTString* topicName, MQTTMessage* message) int keepalive(MQTTClient* c) { - int rc = FAILURE; + int rc = SUCCESSS; if (c->keepAliveInterval == 0) + goto exit; + + if (!c->isconnected) { - rc = SUCCESSS; + rc = FAILURE; goto exit; } @@ -209,9 +214,24 @@ int keepalive(MQTTClient* c) Timer timer; TimerInit(&timer); TimerCountdownMS(&timer, 1000); + int len = MQTTSerialize_pingreq(c->buf, c->buf_size); - if (len > 0 && (rc = sendPacket(c, len, &timer)) == SUCCESSS) // send the ping packet + if (len <= 0) + { + rc = FAILURE; + goto exit; + } + + rc = sendPacket(c, len, &timer); + if (rc == SUCCESSS) + { c->ping_outstanding = 1; + TimerCountdown(&c->ping_timer, c->keepAliveInterval); + } + } + else + { + rc = FAILURE; } } @@ -436,9 +456,10 @@ int MQTTSubscribe(MQTTClient* c, const char* topicFilter, enum QoS qos, messageH int i; for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i) { - if (c->messageHandlers[i].topicFilter == 0) + if (c->messageHandlers[i].topicFilter[0] == '\0') { - c->messageHandlers[i].topicFilter = topicFilter; + strncpy(c->messageHandlers[i].topicFilter, topicFilter, MAX_TOPIC_LENGTH - 1); + c->messageHandlers[i].topicFilter[MAX_TOPIC_LENGTH - 1] = '\0'; c->messageHandlers[i].fp = messageHandler; rc = 0; break; diff --git a/lib/ioLibrary_Driver/MQTT/MQTTClient.h b/lib/ioLibrary_Driver/MQTT/MQTTClient.h index 47ea415..dfc372c 100644 --- a/lib/ioLibrary_Driver/MQTT/MQTTClient.h +++ b/lib/ioLibrary_Driver/MQTT/MQTTClient.h @@ -43,6 +43,8 @@ #define MAX_MESSAGE_HANDLERS 5 /* redefinable - how many subscriptions do you want? */ #endif +#define MAX_TOPIC_LENGTH 64 + enum QoS { QOS0, QOS1, QOS2 }; /* all failure return codes must be negative */ @@ -97,7 +99,7 @@ typedef struct MQTTClient struct MessageHandlers { - const char* topicFilter; + char topicFilter[MAX_TOPIC_LENGTH]; void (*fp) (MessageData*); } messageHandlers[MAX_MESSAGE_HANDLERS]; /* Message handlers are indexed by subscription topic */ diff --git a/src/main.c b/src/main.c index aed7c6c..35b7016 100644 --- a/src/main.c +++ b/src/main.c @@ -1,151 +1,45 @@ -#include - #include "ch32v003fun.h" +#include "config.h" #include "debug.h" #include "dhcp.h" -#include "gpio.h" -#include "socket.h" -#include "spi_dma.h" -#include "systick.h" -#include "timer.h" -#include "uart.h" +#include "modbus_handler.h" +#include "mqtt_handler.h" +#include "system_init.h" #include "w5500.h" -#define MQTT_RECONNECT_INTERVAL 5000 -#define MQTT_YIELD_INTERVAL 100 -#define W5500_INIT_DELAY_MS 55 -#define DHCP_TIMEOUT_MS 15000 +static mqtt_state_t mqtt_state; +static modbus_context_t modbus_ctx; -typedef struct { - Network network; - MQTTClient client; - ch32_mqtt_options_t opts; - uint32_t last_reconnect; - uint32_t last_yield; - uint8_t is_connected; -} mqtt_state_t; - -// init MQTT state -void mqtt_init(mqtt_state_t* state, char* client_id) { - // TODO - state->opts.clientid = client_id; - state->opts.username = ""; - state->opts.password = ""; - state->opts.qos = QOS0; - state->last_reconnect = 0; - state->last_yield = 0; - state->is_connected = 0; -} - -// cb fn for when a message is received -void message_arrived(MessageData* md) { - if (!md || !md->message) { - DEBUG_PRINT("Error: MessageData is NULL.\n"); - return; +// Callback for modbus values +static void on_modbus_value(uint8_t device_idx, const char* property, + uint16_t value) { + if (mqtt_state.is_connected) { + publish_value(&mqtt_state.client, RS485_DEVICES[device_idx].name, property, + value); } - - MQTTMessage* message = md->message; - DEBUG_PRINT("MQTT Message Arrived:\n"); - DEBUG_PRINT("QoS: %d\n", message->qos); - DEBUG_PRINT("Retained: %d\n", message->retained); - DEBUG_PRINT("Duplicate: %d\n", message->dup); - DEBUG_PRINT("Message ID: %u\n", message->id); - DEBUG_PRINT("Payload Length: %u\n", (unsigned int)message->payloadlen); - - if (message->payload != NULL && message->payloadlen > 0) { - DEBUG_PRINT("Payload: "); - for (unsigned int i = 0; i < (unsigned int)message->payloadlen; ++i) { - putchar(((unsigned char*)message->payload)[i]); - } - DEBUG_PRINT("\n"); - } else { - DEBUG_PRINT("Payload: \n"); - } -} - -// mqtt reconnect -// TODO: setup will, publish discovery messages, setup subs -void mqtt_process(mqtt_state_t* state) { - uint32_t now = millis(); - int rc; - - if (!state->is_connected) { - if (now - state->last_reconnect >= MQTT_RECONNECT_INTERVAL) { - DEBUG_PRINT("Attempting MQTT connection...\n"); - - rc = setup_mqtt_client(&state->network, &state->opts, &state->client); - if (rc == 0) { - DEBUG_PRINT("MQTT connected\n"); - state->is_connected = 1; - - rc = subscribe_to_topic(&state->client, SUB_TOPIC, QOS0, - message_arrived); - if (rc != 0) { - DEBUG_PRINT("Subscribe failed: %d\n", rc); - state->is_connected = 0; - } else { - publish_message(&state->client, "Device connected", PUB_TOPIC); - } - } - state->last_reconnect = now; - } - } else if (now - state->last_yield >= MQTT_YIELD_INTERVAL) { - rc = MQTTYield(&state->client, 100); - if (rc != 0) { - DEBUG_PRINT("Yield failed: %d\n", rc); - state->is_connected = 0; - } - state->last_yield = now; - } -} - -void init_system(void) { - SystemInit(); - - init_gpio(); - init_systick(); - tim2_init(); - - init_uart(UART_BRR_APB1); - init_spidma(); -} - -// Wait for DHCP lease -static int wait_for_dhcp(void) { - uint32_t start = millis(); - while (dhcp_get_state() != DHCP_STATE_LEASED) { - if (millis() - start >= DHCP_TIMEOUT_MS) { - DEBUG_PRINT("DHCP timeout\n"); - return 0; - } - dhcp_process(); - } - return 1; } int main(void) { init_system(); Delay_Ms(W5500_INIT_DELAY_MS); - // Configure network and DHCP configure_network(); dhcp_init(); if (!wait_for_dhcp()) { while (1) { - // todo: handle err + // TODO: Implement proper error handling } } - resolve_domain_name("example.com"); - - // mqtt - mqtt_state_t mqtt; - mqtt_init(&mqtt, CLIENT_ID); + // init handlers + mqtt_init(&mqtt_state); + modbus_handler_init(&modbus_ctx, on_modbus_value); while (1) { dhcp_process(); - mqtt_process(&mqtt); + mqtt_process(&mqtt_state); + modbus_handler_process(); } return 0; diff --git a/src/modbus_handler.c b/src/modbus_handler.c new file mode 100644 index 0000000..169bc1b --- /dev/null +++ b/src/modbus_handler.c @@ -0,0 +1,79 @@ +#include "modbus_handler.h" + +#include + +#include "debug.h" +#include "systick.h" + +static struct { + modbus_context_t* ctx; + modbus_value_cb value_cb; + uint8_t last_device_idx; + char last_property[16]; +} handler; + +static void on_modbus_response(uint8_t* buf, uint16_t len, uint16_t value) { + if (handler.last_device_idx >= RS485_DEVICE_COUNT || !handler.value_cb) { + return; + } + + // call the value callback with parsed data + handler.value_cb(handler.last_device_idx, handler.last_property, value); + + // clear the context + handler.last_device_idx = 0xFF; + handler.last_property[0] = '\0'; +} + +static void on_modbus_error(uint8_t error_code) { + // clear the context on error + handler.last_device_idx = 0xFF; + handler.last_property[0] = '\0'; +} + +void modbus_handler_init(modbus_context_t* ctx, + modbus_value_cb value_callback) { + handler.ctx = ctx; + handler.value_cb = value_callback; + handler.last_device_idx = 0xFF; + handler.last_property[0] = '\0'; + + // Initialize modbus with our callbacks + modbus_init(ctx, on_modbus_response, on_modbus_error); + modbus_set_timeout(ctx, 200); +} + +void modbus_handler_process(void) { modbus_process(handler.ctx); } + +// Get Modbus register for specific device type and property +static uint16_t get_register_address(device_type_t type, const char* property) { + if (type == DEVICE_RELAY && strcmp(property, "state") == 0) { + return 0x0000; + } + + return 0xFFFF; +} +uint8_t modbus_handler_send_request(uint8_t device_idx, const char* property, + uint8_t is_write, uint16_t value) { + if (device_idx >= RS485_DEVICE_COUNT) { + return 0; + } + + uint16_t reg = get_register_address(RS485_DEVICES[device_idx].type, property); + if (reg == 0xFFFF) { + return 0; + } + + uint8_t fc = is_write ? MODBUS_FC_WRITE_SINGLE_REGISTER + : MODBUS_FC_READ_HOLDING_REGISTERS; + + if (modbus_send_request(handler.ctx, RS485_DEVICES[device_idx].slave_id, fc, + reg, value)) { + handler.last_device_idx = device_idx; + strncpy(handler.last_property, property, sizeof(handler.last_property) - 1); + handler.last_property[sizeof(handler.last_property) - 1] = '\0'; + return 1; + } + + return 0; +} \ No newline at end of file diff --git a/src/modbus_master.c b/src/modbus_master.c new file mode 100644 index 0000000..b28bdcb --- /dev/null +++ b/src/modbus_master.c @@ -0,0 +1,247 @@ +#include "modbus_master.h" + +#include "debug.h" +#include "rs485.h" +#include "systick.h" + +static uint16_t modbus_crc16(const unsigned char* buf, unsigned int len) { + static const uint16_t table[256] = { + 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, 0xC601, + 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440, 0xCC01, 0x0CC0, + 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, 0x0A00, 0xCAC1, 0xCB81, + 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841, 0xD801, 0x18C0, 0x1980, 0xD941, + 0x1B00, 0xDBC1, 0xDA81, 0x1A40, 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, + 0x1DC0, 0x1C80, 0xDC41, 0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, + 0x1680, 0xD641, 0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, + 0x1040, 0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240, + 0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441, 0x3C00, + 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41, 0xFA01, 0x3AC0, + 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840, 0x2800, 0xE8C1, 0xE981, + 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41, 0xEE01, 0x2EC0, 0x2F80, 0xEF41, + 0x2D00, 0xEDC1, 0xEC81, 0x2C40, 0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, + 0xE7C1, 0xE681, 0x2640, 0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, + 0x2080, 0xE041, 0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, + 0x6240, 0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441, + 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41, 0xAA01, + 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840, 0x7800, 0xB8C1, + 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41, 0xBE01, 0x7EC0, 0x7F80, + 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40, 0xB401, 0x74C0, 0x7580, 0xB541, + 0x7700, 0xB7C1, 0xB681, 0x7640, 0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, + 0x71C0, 0x7080, 0xB041, 0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, + 0x5280, 0x9241, 0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, + 0x5440, 0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40, + 0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841, 0x8801, + 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40, 0x4E00, 0x8EC1, + 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41, 0x4400, 0x84C1, 0x8581, + 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641, 0x8201, 0x42C0, 0x4380, 0x8341, + 0x4100, 0x81C1, 0x8081, 0x4040}; + + uint8_t xor = 0; + uint16_t crc = 0xFFFF; + + while (len--) { + xor = (*buf++) ^ crc; + crc >>= 8; + crc ^= table[xor]; + } + + return crc; +} + +static inline uint16_t mb_word(uint8_t hi, uint8_t lo) { + return ((uint16_t)hi << 8) | lo; +} + +static inline void mb_split(uint16_t val, uint8_t* hi, uint8_t* lo) { + *hi = (val >> 8) & 0xFF; + *lo = val & 0xFF; +} + +uint16_t modbus_create_request(uint8_t* req, uint8_t slave_addr, + uint8_t function, uint16_t address, + uint16_t value) { + req[0] = slave_addr; + req[1] = function; + + uint8_t hi, lo; + mb_split(address, &hi, &lo); + req[2] = hi; + req[3] = lo; + + if (function == MODBUS_FC_WRITE_SINGLE_REGISTER) { + mb_split(value, &hi, &lo); + req[4] = hi; + req[5] = lo; + + // add crc + uint16_t crc = modbus_crc16(req, 6); + req[6] = crc & 0xFF; + req[7] = (crc >> 8) & 0xFF; + return 8; + } else if (function == MODBUS_FC_READ_HOLDING_REGISTERS) { + mb_split(value, &hi, &lo); // value = number of registers to read + req[4] = hi; + req[5] = lo; + + // add crc + uint16_t crc = modbus_crc16(req, 6); + req[6] = crc & 0xFF; + req[7] = (crc >> 8) & 0xFF; + return 8; + } + + return 0; +} + +uint8_t modbus_process_response(uint8_t* buf, uint16_t len, uint16_t* value) { + if (len < MB_MIN_LEN) { + return MODBUS_ERROR_VALUE; + } + + // check for error response + if (buf[1] & 0x80) { + return buf[2]; + } + + // verify crc + uint16_t rcv_crc = mb_word(buf[len - 1], buf[len - 2]); + uint16_t calc_crc = modbus_crc16(buf, len - 2); + + if (rcv_crc != calc_crc) { + return MODBUS_ERROR_VALUE; + } + + switch (buf[1]) { + case MODBUS_FC_WRITE_SINGLE_REGISTER: + if (len != MB_WREG_LEN) { + return MODBUS_ERROR_VALUE; + } + *value = mb_word(buf[4], buf[5]); + break; + + case MODBUS_FC_READ_HOLDING_REGISTERS: + if (len < 5) { + return MODBUS_ERROR_VALUE; + } + uint8_t byte_count = buf[2]; + for (uint8_t i = 0; i < byte_count / 2; i++) { + value[i] = mb_word(buf[3 + i * 2], buf[4 + i * 2]); + } + break; + + default: + return MODBUS_ERROR_FUNCTION; + } + + return MODBUS_ERROR_NONE; +} + +void modbus_init(modbus_context_t* ctx, + void (*response_callback)(uint8_t*, uint16_t, uint16_t), + void (*error_callback)(uint8_t)) { + while (rs485_available()) { + uint8_t byte = rs485_read(); + DEBUG_PRINT("Flushed stale byte: 0x%02X\n", byte); + } + + ctx->state = MODBUS_IDLE; + ctx->last_send_time = 0; + ctx->response_timeout = 100; // Default 100ms timeout + ctx->rx_len = 0; + ctx->current_bit = 1; + ctx->last_value = 0; + ctx->on_response = response_callback; + ctx->on_error = error_callback; +} + +void modbus_set_timeout(modbus_context_t* ctx, uint32_t timeout_ms) { + ctx->response_timeout = timeout_ms; +} + +uint8_t modbus_send_request(modbus_context_t* ctx, uint8_t slave_addr, + uint8_t function, uint16_t address, + uint16_t value) { + if (ctx->state != MODBUS_IDLE) { + return 0; // Busy + } + + uint16_t len = + modbus_create_request(ctx->buffer, slave_addr, function, address, value); + rs485_send(ctx->buffer, len); + + ctx->last_send_time = millis(); + ctx->rx_len = 0; + ctx->state = MODBUS_WAITING_RESPONSE; + + return 1; // Success +} + +void modbus_process(modbus_context_t* ctx) { + uint32_t current_time = millis(); + + switch (ctx->state) { + case MODBUS_IDLE: + break; + + case MODBUS_WAITING_RESPONSE: + // Check for timeout + if (current_time - ctx->last_send_time >= ctx->response_timeout) { + DEBUG_PRINT("Timeout after %lu ms with %d bytes received", + current_time - ctx->last_send_time, ctx->rx_len); + if (ctx->on_error) { + ctx->on_error(MODBUS_ERROR_TIMEOUT); + } + ctx->state = MODBUS_IDLE; + break; + } + + // Read available data + while (rs485_available() && ctx->rx_len < sizeof(ctx->buffer)) { + ctx->buffer[ctx->rx_len++] = rs485_read(); + // DEBUG_PRINT("Received byte: 0x%02X\n", ctx->buffer[ctx->rx_len - 1]); + + if (ctx->rx_len >= MB_MIN_LEN) { + // holding reg + if (ctx->buffer[1] == MODBUS_FC_READ_HOLDING_REGISTERS) { + if (ctx->rx_len >= 3 && ctx->rx_len >= (3 + ctx->buffer[2] + 2)) { + ctx->state = MODBUS_PROCESS_RESPONSE; + break; + } + } + + // single reg w + else if (ctx->buffer[1] == MODBUS_FC_WRITE_SINGLE_REGISTER) { + if (ctx->rx_len >= MB_WREG_LEN) { + ctx->state = MODBUS_PROCESS_RESPONSE; + break; + } + } + + else if (ctx->buffer[1] & 0x80) { + if (ctx->rx_len >= 5) { // err 5b + ctx->state = MODBUS_PROCESS_RESPONSE; + break; + } + } + } + } + break; + + case MODBUS_PROCESS_RESPONSE: + uint16_t value; + uint8_t result = + modbus_process_response(ctx->buffer, ctx->rx_len, &value); + + if (result == MODBUS_ERROR_NONE) { + if (ctx->on_response) { + ctx->on_response(ctx->buffer, ctx->rx_len, value); + } + ctx->last_value = value; + } else if (ctx->on_error) { + ctx->on_error(result); + } + + ctx->state = MODBUS_IDLE; + break; + } +} \ No newline at end of file diff --git a/src/mqtt_handler.c b/src/mqtt_handler.c new file mode 100644 index 0000000..9c7b1f2 --- /dev/null +++ b/src/mqtt_handler.c @@ -0,0 +1,407 @@ +#include "mqtt_handler.h" + +#include +#include +#include + +#include "debug.h" +#include "modbus_handler.h" +#include "systick.h" + +// MQTT +#define MQTT_YIELD_INTERVAL 100 // 100ms between yields in main loop +#define MQTT_MAX_PACKET_WAIT 20 // Only wait up to 20ms for packet processing +#define MQTT_RECONNECT_INTERVAL 5000 // 5 seconds between reconnection attempts + +// Homie convention constants +#define HOMIE_VERSION "4.0" +#define HOMIE_STATE_READY "ready" +#define HOMIE_STATE_LOST "lost" +#define HOMIE_STATE_SLEEPING "sleeping" +#define HOMIE_STATE_DISCONNECTED "disconnected" +#define MAX_PAYLOAD_LENGTH 256 + +// TODO: +// Property definitions +typedef enum { + PROP_STATE, // for relay + PROP_MOISTURE, // for soil sensor + PROP_TEMPERATURE, // for soil sensor and thermometer +} device_property_t; + +// Parse Homie topic format: homie/node-id/device-name/property/[set|get] +// Returns: 1 if successful, 0 if invalid format or buffer overflow +static uint8_t parse_homie_topic(const char* topic, size_t topic_len, + char* device_name, size_t name_max, + char* property, size_t prop_max, + uint8_t* is_set) { + const char* segment_start = topic; + const char* topic_end = topic + topic_len; + uint8_t segment = 0; + + // Skip first three segments (homie/node-id/device-name/) + while (segment < 3 && segment_start < topic_end) { + const char* slash = memchr(segment_start, '/', topic_end - segment_start); + if (!slash) return 0; + + if (segment == 2) { + size_t len = slash - segment_start; + if (len >= name_max) return 0; + memcpy(device_name, segment_start, len); + device_name[len] = '\0'; + } + + segment_start = slash + 1; + segment++; + } + + const char* slash = memchr(segment_start, '/', topic_end - segment_start); + if (!slash) return 0; + + size_t len = slash - segment_start; + if (len >= prop_max) return 0; + memcpy(property, segment_start, len); + property[len] = '\0'; + + segment_start = slash + 1; + if (segment_start >= topic_end) return 0; // Missing set/get + + *is_set = (*segment_start == 's'); + + return 1; +} + +// MQTT client +int setup_mqtt_client(Network* network, ch32_mqtt_options_t* opts, + MQTTClient* client) { + static unsigned char tx_buffer[MQTT_TX_BUFFER_SIZE]; + static unsigned char rx_buffer[MQTT_RX_BUFFER_SIZE]; + int rc; + uint8_t target_ip[] = MQTT_SERVER_IP; + + // Initialize network + NewNetwork(network, TCP_SOCKET); + rc = ConnectNetwork(network, target_ip, MQTT_PORT); + if (rc != SOCK_OK) { + DEBUG_PRINT("Network connection failed: %d\n", rc); + return -1; + } + + // Initialize the MQTT client + MQTTClientInit(client, network, MQTT_COMMAND_TIMEOUT_MS, tx_buffer, + sizeof(tx_buffer), rx_buffer, sizeof(rx_buffer)); + + // Setup MQTT connection data + MQTTPacket_connectData connect_data = MQTTPacket_connectData_initializer; + + // Configure Last Will and Testament + char will_topic[MAX_TOPIC_LENGTH]; + snprintf(will_topic, sizeof(will_topic), "homie/%s/$state", NODE_CONFIG.id); + + connect_data.willFlag = 1; // Enable LWT + connect_data.will = + (MQTTPacket_willOptions)MQTTPacket_willOptions_initializer; + connect_data.will.topicName.cstring = will_topic; + connect_data.will.message.cstring = HOMIE_STATE_LOST; + connect_data.will.retained = 1; + connect_data.will.qos = QOS1; + + // rest + connect_data.MQTTVersion = 3; + connect_data.clientID.cstring = opts->clientid; + connect_data.username.cstring = opts->username; + connect_data.password.cstring = opts->password; + connect_data.keepAliveInterval = MQTT_KEEP_ALIVE_INTERVAL; + connect_data.cleansession = 1; + + // Connect to MQTT broker + rc = MQTTConnect(client, &connect_data); + if (rc != 0) { + DEBUG_PRINT("Failed to connect: %d\n", rc); + return -2; + } + + return 0; +} + +int subscribe_to_topic(MQTTClient* client, const char* topic, enum QoS qos, + messageHandler message_callback) { + int rc = MQTTSubscribe(client, topic, qos, message_callback); + if (rc != 0) { + DEBUG_PRINT("Failed to subscribe to %s: %d\n", topic, rc); + return rc; + } + return 0; +} + +void publish_message(MQTTClient* client, const char* payload, + const char* topic) { + MQTTMessage message = {.qos = QOS0, + .retained = 0, + .dup = 0, + .payload = (void*)payload, + .payloadlen = strlen(payload) + + }; + + if (MQTTPublish(client, topic, &message) != 0) { + DEBUG_PRINT("Publish failed\n"); + } else { + DEBUG_PRINT("Message published successfully\n"); + } +} + +// publish retained messages +static void publish_retained(MQTTClient* client, const char* topic, + const char* payload) { + MQTTMessage message = {.qos = QOS1, + .retained = 1, + .dup = 0, + .payload = (void*)payload, + .payloadlen = strlen(payload)}; + + if (MQTTPublish(client, topic, &message) != 0) { + DEBUG_PRINT("Failed to publish to %s\n", topic); + } +} + +// Send state update +void publish_value(MQTTClient* client, const char* device_name, + const char* property, uint16_t value) { + DEBUG_PRINT("publish_value(device_name=%s, property=%s, value=%u)\n", + device_name, property, value); + + char topic[MAX_TOPIC_LENGTH]; + char payload[17]; // 16 bits + null terminator + + snprintf(topic, sizeof(topic), "homie/%s/%s/%s", NODE_CONFIG.id, device_name, + property); + + // formta based on property type + if (strcmp(property, "state") == 0) { + for (int i = 15; i >= 0; i--) { + payload[15 - i] = '0' + ((value >> i) & 1); + } + payload[16] = '\0'; + } else { + // todo: + return; + } + + MQTTMessage message = {.qos = QOS1, + .retained = 1, + .payload = payload, + .payloadlen = strlen(payload)}; + + if (MQTTPublish(client, topic, &message) != 0) { + DEBUG_PRINT("Failed to publish to %s\n", topic); + } +} + +static void publish_device_attributes(MQTTClient* client) { + char topic[MAX_TOPIC_LENGTH]; + static char nodes_list[MAX_PAYLOAD_LENGTH]; + char* ptr = nodes_list; + size_t remaining = sizeof(nodes_list); + + // Device attributes + snprintf(topic, sizeof(topic), "homie/%s/$homie", NODE_CONFIG.id); + publish_retained(client, topic, HOMIE_VERSION); + + snprintf(topic, sizeof(topic), "homie/%s/$name", NODE_CONFIG.id); + publish_retained(client, topic, NODE_CONFIG.name); + + snprintf(topic, sizeof(topic), "homie/%s/$state", NODE_CONFIG.id); + publish_retained(client, topic, HOMIE_STATE_READY); + + ptr = nodes_list; + *ptr = '\0'; + for (int i = 0; i < RS485_DEVICE_COUNT; i++) { + if (i > 0 && remaining > 1) { + *ptr++ = ','; + remaining--; + } + size_t len = strlen(RS485_DEVICES[i].name); + if (len >= remaining) break; + memcpy(ptr, RS485_DEVICES[i].name, len); + ptr += len; + remaining -= len; + } + *ptr = '\0'; + + snprintf(topic, sizeof(topic), "homie/%s/$nodes", NODE_CONFIG.id); + publish_retained(client, topic, nodes_list); + + // loc attribute + if (NODE_CONFIG.location[0] != '\0') { + snprintf(topic, sizeof(topic), "homie/%s/$location", NODE_CONFIG.id); + publish_retained(client, topic, NODE_CONFIG.location); + } +} + +static void publish_node_attributes(MQTTClient* client, + const rs485_device_t* device) { + char topic[MAX_TOPIC_LENGTH]; + + // Node base attributes + snprintf(topic, sizeof(topic), "homie/%s/%s/$name", NODE_CONFIG.id, + device->name); + publish_retained(client, topic, device->name); + + // Set properties based on device type + switch (device->type) { + case DEVICE_RELAY: + snprintf(topic, sizeof(topic), "homie/%s/%s/$properties", NODE_CONFIG.id, + device->name); + publish_retained(client, topic, "state"); + + snprintf(topic, sizeof(topic), "homie/%s/%s/state/$name", NODE_CONFIG.id, + device->name); + publish_retained(client, topic, "State"); + + snprintf(topic, sizeof(topic), "homie/%s/%s/state/$datatype", + NODE_CONFIG.id, device->name); + publish_retained(client, topic, "boolean"); + + snprintf(topic, sizeof(topic), "homie/%s/%s/state/$settable", + NODE_CONFIG.id, device->name); + publish_retained(client, topic, "true"); + break; + + case DEVICE_SOIL_SENSOR: + snprintf(topic, sizeof(topic), "homie/%s/%s/$properties", NODE_CONFIG.id, + device->name); + publish_retained(client, topic, "moisture,temperature"); + + snprintf(topic, sizeof(topic), "homie/%s/%s/moisture/$name", + NODE_CONFIG.id, device->name); + publish_retained(client, topic, "Moisture Level"); + + snprintf(topic, sizeof(topic), "homie/%s/%s/moisture/$datatype", + NODE_CONFIG.id, device->name); + publish_retained(client, topic, "integer"); + + snprintf(topic, sizeof(topic), "homie/%s/%s/moisture/$unit", + NODE_CONFIG.id, device->name); + publish_retained(client, topic, "%"); + + // Temperature property + snprintf(topic, sizeof(topic), "homie/%s/%s/temperature/$name", + NODE_CONFIG.id, device->name); + publish_retained(client, topic, "Temperature"); + + snprintf(topic, sizeof(topic), "homie/%s/%s/temperature/$datatype", + NODE_CONFIG.id, device->name); + publish_retained(client, topic, "float"); + + snprintf(topic, sizeof(topic), "homie/%s/%s/temperature/$unit", + NODE_CONFIG.id, device->name); + publish_retained(client, topic, "°C"); + break; + case DEVICE_THERMOMETER: + // TODO + DEBUG_PRINT("not implemented\n"); + } +} + +// Initialize MQTT with Homie discovery +void mqtt_init(mqtt_state_t* state) { + state->opts.clientid = (char*)NODE_CONFIG.id; // Use node ID as client ID + state->opts.username = ""; + state->opts.password = ""; + state->opts.qos = QOS1; + state->last_reconnect = 0; + state->last_yield = 0; + state->is_connected = 0; +} + +// Find device by name and return its index +static int8_t find_device_index(const char* name) { + for (uint8_t i = 0; i < RS485_DEVICE_COUNT; i++) { + if (strcmp(RS485_DEVICES[i].name, name) == 0) { + return i; + } + } + return -1; +} + +void message_arrived(MessageData* md) { + if (!md || !md->message || !md->topicName) { + DEBUG_PRINT("Error: MessageData is NULL.\n"); + return; + } + + char device_name[16]; + char property[16]; + uint8_t is_set; + + if (!parse_homie_topic(md->topicName->lenstring.data, + md->topicName->lenstring.len, device_name, + sizeof(device_name), property, sizeof(property), + &is_set)) { + DEBUG_PRINT("Failed to parse topic\n"); + return; + } + + // For set operations, parse value + if (is_set && md->message->payloadlen > 0) { + uint16_t value = 0; + int8_t idx = find_device_index(device_name); + if (idx < 0) return; + + if (RS485_DEVICES[idx].type == DEVICE_RELAY) { + if (md->message->payloadlen != 16) return; + char* str = (char*)md->message->payload; + for (int i = 0; i < 16; i++) { + value = (value << 1) | (str[i] - '0'); + } + } else { + // todo: + DEBUG_PRINT("not implemented\n"); + } + + modbus_handler_send_request(idx, property, is_set, value); + } +} + +void mqtt_process(mqtt_state_t* state) { + uint32_t now = millis(); + int rc; + static uint8_t discovery_published = 0; + + if (!state->is_connected) { + if (now - state->last_reconnect >= MQTT_RECONNECT_INTERVAL) { + rc = setup_mqtt_client(&state->network, &state->opts, &state->client); + + if (rc == 0) { + state->is_connected = 1; + + if (!discovery_published) { + publish_device_attributes(&state->client); + + for (int i = 0; i < RS485_DEVICE_COUNT; i++) { + publish_node_attributes(&state->client, &RS485_DEVICES[i]); + } + discovery_published = 1; + } + + char sub_topic[MAX_TOPIC_LENGTH]; + snprintf(sub_topic, sizeof(sub_topic), "homie/%s/+/+/set", + NODE_CONFIG.id); + + rc = subscribe_to_topic(&state->client, sub_topic, QOS1, + message_arrived); + if (rc != 0) { + state->is_connected = 0; + } + } + state->last_reconnect = now; + } + } else if (now - state->last_yield >= MQTT_YIELD_INTERVAL) { + rc = MQTTYield(&state->client, MQTT_MAX_PACKET_WAIT); + if (rc != 0) { + state->is_connected = 0; + } + state->last_yield = now; + } +} \ No newline at end of file diff --git a/src/rs485.c b/src/rs485.c new file mode 100644 index 0000000..7a78b34 --- /dev/null +++ b/src/rs485.c @@ -0,0 +1,60 @@ +#include "rs485.h" + +#include "ch32v003fun.h" + +#define RS485_TX_EN (1 << 14) // PB14 +#define RS485_RX_EN (1 << 15) // PB15 + +void rs485_init(int uart_brr) { + RCC->APB2PCENR |= RCC_APB2Periph_GPIOA | // Enable GPIOA + RCC_APB2Periph_GPIOB | // Enable GPIOB + RCC_APB2Periph_USART1; // Enable USART1 + + // configure uart1 pins (PA9=TX, PA10=RX) + GPIOA->CFGHR &= + ~(0xf << (4 * 1) | 0xf << (4 * 2)); // Clear PA9 & PA10 config + GPIOA->CFGHR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP_AF) + << (4 * 1); // PA9 (TX) + GPIOA->CFGHR |= GPIO_CNF_IN_FLOATING << (4 * 2); // PA10 (RX) + + // configure rs485 direction control pins (PB14=TX_EN, PB15=RX_EN) + GPIOB->CFGHR &= + ~(0xf << (4 * 6) | 0xf << (4 * 7)); // Clear PB14 & PB15 config + GPIOB->CFGHR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP) + << (4 * 6); // PB14 (TX_EN) + GPIOB->CFGHR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP) + << (4 * 7); // PB15 (RX_EN) + + // initial state: rx mode + GPIOB->BCR = RS485_TX_EN; // TX_EN low + GPIOB->BSHR = RS485_RX_EN; // RX_EN high + + // uart1 configuration + USART1->CTLR1 = + USART_WordLength_8b | USART_Parity_No | USART_Mode_Tx | USART_Mode_Rx; + USART1->CTLR2 = USART_StopBits_1; + USART1->CTLR3 = USART_HardwareFlowControl_None; + USART1->BRR = uart_brr; + USART1->CTLR1 |= CTLR1_UE_Set; +} + +void rs485_send(uint8_t *buf, uint16_t len) { + // enable tx mode + GPIOB->BSHR = RS485_TX_EN; // TX_EN high + GPIOB->BSHR = RS485_RX_EN; // RX_EN low + + for (uint16_t i = 0; i < len; i++) { + while (!(USART1->STATR & USART_FLAG_TXE)); + USART1->DATAR = buf[i]; + } + + while (!(USART1->STATR & USART_FLAG_TC)); + + // switch back to rx mode + GPIOB->BCR = RS485_TX_EN; // TX_EN low + GPIOB->BCR = RS485_RX_EN; // RX_EN high +} + +uint8_t rs485_available(void) { return (USART1->STATR & USART_FLAG_RXNE) != 0; } + +uint8_t rs485_read(void) { return USART1->DATAR & 0xFF; } diff --git a/src/system_init.c b/src/system_init.c new file mode 100644 index 0000000..8190ee9 --- /dev/null +++ b/src/system_init.c @@ -0,0 +1,35 @@ +#include "system_init.h" + +#include "ch32v003fun.h" +#include "debug.h" +#include "dhcp.h" +#include "gpio.h" +#include "rs485.h" +#include "spi_dma.h" +#include "systick.h" +#include "timer.h" +#include "uart.h" + +#define DHCP_TIMEOUT_MS 15000 + +void init_system(void) { + SystemInit(); + init_gpio(); + init_systick(); + tim2_init(); + init_spidma(); + init_uart(UART_BRR_APB1); + rs485_init(UART_BRR_APB2); +} + +int wait_for_dhcp(void) { + uint32_t start = millis(); + while (dhcp_get_state() != DHCP_STATE_LEASED) { + if (millis() - start >= DHCP_TIMEOUT_MS) { + DEBUG_PRINT("DHCP timeout\n"); + return 0; + } + dhcp_process(); + } + return 1; +} diff --git a/src/w5500.c b/src/w5500.c index ea7b501..9d34304 100644 --- a/src/w5500.c +++ b/src/w5500.c @@ -1,10 +1,6 @@ #include "w5500.h" -#include -#include -#include #include -#include #include #include "ch32v003fun.h" @@ -13,8 +9,6 @@ #include "spi_dma.h" #include "systick.h" -static uint8_t dns_buffer[512]; - void configure_network(void) { DEBUG_PRINT("===\n"); DEBUG_PRINT("Starting network configuration...\n"); @@ -29,93 +23,29 @@ void configure_network(void) { wizchip_init(rx_tx_buff_sizes, rx_tx_buff_sizes); } +// static uint8_t dns_buffer[512]; + // todo: rm !!! -void resolve_domain_name(const char* domain_name) { - DEBUG_PRINT("Resolving domain name \"%s\"...\n", domain_name); +// void resolve_domain_name(const char* domain_name) { +// DEBUG_PRINT("Resolving domain name \"%s\"...\n", domain_name); - DNS_init(DNS_SOCKET, dns_buffer); - // cloudflare dns - uint8_t dns[] = {1, 1, 1, 1}; - uint8_t addr[4]; - int8_t res; - uint8_t retries = 0; +// DNS_init(DNS_SOCKET, dns_buffer); +// // cloudflare dns +// uint8_t dns[] = {1, 1, 1, 1}; +// uint8_t addr[4]; +// int8_t res; +// uint8_t retries = 0; - while (retries < 3) { - Delay_Ms(250); +// while (retries < 3) { +// Delay_Ms(250); - res = DNS_run(dns, (uint8_t*)domain_name, addr); - if (res == 1) { - DEBUG_PRINT("Result: %d.%d.%d.%d\n", addr[0], addr[1], addr[2], addr[3]); - break; - } else { - DEBUG_PRINT("DNS_run() failed, res = %d. Retries: %u\n", res, retries); - } - retries++; - } -} - -int setup_mqtt_client(Network* network, ch32_mqtt_options_t* opts, - MQTTClient* client) { - static unsigned char tx_buffer[MQTT_TX_BUFFER_SIZE]; - static unsigned char rx_buffer[MQTT_RX_BUFFER_SIZE]; - int rc; - uint8_t target_ip[] = MQTT_TARGET_IP; - - // Initialize network - NewNetwork(network, TCP_SOCKET); - rc = ConnectNetwork(network, target_ip, MQTT_TARGET_PORT); - if (rc != SOCK_OK) { - DEBUG_PRINT("Network connection failed: %d\n", rc); - return -1; - } - - // Initialize the MQTT client - MQTTClientInit(client, network, MQTT_COMMAND_TIMEOUT_MS, tx_buffer, - sizeof(tx_buffer), rx_buffer, sizeof(rx_buffer)); - - // Setup MQTT connection data - MQTTPacket_connectData connect_data = MQTTPacket_connectData_initializer; - connect_data.willFlag = 0; - connect_data.MQTTVersion = 3; - connect_data.clientID.cstring = opts->clientid; - connect_data.username.cstring = opts->username; - connect_data.password.cstring = opts->password; - connect_data.keepAliveInterval = MQTT_KEEP_ALIVE_INTERVAL; - connect_data.cleansession = 1; - - // Connect to MQTT broker - rc = MQTTConnect(client, &connect_data); - if (rc != 0) { - DEBUG_PRINT("Failed to connect: %d\n", rc); - return -2; - } - - return 0; -} - -int subscribe_to_topic(MQTTClient* client, const char* topic, enum QoS qos, - messageHandler message_callback) { - int rc = MQTTSubscribe(client, topic, qos, message_callback); - if (rc != 0) { - DEBUG_PRINT("Failed to subscribe to %s: %d\n", topic, rc); - return rc; - } - return 0; -} - -void publish_message(MQTTClient* client, const char* payload, - const char* topic) { - MQTTMessage message = {.qos = QOS0, - .retained = 0, - .dup = 0, - .payload = (void*)payload, - .payloadlen = strlen(payload) - - }; - - if (MQTTPublish(client, topic, &message) != 0) { - DEBUG_PRINT("Publish failed\n"); - } else { - DEBUG_PRINT("Message published successfully\n"); - } -} \ No newline at end of file +// res = DNS_run(dns, (uint8_t*)domain_name, addr); +// if (res == 1) { +// DEBUG_PRINT("Result: %d.%d.%d.%d\n", addr[0], addr[1], addr[2], +// addr[3]); break; +// } else { +// DEBUG_PRINT("DNS_run() failed, res = %d. Retries: %u\n", res, retries); +// } +// retries++; +// } +// }