diff --git a/docs/temperature-sensor.md b/docs/temperature-sensor.md index 05af4ef..fc71a46 100644 --- a/docs/temperature-sensor.md +++ b/docs/temperature-sensor.md @@ -76,15 +76,26 @@ Temperature value = 0x131, converted to decimal 305, actual temperature value = #### The host reads the humidity command frame (0x04): +> [!WARNING] +> **Both frames in this humidity section had wrong check bytes in the source we transcribed from, +> and both have been corrected below.** The request was printed as `0xC1 0xCA` but `01 04 00 02 00 +> 01` checksums to `0x90 0x0A`; the response was printed as `0xD1 0xBA` but `01 04 02 02 22` +> checksums to `0x38 0x49`. +> +> Every other frame in this file is correct, as is every frame in the relay manual. They were all +> checked against the CRC implementation in +> [serial/src/modbus/crc.ts](../serial/src/modbus/crc.ts) by `serial/src/modbus/crc.test.ts`, which +> is where the mistake surfaced. Do not "fix" these back without recomputing them. + | Slave address | function code | Register address High byte | Register address Low byte | Number of registers High byte | Number of registers Low byte | CRC High byte | CRC Low byte | | ------------- | ------------- | -------------------------- | ------------------------- | ----------------------------- | ---------------------------- | ------------- | ------------ | -| 0x01 | 0x04 | 0x00 | 0x02 | 0x00 | 0x01 | 0xC1 | 0xCA | +| 0x01 | 0x04 | 0x00 | 0x02 | 0x00 | 0x01 | 0x90 | 0x0A | #### The slave responds to the data frame: | Slave address | function code | Number of bytes | humidity High byte | humidity Low byte | CRC High byte | CRC Low byte | | ------------- | ------------- | --------------- | ------------------ | ----------------- | ------------- | ------------ | -| 0x01 | 0x04 | 0x02 | 0x02 | 0x22 | 0xD1 | 0xBA | +| 0x01 | 0x04 | 0x02 | 0x02 | 0x22 | 0x38 | 0x49 | Humidity value=0x222, converted to decimal 546, actual humidity value=546 / 10 = 54.6%; diff --git a/serial/.vscode/extensions.json b/serial/.vscode/extensions.json index bdef820..1b39e50 100644 --- a/serial/.vscode/extensions.json +++ b/serial/.vscode/extensions.json @@ -1,3 +1,3 @@ { - "recommendations": ["svelte.svelte-vscode"] + "recommendations": ["solidjs-community.solid-snippets", "vitest.explorer"] } diff --git a/serial/README.md b/serial/README.md index ceeb933..efffbe8 100644 --- a/serial/README.md +++ b/serial/README.md @@ -1,38 +1,47 @@ # serial -A browser tool for talking to the RS-485/Modbus RTU devices on this project's bus, using the -[Web Serial API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API). It opens a USB -serial adapter straight from the page, reads input registers and reads *and writes* holding -registers — which is how a device gets configured before it is wired into the fan controller. +A browser tool for the RS-485/Modbus RTU bus this project's devices sit on, using the +[Web Serial API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API). It does two +things a firmware build cannot: -It was folded in from [SantaClaas/serial](https://github.com/SantaClaas/serial), where it lived as -its own repository. +- **Watches the bus.** Decodes the traffic between the fan controller and the fans without joining + in, the way [debug-listener](../debug-listener) does from the desktop — but framed and decoded + rather than printed as bytes. +- **Talks to a device directly.** Reads input registers and reads *and writes* holding registers, + which is how a device gets its address, baud rate and correction values set before it is wired in. + +Only one of those at a time per port: watching is passive and polling is not, and a half-duplex +RS-485 line has room for one master. ## Running it ```bash -cd serial && npm install && npm run dev +cd serial && pnpm install && pnpm dev ``` +`pnpm test` runs the protocol tests, `pnpm check` type-checks, `pnpm build` does both and produces +`dist/`. + The Web Serial API is Chromium-only (Chrome, Edge, Opera) and needs a secure context, so `localhost` -or HTTPS. The app says so itself when the API is missing rather than failing silently. +or HTTPS. The app says so itself rather than failing silently. + +## Layout -`npm run check` type-checks the Svelte components. +| Path | What it is | +|---|---| +| `src/modbus/crc.ts` | The CRC-16 every RTU frame ends with. The one little-endian field in the protocol. | +| `src/modbus/pdu.ts` | Frame lengths, decoding and building, per function code. | +| `src/modbus/monitor.ts` | Recovering frames from a bus you are not driving. | +| `src/modbus/frames.fixture.ts` | Frames copied out of the device manuals, CRCs and all. | -## What it does today +Everything under `src/modbus` is plain TypeScript over bytes with no DOM in it, so it is tested +directly with `vitest`. The tests are built on frames the *manufacturers* printed, which is the +strongest check available: they agree only if this code agrees with the devices rather than merely +with itself. -- **Add Port** calls `navigator.serial.requestPort()` filtered to USB vendor `0x1A86` / product - `0x7523` — the CH340 adapter. That filter is hardcoded in `src/lib/SerialPortsList.svelte`, so a - different adapter will not show up in the picker. -- Baud rate and parity are chosen per port before opening it. The RadiCal fans want 19 200 baud with - **even** parity; the temperature sensor defaults to 9600 with **no** parity. A port can only speak - one of those at a time. -- Devices are added to an open port by Modbus address. Each device type declares its registers - once — address, length, how to decode the bytes, and for holding registers how to encode and - validate them — and the UI is generated from that declaration - (`src/lib/modbus.ts`, `src/lib/devices/`). -- Two device types are implemented: the ebm-papst RadiCal fan (`devices/fan.ts`) and the temperature - sensor (`devices/temperatureSensor.ts`). +> That is not hypothetical. The humidity frames in +> [docs/temperature-sensor.md](../docs/temperature-sensor.md) had wrong check bytes, and the test +> comparing them against `crc.ts` is what found it. Both are corrected there now. ## Where the device documentation lives @@ -42,18 +51,14 @@ or HTTPS. The app says so itself when the API is missing rather than failing sil - **Temperature sensor** — [docs/temperature-sensor.md](../docs/temperature-sensor.md). No manufacturer PDF exists for it; that file is all there is. -The register names in `devices/fan.ts` are working translations of the German manual made before -that manual was checked in, and several are flagged as guesses in the source (`PhaseControlFactor` -for *Aussteuergrad*, `CurrentDesiredEffect` for *Aktueller Wirksinn*). Prefer the terminology in the -RadiCal documentation over these. +The German terms in the RadiCal manual are the reference for what a register is called — *Aussteuergrad*, +*Wirksinn*, *Sollwert* — rather than any translation invented here. -## Status +## Stack -This is Svelte 4 on Vite 4 and is slated to be rewritten from scratch on SolidJS, adding passive -listening of bus traffic in the manner of [debug-listener](../debug-listener). Treat the current -code as reference for what the devices do, not as the shape the rewrite should take. +SolidJS 2.0 (release candidate) on Vite, with pnpm. Solid 2.0 differs from 1.x in ways that matter +when reading this code: -The GitHub Pages deployment workflow that shipped with the standalone repository was dropped during -the fold-in: GitHub only reads workflows from the repository root, so it would never have run from -`serial/.github/`. It needs to be re-created at the repository root, scoped to this directory, when -the rewrite is ready to deploy. +- `createEffect` takes **two** functions, a compute and an effect: `createEffect(() => signal(), value => …)`. + The single-argument form from 1.x is gone and is typed `never`. +- DOM rendering lives in `@solidjs/web`, not `solid-js/web`, and that is also the `jsxImportSource`. diff --git a/serial/index.html b/serial/index.html index 1de9aa6..1fc8d22 100644 --- a/serial/index.html +++ b/serial/index.html @@ -1,16 +1,13 @@ - -
+ - -