7fd407ac82
Lock down the relay device for the flat (no-VLAN) network. Relay control: - pulseOpen/setRelay now use the Dingtian BINARY protocol (:60000) with a relay password — the only relay option with auth (string :60001 has none, and is kept only for the read-only status query). Frame verified on hardware. HardenableDevice capability (driver harden()): - set a random relay_pw (1-9999); disable unused channels (rs485/can/tcp x2/mqtt -> p:255), keeping UDP1 binary (control) + UDP2 string (status). - write-verified (device reboots on apply). Assign/Save flow now does: fix preconditions -> harden -> set up input push; the relay password is stored in lane_devices so the runtime device can command the relay. DELIBERATELY NOT touching the device's HTTP CGI session check (session_en): enabling it on this firmware breaks the config-READ API (ECONNRESET) and locked the backend out — required a factory reset to recover. The open CGI API is accepted as flat-network reality; the signed event log is the real guarantee. Verified end to end on hardware: assign hardens + configures the device, config API stays reachable, pulseOpen with the stored password fires the relay, without it is rejected. wiki: device-input-flow + dingtian-relay updated.
93 lines
5.5 KiB
Markdown
93 lines
5.5 KiB
Markdown
---
|
|
type: entity
|
|
tags: [parking, hardware, access-control, relay]
|
|
sources: []
|
|
updated: 2026-06-15
|
|
---
|
|
|
|
# Dingtian Relay Controller
|
|
|
|
A network **relay + input** board (the unit on hand is the **4-channel** variant: 4 relays + 4
|
|
inputs). Chosen to drive the entry/exit lane because — unlike the [[uhppote-controller]] — its
|
|
**inputs are independent of its relays**, which solves the [[access-controller-button-flow]]
|
|
blocker (a button on an input does not auto-open a relay; the host decides).
|
|
|
|
SDK: `dingtian/4ch/sdk_v2_0_0/` (programming manual, examples). MIT-compatible use; no vendor
|
|
runtime needed.
|
|
|
|
## ⚠️ The one gotcha: `input_link_relay`
|
|
|
|
By **default the device links each input to auto-fire its matching relay** (`input_link_relay: 1`,
|
|
`on_action_on: [[0],[1],…]` in the config) — i.e. the *same* auto-open problem as the UHPPOTE.
|
|
The difference: **it is configurable.** Set `input_link_relay: 0` (or clear the action mappings)
|
|
so an input only *reports* and the host commands the relay. **This config step is mandatory** for
|
|
the ticket-first entry flow. See [[autonomous-direction]].
|
|
|
|
## Protocol (Dingtian string — what we use)
|
|
|
|
Transport options: UDP/TCP string, UDP binary, HTTP CGI, Modbus, MQTT. We use **HTTP + UDP** —
|
|
see [[dingtian-vs-mqtt]].
|
|
|
|
- **Relay control — UDP *binary*, port 60000 (authenticated):** the driver's `pulseOpen` sends a
|
|
binary "write relay with jogging" frame carrying the `relay_pw` (the only relay option with a
|
|
password). Frame (verified on hardware):
|
|
`FF AA <session> 03 <pwLo> <pwHi> <relayByte> <jogLo> <jogHi>` — relayByte bit0=on, bits1-7=
|
|
channel-1; jog is 100 ms units, LSB-first; password 16-bit LSB-first (0 = none). The relay jogs
|
|
ON then auto-releases, so we never time a close ([[barrier-not-a-door]]). *(The simpler string
|
|
protocol — `1`+ch on, `2`+ch off, `11*` jog — works too but has no auth; we use it only for the
|
|
read-only status query.)*
|
|
- **Status / inputs — send `00`** → `「relays」:「inputs」:「count」`, e.g. **`0000:1111:4`** (4ch:
|
|
relays off, inputs high). `0` = OFF/Low, `1` = ON/High. Poll-based.
|
|
- **Input push — `input_link_url`:** device **HTTP POSTs to a host URL on input change** — the
|
|
push path for button events without a broker.
|
|
- **Discovery:** UDP multicast `224.0.2.11:60000`, send `\x05\xAA` (devices reply). Defaults:
|
|
IP `192.168.1.100`, UDP `60000` (binary) / `60001` (string).
|
|
- Binary protocol (port 60000) adds optional **password** + multicast; bitmask relay/input maps.
|
|
|
|
## Driver & config API
|
|
|
|
The `dingtian` driver ([[device-registry]]) implements three capabilities:
|
|
`AccessControlDevice` (relay pulse/latch over UDP), `InputDevice` (read inputs + poll-based
|
|
press/release events ~50 ms), and `PreconditionDevice` (below). Config fields include a separate
|
|
**`httpPort`** — the device's web/config API is on a configurable HTTP port (this unit: **8080**,
|
|
not the default 80), distinct from the UDP control port 60001.
|
|
|
|
### Precondition: input_link_relay must be OFF
|
|
|
|
The driver reads the device's JSON config (`GET /api/v2/config.cgi`) and **checks
|
|
`input_link_relay`**; if enabled it reports a fixable issue, and `fixPreconditions()` writes the
|
|
correction (`POST /api/v2/config_set.cgi`) — setting the flag to 0 and clearing `on_action_on`,
|
|
preserving everything else (network, etc.). This is the generic [[device-registry|precondition]]
|
|
capability: the app doesn't own full device config (that's the vendor web UI), only the few
|
|
settings our flow depends on.
|
|
|
|
> **Write gotcha (cost real debugging):** the GET config payload **omits** a `"command"` field, but
|
|
> the set endpoint **requires `"command":"setconfig"`** injected right after `"status"`. Without it
|
|
> the POST returns/looks like success but silently does nothing (and the device may reset). With it,
|
|
> POST returns `{"status":0}` and the change sticks. JSON node order must be preserved.
|
|
|
|
## Input push (no polling) — the chosen architecture
|
|
|
|
The device **pushes** button events to the backend; the backend decides. **No polling.** The
|
|
driver's `configureInputPush()` writes the device's `input_link_url` config to point each input at
|
|
the backend: input N HTTP-GETs `…/api/devices/dingtian/<deviceId>/input/<N>/on` (and `/off`) on
|
|
press/release. The backend ([[fastify]] route `routes/devices.ts`) translates each push into an
|
|
internal device event ([[device-input-flow]]); the entry flow then prints a ticket and commands
|
|
the relay via UDP. See [[device-input-flow]] for the full path + trust model.
|
|
|
|
> The input-poll path in the driver (`onInput`) remains as a dev/fallback aid, but **push is the
|
|
> real path** — lower latency, and it can be authenticated (the device supports Basic/Digest +
|
|
> HTTPS on the push), unlike the open UDP control direction.
|
|
|
|
## Status — VERIFIED on hardware (DT-R004, sw V3.1.5461A, 10.0.10.172)
|
|
|
|
- ✅ status read (`0000:1111:4`), relay pulse, input press/release events (active-LOW, idle HIGH).
|
|
- ✅ **`input_link_relay` disabled via the driver** → pressing an input reports the event and
|
|
**fires NO relay** (`0000` after presses). The [[access-controller-button-flow]] blocker is
|
|
**solved**.
|
|
- ✅ **Input HTTP-push end to end** — configured the device via `configureInputPush()`, then real
|
|
button presses (all 4 inputs) **pushed to the backend** (`/input/N/on` + `/off` per press,
|
|
source = the device IP). No polling. Host-in-the-loop entry (`button → backend → ticket →
|
|
backend opens relay`) is real.
|
|
- ⬜ Next: wire the actual entry flow (input event → signed event + print ticket → `pulseOpen`).
|