main
3 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
131ae200ee |
Add an ESPHome gateway config for the ESP32-S3-Relay-6CH
The USB-RS485 bridge lets a PC drive a segment from tools/wiredsensor.py, which is a bench arrangement: it needs a host awake and attached. A Waveshare ESP32-S3-Relay-6CH does the same master role permanently and brings six relays of its own, so one board both reads the node and switches things in response. Every pin is fixed by the board and was read off its schematic, so they are written literally rather than as substitutions — a substitution would imply a choice that does not exist. Relays sit on GPIO1/2/41/42/45/46, the WS2812 on GPIO38, the buzzer on GPIO21 and RS485 on GPIO17/18. Two of those deserve their comments. GPIO45 and GPIO46 are strapping pins, and GPIO45 selects the flash supply voltage: the internal pulldown gives 3.3V at reset and the pin is released back to it even with the relay energised, but anything external holding it high across a reset selects 1.8V and the module will not boot at all. The strapping warnings from `esphome config` are left unsuppressed for that reason — channels 5 and 6 can also click during reset, so the warning is not crying wolf. The `modbus:` component sets no flow_control_pin, unlike the other configs here. This board derives the SP485EE's driver-enable from the TX line with a 74HC04D, a 54K/1nF RC and a diode, so there is no DE GPIO to name; stated in a comment because an absent option reads as an oversight. flash_size is 16MB, read off the chip with `esptool flash-id` rather than taken from the schematic, which names an ESP32-S3-WROOM-1U with no memory suffix. The logger goes to USB_SERIAL_JTAG because the Type-C is wired straight to native USB with no bridge chip, and the default hardware UART would log to pins this board does not break out. The node's entities are inline rather than from wiredsensor-node.yaml, which creates its own modbus_controller and would leave two of them polling address 0x01. Verified on hardware: the gateway reports 23.624 C and 43.35 %RH with zero bus CRC errors while the node's own USB diagnostics independently show 23.72 C and 43.04 %RH over the same window, drifting in step. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
5b9c09df3b |
Speak Modbus RTU instead of a custom command protocol
ESPHome only has built-in support for Modbus RTU over RS485, so the application layer moves to it. The transport already conformed: the CRC was CRC-16/MODBUS, the line rate 19200 8N1, the delimiter a 3.5-character idle gap pinned at 1750 us above 19200, and the address space 1..247 with broadcast never answered. Only the bytes between the address and the CRC change, and no external crate is needed for a read-only server. frame.rs drops the explicit LEN byte, which RTU does not have — a request's length is implied by its function code. The parser gets simpler and MAX_FRAME grows to the RTU limit of 256. The CRC is now the only integrity check there is, so a truncated frame fails it rather than a length comparison; a test asserts every single-bit corruption is still caught, and another pins our encoding against the published example 01 03 00 6B 00 03 74 17. proto.rs becomes a Modbus server: function codes 0x03 and 0x04 over one 19-register table, 0x08 sub-function 0x0000 as the link test that replaces PING, and the four standard exception codes. Registers are big-endian with 32-bit values high word first, which is what masters and ESPHome's S_DWORD assume. Snapshot::registers renders the whole map and reads slice it, so a range read cannot disagree with a single-register read of the same address — the alternative, assembling only the requested registers per read, has no such guarantee. Both read function codes serve the same table. That is not only for masters that implement just 0x03: a read overlapping the measurement block is refused with SERVER_DEVICE_FAILURE when the sensor has never produced a reading, and ESPHome coalesces adjacent registers of one type into a single command, so diagnostic entities merged into that command would go unavailable along with the measurement they were meant to explain. Requesting them under the other function code puts them in their own command. The generated ESPHome config does this. The firmware barely changes, because dispatch's signature does not: flags widen to u16, buffers follow MAX_FRAME, and comments name registers rather than commands. Now ~35 KiB flash and ~2.6 KiB RAM. tools/wiredsensor.py is rewritten and grows from 15 checks to 21, adding agreement between the two function codes, sub-range consistency, and a per-code assertion for every exception. It stays a hand-written Modbus implementation rather than moving to pymodbus: half these checks inject deliberately malformed frames, and a conforming client library exists precisely to make those unconstructable. It also keeps the wire format independent of wiredsensor-core, so a shared bug cannot cancel itself out. Not yet exercised on hardware — the host tests and register-map cross-checks pass, but the timing-dependent behaviour needs `wiredsensor.py test` against a real bus. This replaces the old protocol rather than joining it; command codes 0x01..0x04 are all valid Modbus function codes, so the two cannot share a segment. PROTOCOL_VERSION is 2. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
93b4e2c4f8 |
Initial commit: RP2040 RS485 temperature/humidity node
RTIC 2 firmware for an RP2040 acting as an RS485 slave, reading a Sensirion SHT31 over I2C and answering a custom binary protocol. Split into two crates so the protocol is testable without hardware: - wiredsensor-core: framing, CRC-16/MODBUS, command dispatch and SHT3x data-sheet math. Pure computation, no I/O, no peripherals. Covered by 42 host tests including the CRC-16/MODBUS catalogue vector, the SHT3x data-sheet CRC-8 example, and an exhaustive single-bit-corruption sweep over every byte of a frame. - wiredsensor-fw: the RTIC application, PL011 register driver and SHT31 I2C driver. Requests are answered entirely from a cached reading. An SHT31 high-repeatability conversion takes up to 15 ms, well past the turnaround a master expects, so the sensor is polled at the lowest priority and the bus path never touches I2C. RTIC's priority ceilings make "a conversion cannot delay a reply" a checked property rather than an argument. Two PL011 details drive the low-level approach, since rp2040-hal exposes neither: the receive-timeout interrupt for prompt frame delimiting, and the BUSY flag, which is the only way to know the final stop bit has left the shift register before releasing DE. The HAL performs the fiddly baud-divisor setup once, then the raw peripheral is reclaimed via free(). Note that RTIM cannot be the sole frame delimiter: it only fires while the RX FIFO is non-empty, so a frame landing exactly on the FIFO watermark is drained by the watermark interrupt and never times out. The authoritative delimiter is a monotonic timer measured from the last received byte. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |