diff --git a/.vscode/settings.json b/.vscode/settings.json index bf83ac5..822f36d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -25,6 +25,7 @@ "system_init.h": "c", "mqtt_handler.h": "c", "cstdlib": "c", - "modbus_master.h": "c" + "modbus_master.h": "c", + "ch32v003_gpio_branchless.h": "c" } } \ No newline at end of file diff --git a/include/config.h b/include/config.h index 3768cb2..7da6d67 100644 --- a/include/config.h +++ b/include/config.h @@ -30,12 +30,6 @@ typedef struct { 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 @@ -51,26 +45,10 @@ static const node_config_t NODE_CONFIG = { .id = "ch32-node1", .name = "CH32 Node 1", .location = "somewhere"}; // RS485 Devices Configuration -#define RS485_DEVICE_COUNT 2 +#define RS485_DEVICE_COUNT 1 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; + {.slave_id = 0x01, .type = DEVICE_RELAY, .name = "relay-1"}}; +// {.slave_id = 0x02, .type = DEVICE_SOIL_SENSOR, .name = "soil-monitor-1"}}; #endif // CONFIG_H diff --git a/include/modbus_handler.h b/include/modbus_handler.h index 83a66ac..4b99e0c 100644 --- a/include/modbus_handler.h +++ b/include/modbus_handler.h @@ -11,7 +11,7 @@ typedef void (*modbus_value_cb)(uint8_t device_idx, const char* property, 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, +bool 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 index acea880..61378d6 100644 --- a/include/modbus_master.h +++ b/include/modbus_master.h @@ -2,6 +2,7 @@ #define __MODBUS_MASTER_H #include +#include // Function codes #define MODBUS_FC_READ_HOLDING_REGISTERS 0x03 @@ -51,6 +52,6 @@ void modbus_init(modbus_context_t* ctx, 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, +bool 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 index 6f5da85..740445e 100644 --- a/include/mqtt_handler.h +++ b/include/mqtt_handler.h @@ -2,10 +2,13 @@ #define MQTT_HANDLER_H #include +#include #include "ch32v003fun.h" #include "w5500.h" +#define MAX_PAYLOAD_LENGTH 256 + // Options structure for client identification typedef struct { char* clientid; @@ -20,14 +23,20 @@ typedef struct { ch32_mqtt_options_t opts; uint32_t last_reconnect; uint32_t last_yield; - uint8_t is_connected; + bool is_connected; char base_topic[64]; } mqtt_state_t; +extern char nodes_list[MAX_PAYLOAD_LENGTH]; + 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); +void publish_retained(MQTTClient* client, const char* topic, + const char* payload); +void publish_message(MQTTClient* client, const char* payload, + const char* topic); #endif \ No newline at end of file diff --git a/include/onewire_temp.h b/include/onewire_temp.h new file mode 100644 index 0000000..282eb16 --- /dev/null +++ b/include/onewire_temp.h @@ -0,0 +1,46 @@ +#ifndef ONEWIRE_TEMP_H +#define ONEWIRE_TEMP_H + +#include +#include + +#include "mqtt_handler.h" + +#define ONEWIRE_MAX_SENSORS 16 +#define ONEWIRE_TEMP_INVALID -999.0f + +typedef enum { + ONEWIRE_STATE_READY, + ONEWIRE_STATE_CONVERTING, + ONEWIRE_STATE_READ +} onewire_state_t; + +// Initialize OneWire temperature system +void onewire_temp_init(void); + +// Process all sensors (call regularly) +void onewire_temp_process(void); + +// Start parallel conversion on all sensors +void onewire_temp_start_parallel(void); + +// Set parallel conversion mode +void onewire_temp_set_parallel(bool enable); + +// Get temperature for sensor index +float onewire_temp_get(uint8_t index); + +// Get total number of discovered sensors +uint8_t onewire_temp_count(void); + +// Get sensor address +const uint8_t* onewire_temp_address(uint8_t index); + +// Check if sensor is valid +bool onewire_temp_valid(uint8_t index); + +// MQTT +void onewire_temp_publish_discovery(MQTTClient* client, const char* node_id); +void onewire_temp_publish_values(MQTTClient* client, const char* node_id); + +#endif // ONEWIRE_TEMP_H \ No newline at end of file diff --git a/include/system_init.h b/include/system_init.h index 6da8f3c..4b99f1b 100644 --- a/include/system_init.h +++ b/include/system_init.h @@ -1,9 +1,11 @@ #ifndef SYSTEM_INIT_H #define SYSTEM_INIT_H +#include + #define W5500_INIT_DELAY_MS 55 void init_system(void); -int wait_for_dhcp(void); +bool wait_for_dhcp(void); #endif diff --git a/lib/onewire/onewire.c b/lib/onewire/onewire.c new file mode 100644 index 0000000..9728d36 --- /dev/null +++ b/lib/onewire/onewire.c @@ -0,0 +1,619 @@ +/* + +Single-File One Wire Communication Functions for CH32V003 + +Relies on the CH32V003fun library from: + https://github.com/cnlohr/ch32v003fun + +This is very heavily derived from the Arduino OneWire library, + at https://github.com/PaulStoffregen/OneWire + + +Original copyright notices follow: +-------------------------------------------- + +Copyright (c) 2007, Jim Studt (original old version - many contributors since) + +The latest version of this library may be found at: + http://www.pjrc.com/teensy/td_libs_OneWire.html + +OneWire has been maintained by Paul Stoffregen (paul@pjrc.com) since +January 2010. + +DO NOT EMAIL for technical support, especially not for ESP chips! +All project support questions must be posted on public forums +relevant to the board or chips used. If using Arduino, post on +Arduino's forum. If using ESP, post on the ESP community forums. +There is ABSOLUTELY NO TECH SUPPORT BY PRIVATE EMAIL! + +Github's issue tracker for OneWire should be used only to report +specific bugs. DO NOT request project support via Github. All +project and tech support questions must be posted on forums, not +github issues. If you experience a problem and you are not +absolutely sure it's an issue with the library, ask on a forum +first. Only use github to report issues after experts have +confirmed the issue is with OneWire rather than your project. + +Back in 2010, OneWire was in need of many bug fixes, but had +been abandoned the original author (Jim Studt). None of the known +contributors were interested in maintaining OneWire. Paul typically +works on OneWire every 6 to 12 months. Patches usually wait that +long. If anyone is interested in more actively maintaining OneWire, +please contact Paul (this is pretty much the only reason to use +private email about OneWire). + +OneWire is now very mature code. No changes other than adding +definitions for newer hardware support are anticipated. + + ESP32 mods authored by stickbreaker: + @stickbreaker 30APR2018 add IRAM_ATTR to read_bit() OneWireWriteBit() to solve +ICache miss timing failure. thanks @everslick re: +https://github.com/espressif/arduino-esp32/issues/1335 Altered by garyd9 for +clean merge with Paul Stoffregen's source + +Version 2.3: + Unknown chip fallback mode, Roger Clark + Teensy-LC compatibility, Paul Stoffregen + Search bug fix, Love Nystrom + +Version 2.2: + Teensy 3.0 compatibility, Paul Stoffregen, paul@pjrc.com + Arduino Due compatibility, http://arduino.cc/forum/index.php?topic=141030 + Fix DS18B20 example negative temperature + Fix DS18B20 example's low res modes, Ken Butcher + Improve reset timing, Mark Tillotson + Add const qualifiers, Bertrik Sikken + Add initial value input to crc16, Bertrik Sikken + Add target_search() function, Scott Roberts + +Version 2.1: + Arduino 1.0 compatibility, Paul Stoffregen + Improve temperature example, Paul Stoffregen + DS250x_PROM example, Guillermo Lovato + PIC32 (chipKit) compatibility, Jason Dangel, dangel.jason AT gmail.com + Improvements from Glenn Trewitt: + - crc16() now works + - check_crc16() does all of calculation/checking work. + - Added read_bytes() and write_bytes(), to reduce tedious loops. + - Added ds2408 example. + Delete very old, out-of-date readme file (info is here) + +Version 2.0: Modifications by Paul Stoffregen, January 2010: +http://www.pjrc.com/teensy/td_libs_OneWire.html + Search fix from Robin James + http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1238032295/27#27 + Use direct optimized I/O in all cases + Disable interrupts during timing critical sections + (this solves many random communication errors) + Disable interrupts during read-modify-write I/O + Reduce RAM consumption by eliminating unnecessary + variables and trimming many to 8 bits + Optimize both crc8 - table version moved to flash + +Modified to work with larger numbers of devices - avoids loop. +Tested in Arduino 11 alpha with 12 sensors. +26 Sept 2008 -- Robin James +http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1238032295/27#27 + +Updated to work with arduino-0008 and to include skip() as of +2007/07/06. --RJL20 + +Modified to calculate the 8-bit CRC directly, avoiding the need for +the 256-byte lookup table to be loaded in RAM. Tested in arduino-0010 +-- Tom Pollard, Jan 23, 2008 + +Jim Studt's original library was modified by Josh Larios. + +Tom Pollard, pollard@alum.mit.edu, contributed around May 20, 2008 + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Much of the code was inspired by Derek Yerger's code, though I don't +think much of that remains. In any event that was.. + (copyleft) 2006 by Derek Yerger - Free to distribute freely. + +The CRC code was excerpted and inspired by the Dallas Semiconductor +sample code bearing this copyright. +//--------------------------------------------------------------------------- +// Copyright (C) 2000 Dallas Semiconductor Corporation, All Rights Reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL DALLAS SEMICONDUCTOR BE LIABLE FOR ANY CLAIM, DAMAGES +// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// Except as contained in this notice, the name of Dallas Semiconductor +// shall not be used except as stated in the Dallas Semiconductor +// Branding Policy. +//-------------------------------------------------------------------------- +*/ + +#include "onewire.h" + +#include +#include +#include +#include + +#include "ch32v003fun.h" + +// global search state +unsigned char ROM_NO[8]; +uint8_t LastDiscrepancy; +uint8_t LastFamilyDiscrepancy; +bool LastDeviceFlag; + +void OneWireBegin(void); + +// Perform a 1-Wire reset cycle. Returns 1 if a device responds +// with a presence pulse. Returns 0 if there is no device or the +// bus is shorted or otherwise held low for more than 250uS +uint8_t OneWireReset(void); + +// Issue a 1-Wire rom select command, you do the reset first. +void OneWireSelect(const uint8_t rom[8]); + +// Issue a 1-Wire rom skip command, to address all on bus. +// void skip(void); + +// Write a byte. If 'power' is one then the wire is held high at +// the end for parasitically powered devices. You are responsible +// for eventually depowering it by calling depower() or doing +// another read or write. +void OneWireWrite(uint8_t v, uint8_t power); + +void OneWireWriteBytes(const uint8_t *buf, uint16_t count, bool power); + +// Read a byte. +uint8_t OneWireRead(void); + +void OneWireReadBytes(uint8_t *buf, uint16_t count); + +// Write a bit. The bus is always left powered at the end, see +// note in write() about that. +void OneWireWriteBit(uint8_t v); + +// Read a bit. +uint8_t OneWireReadBit(void); + +// Stop forcing power onto the bus. You only need to do this if +// you used the 'power' flag to write() or used a write_bit() call +// and aren't about to do another read or write. You would rather +// not leave this powered if you don't have to, just in case +// someone shorts your bus. +void OneWireDepower(void); + +// Clear the search state so that if will start from the beginning again. +void OneWireResetSearch(); + +// Setup the search to find the device type 'family_code' on the next call +// to search(*newAddr) if it is present. +void OneWireTargetSearch(uint8_t family_code); + +// Look for the next device. Returns 1 if a new address has been +// returned. A zero might mean that the bus is shorted, there are +// no devices, or you have already retrieved all of them. It +// might be a good idea to check the CRC to make sure you didn't +// get garbage. The order is deterministic. You will always get +// the same devices in the same order. +bool OneWireSearch(uint8_t *newAddr, bool search_mode); + +// Compute a Dallas Semiconductor 8 bit CRC, these are used in the +// ROM and scratchpad registers. +uint8_t OneWireCrc8(const uint8_t *addr, uint8_t len); + +// Compute the 1-Wire CRC16 and compare it against the received CRC. +// Example usage (reading a DS2408): +// // Put everything in a buffer so we can compute the CRC easily. +// uint8_t buf[13]; +// buf[0] = 0xF0; // Read PIO Registers +// buf[1] = 0x88; // LSB address +// buf[2] = 0x00; // MSB address +// WriteBytes(net, buf, 3); // Write 3 cmd bytes +// ReadBytes(net, buf+3, 10); // Read 6 data bytes, 2 0xFF, 2 CRC16 +// if (!CheckCRC16(buf, 11, &buf[11])) { +// // Handle error. +// } +// +// @param input - Array of bytes to checksum. +// @param len - How many bytes to use. +// @param inverted_crc - The two CRC16 bytes in the received data. +// This should just point into the received data, +// *not* at a 16-bit integer. +// @param crc - The crc starting value (optional) +// @return True, iff the CRC matches. +bool OneWireCheckCrc16(const uint8_t *input, uint16_t len, + const uint8_t *inverted_crc, uint16_t crc); + +// Compute a Dallas Semiconductor 16 bit CRC. This is required to check +// the integrity of data received from many 1-Wire devices. Note that the +// CRC computed here is *not* what you'll get from the 1-Wire network, +// for two reasons: +// 1) The CRC is transmitted bitwise inverted. +// 2) Depending on the endian-ness of your processor, the binary +// representation of the two-byte return value may have a different +// byte order than the two bytes you get from 1-Wire. +// @param input - Array of bytes to checksum. +// @param len - How many bytes to use. +// @param crc - The crc starting value (optional) +// @return The CRC16, as defined by Dallas Semiconductor. +uint16_t OneWireCrc16(const uint8_t *input, uint16_t len, uint16_t crc); + +void OneWireBegin() { + directModeInput(); + OneWireResetSearch(); +} + +// Perform the onewire reset function. We will wait up to 250uS for +// the bus to come high, if it doesn't then it is broken or shorted +// and we return a 0; +// +// Returns 1 if a device asserted a presence pulse, 0 otherwise. +// +uint8_t OneWireReset(void) { + uint8_t r; + uint8_t retries = 125; + + DIRECT_MODE_INPUT(); + + // wait until the wire is high... just in case + do { + if (--retries == 0) return 0; + Delay_Us(2); + } while (!DIRECT_READ()); + + DIRECT_WRITE_LOW(); + DIRECT_MODE_OUTPUT(); // drive output low + + Delay_Us(480); + + DIRECT_MODE_INPUT(); // allow it to float + Delay_Us(70); + r = !DIRECT_READ(); + + Delay_Us(410); + return r; +} + +// +// Write a bit. Port and bit is used to cut lookup time and provide +// more certain timing. +// +void OneWireWriteBit(uint8_t v) { + if (v & 1) { + DIRECT_WRITE_LOW(); + DIRECT_MODE_OUTPUT(); // drive output low + Delay_Us(10); + DIRECT_WRITE_HIGH(); // drive output high + + Delay_Us(55); + } else { + DIRECT_WRITE_LOW(); + DIRECT_MODE_OUTPUT(); // drive output low + Delay_Us(65); + DIRECT_WRITE_HIGH(); // drive output high + + Delay_Us(5); + } +} + +// +// Read a bit. Port and bit is used to cut lookup time and provide +// more certain timing. +// +uint8_t OneWireReadBit(void) { + uint8_t r; + + DIRECT_MODE_OUTPUT(); + DIRECT_WRITE_LOW(); + Delay_Us(3); + DIRECT_MODE_INPUT(); // let pin float, pull up will raise + Delay_Us(10); + r = DIRECT_READ(); + + Delay_Us(53); + return r; +} + +// +// Write a byte. The writing code uses the active drivers to raise the +// pin high, if you need power after the write (e.g. DS18S20 in +// parasite power mode) then set 'power' to 1, otherwise the pin will +// go tri-state at the end of the write to avoid heating in a short or +// other mishap. +// +void OneWireWrite(uint8_t v, uint8_t power /* = 0 */) { + uint8_t bitMask; + + for (bitMask = 0x01; bitMask; bitMask <<= 1) { + OneWireWriteBit((bitMask & v) ? 1 : 0); + } + if (!power) { + DIRECT_MODE_INPUT(); + DIRECT_WRITE_LOW(); + } +} + +void OneWireWriteBytes(const uint8_t *buf, uint16_t count, bool power) { + for (uint16_t i = 0; i < count; i++) OneWireWrite(buf[i], 0); + if (!power) { + DIRECT_MODE_INPUT(); + DIRECT_WRITE_LOW(); + } +} + +// +// Read a byte +// +uint8_t OneWireRead() { + uint8_t bitMask; + uint8_t r = 0; + + for (bitMask = 0x01; bitMask; bitMask <<= 1) { + if (OneWireReadBit()) r |= bitMask; + } + return r; +} + +void OneWireReadBytes(uint8_t *buf, uint16_t count) { + for (uint16_t i = 0; i < count; i++) buf[i] = OneWireRead(); +} + +// +// Do a ROM select +// +void OneWireSelect(const uint8_t rom[8]) { + uint8_t i; + + OneWireWrite(0x55, 0); // Choose ROM + + for (i = 0; i < 8; i++) OneWireWrite(rom[i], 0); +} + +// +// Do a ROM skip +// +void OneWireSkip() { + OneWireWrite(0xCC, 0); // Skip ROM +} + +void OneWireDepower() { DIRECT_MODE_INPUT(); } + +// +// You need to use this function to start a search again from the beginning. +// You do not need to do it for the first search, though you could. +// +void OneWireResetSearch() { + // reset the search state + LastDiscrepancy = 0; + LastDeviceFlag = false; + LastFamilyDiscrepancy = 0; + for (int i = 7;; i--) { + ROM_NO[i] = 0; + if (i == 0) break; + } +} + +// Setup the search to find the device type 'family_code' on the next call +// to search(*newAddr) if it is present. +// +void OneWireTargetSearch(uint8_t family_code) { + // set the search state to find SearchFamily type devices + ROM_NO[0] = family_code; + for (uint8_t i = 1; i < 8; i++) ROM_NO[i] = 0; + LastDiscrepancy = 64; + LastFamilyDiscrepancy = 0; + LastDeviceFlag = false; +} + +// +// Perform a search. If this function returns a '1' then it has +// enumerated the next device and you may retrieve the ROM from the +// OneWireAddress variable. If there are no devices, no further +// devices, or something horrible happens in the middle of the +// enumeration then a 0 is returned. If a new device is found then +// its address is copied to newAddr. Use OneWireReset_search() to +// start over. +// +// --- Replaced by the one from the Dallas Semiconductor web site --- +//-------------------------------------------------------------------------- +// Perform the 1-Wire Search Algorithm on the 1-Wire bus using the existing +// search state. +// Return TRUE : device found, ROM number in ROM_NO buffer +// FALSE : device not found, end of search +// +bool OneWireSearch(uint8_t *newAddr, bool search_mode /* = true */) { + uint8_t id_bit_number; + uint8_t last_zero, rom_byte_number; + bool search_result; + uint8_t id_bit, cmp_id_bit; + + unsigned char rom_byte_mask, search_direction; + + // initialize for search + id_bit_number = 1; + last_zero = 0; + rom_byte_number = 0; + rom_byte_mask = 1; + search_result = false; + + // if the last call was not the last one + if (!LastDeviceFlag) { + // 1-Wire reset + if (!OneWireReset()) { + // reset the search + LastDiscrepancy = 0; + LastDeviceFlag = false; + LastFamilyDiscrepancy = 0; + return false; + } + + // issue the search command + if (search_mode == true) { + OneWireWrite(0xF0, 0); // NORMAL SEARCH + } else { + OneWireWrite(0xEC, 0); // CONDITIONAL SEARCH + } + + // loop to do the search + do { + // read a bit and its complement + id_bit = OneWireReadBit(); + cmp_id_bit = OneWireReadBit(); + + // check for no devices on 1-wire + if ((id_bit == 1) && (cmp_id_bit == 1)) { + break; + } else { + // all devices coupled have 0 or 1 + if (id_bit != cmp_id_bit) { + search_direction = id_bit; // bit write value for search + } else { + // if this discrepancy if before the Last Discrepancy + // on a previous next then pick the same as last time + if (id_bit_number < LastDiscrepancy) { + search_direction = ((ROM_NO[rom_byte_number] & rom_byte_mask) > 0); + } else { + // if equal to last pick 1, if not then pick 0 + search_direction = (id_bit_number == LastDiscrepancy); + } + // if 0 was picked then record its position in LastZero + if (search_direction == 0) { + last_zero = id_bit_number; + + // check for Last discrepancy in family + if (last_zero < 9) LastFamilyDiscrepancy = last_zero; + } + } + + // set or clear the bit in the ROM byte rom_byte_number + // with mask rom_byte_mask + if (search_direction == 1) + ROM_NO[rom_byte_number] |= rom_byte_mask; + else + ROM_NO[rom_byte_number] &= ~rom_byte_mask; + + // serial number search direction write bit + OneWireWriteBit(search_direction); + + // increment the byte counter id_bit_number + // and shift the mask rom_byte_mask + id_bit_number++; + rom_byte_mask <<= 1; + + // if the mask is 0 then go to new SerialNum byte rom_byte_number and + // reset mask + if (rom_byte_mask == 0) { + rom_byte_number++; + rom_byte_mask = 1; + } + } + } while (rom_byte_number < 8); // loop until through all ROM bytes 0-7 + + // if the search was successful then + if (!(id_bit_number < 65)) { + // search successful so set LastDiscrepancy,LastDeviceFlag,search_result + LastDiscrepancy = last_zero; + + // check for last device + if (LastDiscrepancy == 0) { + LastDeviceFlag = true; + } + search_result = true; + } + } + + // if no device found then reset counters so next 'search' will be like a + // first + if (!search_result || !ROM_NO[0]) { + LastDiscrepancy = 0; + LastDeviceFlag = false; + LastFamilyDiscrepancy = 0; + search_result = false; + } else { + for (int i = 0; i < 8; i++) newAddr[i] = ROM_NO[i]; + } + return search_result; +} + +// The 1-Wire CRC scheme is described in Maxim Application Note 27: +// "Understanding and Using Cyclic Redundancy Checks with Maxim iButton +// Products" +// + +// +// Compute a Dallas Semiconductor 8 bit CRC directly. +// this is much slower, but a little smaller, than the lookup table. +// +uint8_t OneWireCrc8(const uint8_t *addr, uint8_t len) { + uint8_t crc = 0; + + while (len--) { + uint8_t inbyte = *addr++; + for (uint8_t i = 8; i; i--) { + uint8_t mix = (crc ^ inbyte) & 0x01; + crc >>= 1; + if (mix) crc ^= 0x8C; + inbyte >>= 1; + } + } + return crc; +} + +bool OneWireCheckCrc16(const uint8_t *input, uint16_t len, + const uint8_t *inverted_crc, uint16_t crc) { + crc = ~OneWireCrc16(input, len, crc); + return (crc & 0xFF) == inverted_crc[0] && (crc >> 8) == inverted_crc[1]; +} + +uint16_t OneWireCrc16(const uint8_t *input, uint16_t len, uint16_t crc) { + static const uint8_t oddparity[16] = {0, 1, 1, 0, 1, 0, 0, 1, + 1, 0, 0, 1, 0, 1, 1, 0}; + + for (uint16_t i = 0; i < len; i++) { + // Even though we're just copying a byte from the input, + // we'll be doing 16-bit computation with it. + uint16_t cdata = input[i]; + cdata = (cdata ^ crc) & 0xff; + crc >>= 8; + + if (oddparity[cdata & 0x0F] ^ oddparity[cdata >> 4]) crc ^= 0xC001; + + cdata <<= 6; + crc ^= cdata; + cdata <<= 1; + crc ^= cdata; + } + + return crc; +} diff --git a/lib/onewire/onewire.h b/lib/onewire/onewire.h new file mode 100644 index 0000000..078a53e --- /dev/null +++ b/lib/onewire/onewire.h @@ -0,0 +1,98 @@ +#ifndef CH32V003_ONEWIRE_H +#define CH32V003_ONEWIRE_H + +#include +#include + +#include "ch32v003fun.h" + +// GPIO Direct Access Definitions +#ifndef OneWire_Direct_GPIO_h +#define OneWire_Direct_GPIO_h + +static inline __attribute__((always_inline)) uint8_t directRead() { + return (GPIOB->INDR & (1 << 9)) ? 1 : 0; +} + +static inline __attribute__((always_inline)) void directModeInput() { + GPIOB->CFGHR &= ~(0xF << (4 * (9 - 8))); + GPIOB->CFGHR |= (0x4 << (4 * (9 - 8))); +} + +static inline __attribute__((always_inline)) void directModeOutput() { + GPIOB->CFGHR &= ~(0xF << (4 * (9 - 8))); + GPIOB->CFGHR |= (0x3 << (4 * (9 - 8))); +} + +static inline __attribute__((always_inline)) void directWriteLow() { + GPIOB->BCR = (1 << 9); +} + +static inline __attribute__((always_inline)) void directWriteHigh() { + GPIOB->BSHR = (1 << 9); +} + +#define DIRECT_READ() directRead() +#define DIRECT_WRITE_LOW() directWriteLow() +#define DIRECT_WRITE_HIGH() directWriteHigh() +#define DIRECT_MODE_INPUT() directModeInput() +#define DIRECT_MODE_OUTPUT() directModeOutput() +#endif + +// OneWire Function Declarations + +// Initialize the OneWire bus +void OneWireBegin(void); + +// Perform a 1-Wire reset cycle. Returns 1 if a device responds with a presence +// pulse +uint8_t OneWireReset(void); + +// Select a device on the bus using its ROM code +void OneWireSelect(const uint8_t rom[8]); + +// Skip ROM selection - addresses all devices on the bus +void OneWireSkip(void); + +// Write a byte to the bus. If 'power' is true, maintain strong pullup after +// write +void OneWireWrite(uint8_t v, uint8_t power); + +// Write multiple bytes to the bus +void OneWireWriteBytes(const uint8_t *buf, uint16_t count, bool power); + +// Read a byte from the bus +uint8_t OneWireRead(void); + +// Read multiple bytes from the bus +void OneWireReadBytes(uint8_t *buf, uint16_t count); + +// Write a single bit to the bus +void OneWireWriteBit(uint8_t v); + +// Read a single bit from the bus +uint8_t OneWireReadBit(void); + +// Stop forcing power onto the bus +void OneWireDepower(void); + +// Reset the search state +void OneWireResetSearch(void); + +// Setup search to find devices of a specific family code +void OneWireTargetSearch(uint8_t family_code); + +// Search for the next device on the bus +bool OneWireSearch(uint8_t *newAddr, bool search_mode); + +// Calculate 8-bit CRC +uint8_t OneWireCrc8(const uint8_t *addr, uint8_t len); + +// Check if received CRC matches calculated CRC +bool OneWireCheckCrc16(const uint8_t *input, uint16_t len, + const uint8_t *inverted_crc, uint16_t crc); + +// Calculate 16-bit CRC +uint16_t OneWireCrc16(const uint8_t *input, uint16_t len, uint16_t crc); + +#endif // CH32V003_ONEWIRE_H \ No newline at end of file diff --git a/src/main.c b/src/main.c index 35b7016..aa72cf1 100644 --- a/src/main.c +++ b/src/main.c @@ -1,10 +1,14 @@ +#include + #include "ch32v003fun.h" #include "config.h" #include "debug.h" #include "dhcp.h" #include "modbus_handler.h" #include "mqtt_handler.h" +#include "onewire_temp.h" #include "system_init.h" +#include "systick.h" #include "w5500.h" static mqtt_state_t mqtt_state; @@ -33,14 +37,39 @@ int main(void) { } // init handlers + // init sensors before mqtt so we can add them to discovery + onewire_temp_init(); + onewire_temp_set_parallel(true); mqtt_init(&mqtt_state); modbus_handler_init(&modbus_ctx, on_modbus_value); + uint32_t last_temp_publish = 0; + uint32_t last_temp_conversion = 0; + const uint32_t TEMP_PUBLISH_INTERVAL = 10000; // 10 seconds + const uint32_t CONVERSION_INTERVAL = 1000; // 1 second + while (1) { + uint32_t current_time = millis(); + dhcp_process(); mqtt_process(&mqtt_state); modbus_handler_process(); + + // TODO: doesn't make sense to convert every 1s, should be the same interval as publish + if (current_time - last_temp_conversion >= CONVERSION_INTERVAL) { + onewire_temp_start_parallel(); + last_temp_conversion = current_time; + } + + onewire_temp_process(); // Process all sensors + + if (current_time - last_temp_publish >= TEMP_PUBLISH_INTERVAL) { + if (mqtt_state.is_connected) { + onewire_temp_publish_values(&mqtt_state.client, NODE_CONFIG.id); + } + last_temp_publish = current_time; + } } return 0; -} \ No newline at end of file +} diff --git a/src/modbus_handler.c b/src/modbus_handler.c index 707bff9..d09f445 100644 --- a/src/modbus_handler.c +++ b/src/modbus_handler.c @@ -55,15 +55,15 @@ static uint16_t get_register_address(device_type_t type, const char* property) { return 0xFFFF; } -uint8_t modbus_handler_send_request(uint8_t device_idx, const char* property, +bool 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; + return false; } uint16_t reg = get_register_address(RS485_DEVICES[device_idx].type, property); if (reg == 0xFFFF) { - return 0; + return false; } uint8_t fc = is_write ? MODBUS_FC_WRITE_SINGLE_REGISTER @@ -74,8 +74,8 @@ uint8_t modbus_handler_send_request(uint8_t device_idx, const char* property, 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 true; } - return 0; + return false; } \ No newline at end of file diff --git a/src/modbus_master.c b/src/modbus_master.c index b28bdcb..19b3783 100644 --- a/src/modbus_master.c +++ b/src/modbus_master.c @@ -158,11 +158,11 @@ 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, +bool 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 + return false; // Busy } uint16_t len = @@ -173,7 +173,7 @@ uint8_t modbus_send_request(modbus_context_t* ctx, uint8_t slave_addr, ctx->rx_len = 0; ctx->state = MODBUS_WAITING_RESPONSE; - return 1; // Success + return true; // Success } void modbus_process(modbus_context_t* ctx) { diff --git a/src/mqtt_handler.c b/src/mqtt_handler.c index 9f32dc8..22ae04a 100644 --- a/src/mqtt_handler.c +++ b/src/mqtt_handler.c @@ -6,6 +6,7 @@ #include "debug.h" #include "modbus_handler.h" +#include "onewire_temp.h" #include "systick.h" // MQTT @@ -19,11 +20,12 @@ #define HOMIE_STATE_LOST "lost" #define HOMIE_STATE_SLEEPING "sleeping" #define HOMIE_STATE_DISCONNECTED "disconnected" -#define MAX_PAYLOAD_LENGTH 256 + +// nodes list buffer +char nodes_list[MAX_PAYLOAD_LENGTH]; // 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, +static bool 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) { @@ -34,11 +36,11 @@ static uint8_t parse_homie_topic(const char* topic, size_t topic_len, // 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 (!slash) return false; if (segment == 2) { size_t len = slash - segment_start; - if (len >= name_max) return 0; + if (len >= name_max) return false; memcpy(device_name, segment_start, len); device_name[len] = '\0'; } @@ -48,19 +50,19 @@ static uint8_t parse_homie_topic(const char* topic, size_t topic_len, } const char* slash = memchr(segment_start, '/', topic_end - segment_start); - if (!slash) return 0; + if (!slash) return false; size_t len = slash - segment_start; - if (len >= prop_max) return 0; + if (len >= prop_max) return false; memcpy(property, segment_start, len); property[len] = '\0'; segment_start = slash + 1; - if (segment_start >= topic_end) return 0; // Missing set/get + if (segment_start >= topic_end) return false; // Missing set/get *is_set = (*segment_start == 's'); - return 1; + return true; } // MQTT client @@ -138,14 +140,12 @@ void publish_message(MQTTClient* client, const char* 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) { +void publish_retained(MQTTClient* client, const char* topic, + const char* payload) { MQTTMessage message = {.qos = QOS1, .retained = 1, .dup = 0, @@ -157,7 +157,7 @@ static void publish_retained(MQTTClient* client, const char* topic, } } -// Send state update +// TODO: this is a modbus value only 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", @@ -192,7 +192,6 @@ void publish_value(MQTTClient* client, const char* device_name, 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); @@ -208,6 +207,7 @@ static void publish_device_attributes(MQTTClient* client) { ptr = nodes_list; *ptr = '\0'; + // add rs485 devices for (int i = 0; i < RS485_DEVICE_COUNT; i++) { if (i > 0 && remaining > 1) { *ptr++ = ','; @@ -231,8 +231,8 @@ static void publish_device_attributes(MQTTClient* client) { } } -static void publish_node_attributes(MQTTClient* client, - const rs485_device_t* device) { +static void publish_rs485_node_attributes(MQTTClient* client, + const rs485_device_t* device) { char topic[MAX_TOPIC_LENGTH]; // Node base attributes @@ -304,7 +304,7 @@ void mqtt_init(mqtt_state_t* state) { state->opts.qos = QOS1; state->last_reconnect = 0; state->last_yield = 0; - state->is_connected = 0; + state->is_connected = false; } // Find device by name and return its index @@ -366,14 +366,19 @@ void mqtt_process(mqtt_state_t* state) { rc = setup_mqtt_client(&state->network, &state->opts, &state->client); if (rc == 0) { - state->is_connected = 1; + state->is_connected = true; 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]); + publish_rs485_node_attributes(&state->client, &RS485_DEVICES[i]); } + + // with onewire we can discover new devices on the bus during runtime + // is it worth implementing? + onewire_temp_publish_discovery(&state->client, NODE_CONFIG.id); + discovery_published = 1; } @@ -384,7 +389,7 @@ void mqtt_process(mqtt_state_t* state) { rc = subscribe_to_topic(&state->client, sub_topic, QOS1, message_arrived); if (rc != 0) { - state->is_connected = 0; + state->is_connected = false; } } state->last_reconnect = now; @@ -392,7 +397,7 @@ void mqtt_process(mqtt_state_t* state) { } 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->is_connected = false; } state->last_yield = now; } diff --git a/src/onewire_temp.c b/src/onewire_temp.c new file mode 100644 index 0000000..cfc6df2 --- /dev/null +++ b/src/onewire_temp.c @@ -0,0 +1,324 @@ +#include "onewire_temp.h" + +#include +#include + +#include "debug.h" +#include "systick.h" + +#define ONEWIRE_CONVERSION_TIME_MS 750 + +typedef struct { + uint8_t address[8]; + float temperature; + onewire_state_t state; + uint32_t convert_start_time; + bool valid; +} onewire_sensor_t; + +typedef struct { + onewire_sensor_t sensors[ONEWIRE_MAX_SENSORS]; + uint8_t count; + bool parallel_mode; +} onewire_system_t; + +static onewire_system_t ow_sys = {0}; + +void onewire_temp_init(void) { + uint8_t addr[8]; + + OneWireBegin(); + OneWireResetSearch(); + + while (ow_sys.count < ONEWIRE_MAX_SENSORS) { + if (!OneWireSearch(addr, true)) { + break; + } + + // Validate sensor + if (OneWireCrc8(addr, 7) != addr[7]) { + continue; + } + + // Check if it's a temperature sensor + switch (addr[0]) { + case 0x10: // DS18S20 + case 0x28: // DS18B20 + case 0x22: // DS1822 + break; + default: + continue; // Not a supported temperature sensor + } + + // Store valid sensor + onewire_sensor_t* sensor = &ow_sys.sensors[ow_sys.count]; + memcpy(sensor->address, addr, 8); + sensor->valid = true; + sensor->state = ONEWIRE_STATE_READY; + sensor->temperature = ONEWIRE_TEMP_INVALID; + ow_sys.count++; + } +} + +static float convert_temperature(const uint8_t* data, uint8_t family_code) { + int16_t raw = (data[1] << 8) | data[0]; + + // DS18S20 + if (family_code == 0x10) { + raw = raw << 3; + if (data[7] == 0x10) { + raw = (raw & 0xFFF0) + 12 - data[6]; + } + } + // DS18B20/DS1822 + else { + switch (data[4] & 0x60) { + case 0x00: + raw &= ~7; + break; // 9-bit + case 0x20: + raw &= ~3; + break; // 10-bit + case 0x40: + raw &= ~1; + break; // 11-bit + } + } + + return (float)(raw / 16.0); +} + +static bool start_conversion(onewire_sensor_t* sensor) { + if (!sensor->valid || sensor->state != ONEWIRE_STATE_READY) { + return false; + } + + if (!OneWireReset()) { + return false; + } + + OneWireSelect(sensor->address); + OneWireWrite(0x44, 0); // Start conversion w/o parasite power + sensor->state = ONEWIRE_STATE_CONVERTING; + sensor->convert_start_time = millis(); + + return true; +} + +static bool read_temperature(onewire_sensor_t* sensor) { + uint8_t data[9]; + + if (!OneWireReset()) { + return false; + } + + OneWireSelect(sensor->address); + OneWireWrite(0xBE, 0); // Read scratchpad + OneWireReadBytes(data, 9); + + if (OneWireCrc8(data, 8) != data[8]) { + return false; + } + + sensor->temperature = convert_temperature(data, sensor->address[0]); + return true; +} + +void onewire_temp_process(void) { + uint32_t now = millis(); + + for (uint8_t i = 0; i < ow_sys.count; i++) { + onewire_sensor_t* sensor = &ow_sys.sensors[i]; + + if (!sensor->valid) { + continue; + } + + switch (sensor->state) { + case ONEWIRE_STATE_READY: + if (!ow_sys.parallel_mode) { + start_conversion(sensor); + } + break; + + case ONEWIRE_STATE_CONVERTING: + if (now - sensor->convert_start_time >= ONEWIRE_CONVERSION_TIME_MS) { + sensor->state = ONEWIRE_STATE_READ; + } + break; + + case ONEWIRE_STATE_READ: + if (!read_temperature(sensor)) { + sensor->valid = false; + } + sensor->state = ONEWIRE_STATE_READY; + break; + } + } +} + +void onewire_temp_start_parallel(void) { + if (!OneWireReset()) { + return; + } + + OneWireSkip(); // Address all devices + OneWireWrite(0x44, 1); // Start conversion with parasite power + + uint32_t now = millis(); + for (uint8_t i = 0; i < ow_sys.count; i++) { + if (ow_sys.sensors[i].valid) { + ow_sys.sensors[i].state = ONEWIRE_STATE_CONVERTING; + ow_sys.sensors[i].convert_start_time = now; + } + } +} + +float onewire_temp_get(uint8_t index) { + return (index < ow_sys.count && ow_sys.sensors[index].valid) + ? ow_sys.sensors[index].temperature + : ONEWIRE_TEMP_INVALID; +} + +uint8_t onewire_temp_count(void) { return ow_sys.count; } + +const uint8_t* onewire_temp_address(uint8_t index) { + return (index < ow_sys.count) ? ow_sys.sensors[index].address : NULL; +} + +bool onewire_temp_valid(uint8_t index) { + return (index < ow_sys.count) ? ow_sys.sensors[index].valid : false; +} + +void onewire_temp_set_parallel(bool enable) { ow_sys.parallel_mode = enable; } + +// MQTT +void onewire_temp_publish_discovery(MQTTClient* client, const char* node_id) { + char topic[MAX_TOPIC_LENGTH]; + char sensor_name[32]; + uint8_t sensor_count = onewire_temp_count(); + + // append to node list + size_t current_len = strlen(nodes_list); + char* ptr = nodes_list + current_len; + size_t remaining = sizeof(nodes_list) - current_len; + + for (uint8_t i = 0; i < sensor_count && remaining > 1; i++) { + if (!onewire_temp_valid(i)) { + continue; + } + + // , if not 1st + if (current_len > 0 && remaining > 1) { + *ptr++ = ','; + remaining--; + current_len++; + } + + const uint8_t* addr = onewire_temp_address(i); + int written = snprintf(sensor_name, sizeof(sensor_name), + "temp_%02x%02x%02x%02x%02x%02x%02x%02x", + addr[0], addr[1], addr[2], addr[3], + addr[4], addr[5], addr[6], addr[7]); + + if (written < 0 || (size_t)written >= remaining) { + break; + } + + memcpy(ptr, sensor_name, written); + ptr += written; + remaining -= written; + current_len += written; + } + *ptr = '\0'; + + // pub node list + snprintf(topic, sizeof(topic), "homie/%s/$nodes", node_id); + publish_retained(client, topic, nodes_list); + + for (uint8_t i = 0; i < sensor_count; i++) { + if (!onewire_temp_valid(i)) { + continue; + } + + const uint8_t* addr = onewire_temp_address(i); + snprintf(sensor_name, sizeof(sensor_name), + "temp_%02x%02x%02x%02x%02x%02x%02x%02x", + addr[0], addr[1], addr[2], addr[3], + addr[4], addr[5], addr[6], addr[7]); + + snprintf(topic, sizeof(topic), "homie/%s/%s/$name", node_id, sensor_name); + char display_name[48]; + snprintf(display_name, sizeof(display_name), + "Temperature Sensor %02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X", + addr[0], addr[1], addr[2], addr[3], + addr[4], addr[5], addr[6], addr[7]); + publish_retained(client, topic, display_name); + + snprintf(topic, sizeof(topic), "homie/%s/%s/$properties", node_id, sensor_name); + publish_retained(client, topic, "temperature,address"); + + // temperature properties + snprintf(topic, sizeof(topic), "homie/%s/%s/temperature/$name", node_id, sensor_name); + publish_retained(client, topic, "Temperature"); + snprintf(topic, sizeof(topic), "homie/%s/%s/temperature/$datatype", node_id, sensor_name); + publish_retained(client, topic, "float"); + snprintf(topic, sizeof(topic), "homie/%s/%s/temperature/$unit", node_id, sensor_name); + publish_retained(client, topic, "°C"); + + // address properties + snprintf(topic, sizeof(topic), "homie/%s/%s/address/$name", node_id, sensor_name); + publish_retained(client, topic, "ROM Address"); + snprintf(topic, sizeof(topic), "homie/%s/%s/address/$datatype", node_id, sensor_name); + publish_retained(client, topic, "string"); + + char addr_str[24]; + snprintf(addr_str, sizeof(addr_str), + "%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X", + addr[0], addr[1], addr[2], addr[3], + addr[4], addr[5], addr[6], addr[7]); + snprintf(topic, sizeof(topic), "homie/%s/%s/address", node_id, sensor_name); + publish_retained(client, topic, addr_str); + } +} + +// MQTT +void onewire_temp_publish_values(MQTTClient* client, const char* node_id) { + char topic[MAX_TOPIC_LENGTH]; + char value[8]; + uint8_t sensor_count = onewire_temp_count(); + + for (uint8_t i = 0; i < sensor_count; i++) { + if (!onewire_temp_valid(i)) continue; + + float temp = onewire_temp_get(i); + if (temp == ONEWIRE_TEMP_INVALID) continue; + + // fixed-point conversion + int16_t temp_fixed = (int16_t)(temp * 100); + + const uint8_t* addr = onewire_temp_address(i); + snprintf(topic, sizeof(topic), + "homie/%s/temp_%02x%02x%02x%02x%02x%02x%02x%02x/temperature", + node_id, addr[0], addr[1], addr[2], addr[3], addr[4], addr[5], + addr[6], addr[7]); + + uint8_t neg = temp_fixed < 0; + if (neg) temp_fixed = -temp_fixed; + + uint8_t idx = 0; + if (neg) value[idx++] = '-'; + + uint16_t whole = temp_fixed / 100; + uint8_t decimal = temp_fixed % 100; + + if (whole >= 10) value[idx++] = '0' + (whole / 10); + value[idx++] = '0' + (whole % 10); + value[idx++] = '.'; + value[idx++] = '0' + (decimal / 10); + value[idx++] = '0' + (decimal % 10); + value[idx] = '\0'; + + publish_message(client, value, topic); + } +} diff --git a/src/system_init.c b/src/system_init.c index 8190ee9..67547fe 100644 --- a/src/system_init.c +++ b/src/system_init.c @@ -1,5 +1,7 @@ #include "system_init.h" +#include + #include "ch32v003fun.h" #include "debug.h" #include "dhcp.h" @@ -10,7 +12,7 @@ #include "timer.h" #include "uart.h" -#define DHCP_TIMEOUT_MS 15000 +#define DHCP_TIMEOUT_MS 120000 void init_system(void) { SystemInit(); @@ -22,14 +24,14 @@ void init_system(void) { rs485_init(UART_BRR_APB2); } -int wait_for_dhcp(void) { +bool 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; + return false; } dhcp_process(); } - return 1; + return true; } diff --git a/src/uart.c b/src/uart.c index 33dfd9e..579a245 100644 --- a/src/uart.c +++ b/src/uart.c @@ -15,7 +15,7 @@ int _write(__attribute__((unused)) int fd, const char *buf, int size) { int putchar(int c) { while (!(USART2->STATR & USART_FLAG_TC)); // Wait for transmission complete USART2->DATAR = (uint8_t)c; // Send character - return 1; + return (unsigned char)c; } void init_uart(int uart_brr) {