chore: onewire
This commit is contained in:
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -2,6 +2,7 @@
|
||||
#define __MODBUS_MASTER_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// 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
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
#define MQTT_HANDLER_H
|
||||
|
||||
#include <DHCP/dhcp.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#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
|
||||
46
include/onewire_temp.h
Normal file
46
include/onewire_temp.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#ifndef ONEWIRE_TEMP_H
|
||||
#define ONEWIRE_TEMP_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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
|
||||
@@ -1,9 +1,11 @@
|
||||
#ifndef SYSTEM_INIT_H
|
||||
#define SYSTEM_INIT_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#define W5500_INIT_DELAY_MS 55
|
||||
|
||||
void init_system(void);
|
||||
int wait_for_dhcp(void);
|
||||
bool wait_for_dhcp(void);
|
||||
|
||||
#endif
|
||||
|
||||
619
lib/onewire/onewire.c
Normal file
619
lib/onewire/onewire.c
Normal file
@@ -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 <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
98
lib/onewire/onewire.h
Normal file
98
lib/onewire/onewire.h
Normal file
@@ -0,0 +1,98 @@
|
||||
#ifndef CH32V003_ONEWIRE_H
|
||||
#define CH32V003_ONEWIRE_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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
|
||||
31
src/main.c
31
src/main.c
@@ -1,10 +1,14 @@
|
||||
#include <string.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
324
src/onewire_temp.c
Normal file
324
src/onewire_temp.c
Normal file
@@ -0,0 +1,324 @@
|
||||
#include "onewire_temp.h"
|
||||
|
||||
#include <onewire.h>
|
||||
#include <string.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "system_init.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user