node-red-contrib-ax25 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +5 -0
- package/.prettierignore +7 -0
- package/ARCHITECTURE.md +174 -0
- package/CONTEXT.md +90 -0
- package/MESSAGES.md +314 -0
- package/README.md +317 -0
- package/examples/beacons.json +130 -0
- package/examples/beacons.png +0 -0
- package/examples/bye_subflow.json +107 -0
- package/examples/bye_subflow.png +0 -0
- package/examples/delete_all_my_messages.json +491 -0
- package/examples/delete_all_my_messages.png +0 -0
- package/examples/get_message_list_subflow.json +129 -0
- package/examples/get_message_list_subflow.png +0 -0
- package/examples/send_message_subflow.json +367 -0
- package/examples/send_message_subflow.png +0 -0
- package/examples/send_test_message.json +643 -0
- package/examples/send_test_message.png +0 -0
- package/jsconfig.json +37 -0
- package/lib/agwpe-client-transport.js +99 -0
- package/lib/agwpe-frame-builder.js +176 -0
- package/lib/agwpe-frame-pretty.js +107 -0
- package/lib/ax25-codec.js +382 -0
- package/lib/frame-router.js +95 -0
- package/lib/frame-segmentation.js +53 -0
- package/lib/message-utils.js +59 -0
- package/lib/runtime-store.js +94 -0
- package/lib/session-registry.js +142 -0
- package/local/buffer_compare.json +135 -0
- package/local/debug-d-frame.js +84 -0
- package/local/raw-out-test.json +128 -0
- package/nodes/agwpe-client.html +70 -0
- package/nodes/agwpe-client.js +771 -0
- package/nodes/agwpe-client.js.bak +871 -0
- package/nodes/connect.html +128 -0
- package/nodes/connect.js +450 -0
- package/nodes/decode.html +83 -0
- package/nodes/decode.js +56 -0
- package/nodes/disconnect.html +55 -0
- package/nodes/disconnect.js +47 -0
- package/nodes/encode.html +117 -0
- package/nodes/encode.js +164 -0
- package/nodes/monitor-in.html +48 -0
- package/nodes/monitor-in.js +42 -0
- package/nodes/raw-in.html +50 -0
- package/nodes/raw-in.js +72 -0
- package/nodes/raw-out.html +76 -0
- package/nodes/raw-out.js +144 -0
- package/nodes/send.html +91 -0
- package/nodes/send.js +373 -0
- package/nodes/ui-in.html +64 -0
- package/nodes/ui-in.js +68 -0
- package/nodes/ui-out.html +80 -0
- package/nodes/ui-out.js +133 -0
- package/package.json +47 -0
package/.eslintignore
ADDED
package/.prettierignore
ADDED
package/ARCHITECTURE.md
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# Architecture: node-red-contrib-ax25
|
|
2
|
+
|
|
3
|
+
## Layers
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
7
|
+
│ Node-RED flow │
|
|
8
|
+
│ (inject, function, debug, etc.) │
|
|
9
|
+
└──────────────────────┬──────────────────────────────────────┘
|
|
10
|
+
│ msg
|
|
11
|
+
┌──────────────────────▼──────────────────────────────────────┐
|
|
12
|
+
│ Node layer (nodes/) │
|
|
13
|
+
│ agwpe-client · connect · send · disconnect │
|
|
14
|
+
│ ui-out · ui-in · monitor-in · raw-out · raw-in │
|
|
15
|
+
│ decode · encode │
|
|
16
|
+
└──────┬───────────────────────────┬──────────────────────────┘
|
|
17
|
+
│ transport calls │ routed frames
|
|
18
|
+
┌──────▼──────────┐ ┌────────────▼──────────────────────────┐
|
|
19
|
+
│ AgwpeClient │ │ FrameRouter │
|
|
20
|
+
│ Transport │ │ (lib/frame-router.js) │
|
|
21
|
+
│ (lib/agwpe- │ │ routes parsed frames to per-instance │
|
|
22
|
+
│ client- │ │ handler callbacks by frame kind │
|
|
23
|
+
│ transport.js) │ └──────────────┬────────────────────────┘
|
|
24
|
+
│ │ │ handler callbacks
|
|
25
|
+
│ Node.js │ ┌──────────────▼────────────────────────┐
|
|
26
|
+
│ net.Socket │ │ SessionRegistry │
|
|
27
|
+
│ EventEmitter │ │ (lib/session-registry.js) │
|
|
28
|
+
└──────┬──────────┘ │ tracks Ax25Session lifecycle per │
|
|
29
|
+
│ raw bytes │ instance; maps serverSessionId ↔ │
|
|
30
|
+
│ │ sessionId │
|
|
31
|
+
│ └───────────────────────────────────────┘
|
|
32
|
+
│
|
|
33
|
+
┌──────▼──────────────────────────────────────────────────────┐
|
|
34
|
+
│ AGWPE server (external) │
|
|
35
|
+
│ TCP port 8000 (default) │
|
|
36
|
+
└─────────────────────────────────────────────────────────────┘
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Shared lib modules
|
|
40
|
+
|
|
41
|
+
| Module | Role |
|
|
42
|
+
|--------|------|
|
|
43
|
+
| `agwpe-client-transport.js` | Wraps `net.Socket`; emits `connected`, `error`, `close`, and raw `frame` events; injectable `socketFactory` for testing |
|
|
44
|
+
| `agwpe-frame-builder.js` | Builds AGWPE binary frame headers for each frame kind (P, X, C, D, U, K, m, M) |
|
|
45
|
+
| `agwpe-frame-pretty.js` | Debug-friendly AGWPE frame formatting for transport logging |
|
|
46
|
+
| `ax25-codec.js` | AX.25 frame encode/decode (callsign bit-shifting, via path, control, PID, payload) |
|
|
47
|
+
| `frame-router.js` | Parses raw AGWPE bytes into typed frame objects; dispatches to per-instance handler maps |
|
|
48
|
+
| `frame-segmentation.js` | Splits outbound payloads into ≤255-byte chunks; attaches `messageId`/`chunkIndex`/`chunkCount` metadata |
|
|
49
|
+
| `message-utils.js` | `nowTimestamp()`, `makeMessageId()` — shared across nodes and lib |
|
|
50
|
+
| `runtime-store.js` | In-memory store for per-instance context objects; also hosts a module-level `globalBus` (EventEmitter forwarding `conn-data`, `conn-lifecycle`, `conn-timeout-set` from all instances) and a `sessionIndex` map (`sessionId → instanceId`) that lets `send` resolve the correct instance at runtime without a static client reference |
|
|
51
|
+
| `session-registry.js` | CRUD for `Ax25Session` records; indexes by `instanceId` + `sessionId` and by `serverSessionId` |
|
|
52
|
+
|
|
53
|
+
## Node internals pattern
|
|
54
|
+
|
|
55
|
+
Most nodes follow this structural pattern:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
node constructor
|
|
59
|
+
├── validate config
|
|
60
|
+
├── resolve agwpe-client instance (RED.nodes.getNode) ← all nodes except send
|
|
61
|
+
├── register handler callbacks on agwpe-client's FrameRouter
|
|
62
|
+
node.on("input", ...) ← triggers on any message
|
|
63
|
+
├── connect (connect node — uses msg.destination)
|
|
64
|
+
├── send (send, ui-out, raw-out — uses msg.payload)
|
|
65
|
+
└── disconnect (disconnect node — uses msg.sessionId)
|
|
66
|
+
|
|
67
|
+
node.on("close", ...)
|
|
68
|
+
└── deregister from FrameRouter; destroy transport if agwpe-client
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## AGWPE frame kind to Node-RED node routing
|
|
72
|
+
|
|
73
|
+
| AGWPE frame kind | Emitted to node |
|
|
74
|
+
|------------------|-----------------|
|
|
75
|
+
| D (connected data) | `connect` or `send` — matched by session callsign pair |
|
|
76
|
+
| C (connect/disconnect lifecycle) | `connect` — lifecycle event |
|
|
77
|
+
| U / K (UI/raw) | `ui-in` or `raw-in` — gated by mode flag |
|
|
78
|
+
| M / m (monitor) | `monitor-in` — gated by `monitorEnabled` flag |
|
|
79
|
+
| X (callsign registration ACK) | consumed by `agwpe-client` internally |
|
|
80
|
+
| P (login/auth) | consumed by `agwpe-client` internally |
|
|
81
|
+
|
|
82
|
+
## Session index and global bus
|
|
83
|
+
|
|
84
|
+
`runtime-store` maintains a module-level `sessionIndex` map (`sessionId → instanceId`). The `connect` node writes to this index when a session is created and removes the entry on disconnect. The `send` node uses `instanceIdForSession(sessionId)` at message-handling time to look up the correct instance — no static client reference is needed.
|
|
85
|
+
|
|
86
|
+
`runtime-store` also hosts a `globalBus` EventEmitter. Each instance `bus` forwards `conn-data`, `conn-lifecycle`, and `conn-timeout-set` events to `globalBus` so `send` nodes can subscribe once for all instances.
|
|
87
|
+
|
|
88
|
+
## connect node transport status
|
|
89
|
+
|
|
90
|
+
The `connect` node mirrors the transport state of its `agwpe-client` as a Node-RED status badge:
|
|
91
|
+
|
|
92
|
+
| agwpe-client state | connect node status |
|
|
93
|
+
|--------------------|---------------------|
|
|
94
|
+
| connecting | yellow dot — `connecting` |
|
|
95
|
+
| connected | green dot — `ready` |
|
|
96
|
+
| reconnecting | yellow ring — `reconnecting...` |
|
|
97
|
+
| disconnected | grey ring — `disconnected` |
|
|
98
|
+
| failed | red dot — `failed` |
|
|
99
|
+
|
|
100
|
+
On startup, the connect node reads `context.state` to set the initial status immediately. Ongoing changes are driven by `transport-connecting`, `transport-connected`, `transport-reconnecting`, `transport-closed`, and `failed` events on the instance bus.
|
|
101
|
+
|
|
102
|
+
## Session lifecycle state machine
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
connecting → connected → disconnecting → disconnected
|
|
106
|
+
├──────────────────────────────────────→ failed
|
|
107
|
+
└──────────────────────────────────────→ connect-failed (d-frame before C-frame confirmed)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Transitions are driven by AGWPE C-frame events and transport errors. On any transport failure all sessions in the owning instance transition to `failed` immediately.
|
|
111
|
+
|
|
112
|
+
## Message shape contracts
|
|
113
|
+
|
|
114
|
+
### connect node input
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
{ "source": "N0CALL", "destination": "REMOTE-1" }
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### send node input
|
|
121
|
+
|
|
122
|
+
```json
|
|
123
|
+
{ "sessionId": "sess-abc", "payload": "hello" }
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### disconnect node input
|
|
127
|
+
|
|
128
|
+
```json
|
|
129
|
+
{ "sessionId": "sess-abc" }
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Standard success output envelope
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{ "timestamp": "…", "status": "ok", "instanceId": "…",
|
|
136
|
+
"sessionId": "opt", "event": "connected" }
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Standard error output envelope
|
|
140
|
+
|
|
141
|
+
```json
|
|
142
|
+
{ "timestamp": "…", "status": "error",
|
|
143
|
+
"errorCode": "AUTH_FAILED", "errorText": "…",
|
|
144
|
+
"instanceId": "…", "sessionId": "opt" }
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Outbound chunk envelope (attached per segment by send, ui-out)
|
|
148
|
+
|
|
149
|
+
```json
|
|
150
|
+
{ "messageId": "msg-abc", "chunkIndex": 0, "chunkCount": 3, "payload": "…" }
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Payloads ≤255 bytes produce a single chunk (`chunkIndex: 0`, `chunkCount: 1`).
|
|
154
|
+
|
|
155
|
+
### Inbound data envelope (connect, send, ui-in)
|
|
156
|
+
|
|
157
|
+
```json
|
|
158
|
+
{ "timestamp": "…", "instanceId": "…", "sessionId": "…",
|
|
159
|
+
"event": "data", "payload": "…",
|
|
160
|
+
"source": "REMOTE-1", "destination": "N0CALL", "via": [] }
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Each received AGWPE frame is emitted immediately. No grouping metadata is added — there is no way to know whether the remote intended several frames as one logical payload.
|
|
164
|
+
|
|
165
|
+
## Testability design
|
|
166
|
+
|
|
167
|
+
- `AgwpeClientTransport` accepts an injectable `socketFactory` — tests pass a fake factory that returns a mock socket
|
|
168
|
+
- `FrameRouter` and `SessionRegistry` are stateless relative to the transport and can be instantiated and exercised independently
|
|
169
|
+
- `node-red-node-test-helper` is used for all integration-level tests; no live AGWPE server required
|
|
170
|
+
|
|
171
|
+
## Extension points for future work
|
|
172
|
+
|
|
173
|
+
- **Inbound reassembly**: an optional reassembly buffer keyed on a session + sequence, with a configurable flush timeout
|
|
174
|
+
- **TLS transport**: replacing `net.createConnection` with `tls.connect` via the existing `socketFactory` injection point
|
package/CONTEXT.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Project Context: node-red-contrib-ax25
|
|
2
|
+
|
|
3
|
+
## What this is
|
|
4
|
+
|
|
5
|
+
A Node-RED contrib package that enables AX.25 packet radio connectivity through an AGWPE (AGW Packet Engine) server. Radio amateurs use it to integrate packet radio into Node-RED flows — connecting to BBSs, digipeaters, and other AX.25 stations.
|
|
6
|
+
|
|
7
|
+
The package provides ten custom nodes covering connected sessions, unconnected UI frames, monitor mode, raw frames, and AX.25 encode/decode. It manages two distinct connection layers: one TCP connection to the AGWPE server, and multiple concurrent AX.25 protocol sessions to remote stations.
|
|
8
|
+
|
|
9
|
+
## Target users
|
|
10
|
+
|
|
11
|
+
Radio amateurs and enthusiasts running Node-RED who want to interact with AX.25 packet radio networks without writing socket code.
|
|
12
|
+
|
|
13
|
+
## Runtime environment
|
|
14
|
+
|
|
15
|
+
- Node.js 20 LTS, CommonJS modules
|
|
16
|
+
- Node-RED 3.x+
|
|
17
|
+
- No external runtime dependencies (only Node.js `net` and `events`)
|
|
18
|
+
|
|
19
|
+
## Key design decisions
|
|
20
|
+
|
|
21
|
+
**Auto-reconnect is on by default.** When the AGWPE transport drops, all owned sessions are marked disconnected and the node automatically attempts to reconnect after a configurable delay (default 5000 ms). Auto-reconnect can be disabled via the "Auto-Reconnect" checkbox in the config editor. When disabled, all sessions fail and the flow must re-deploy or restart to reconnect.
|
|
22
|
+
|
|
23
|
+
**`agwpe-client` is a config node.** It does not receive messages. All configuration (host, port, callsigns, auth, monitor/raw mode, reconnect settings) is set in the Node-RED editor. The connection is established automatically when the flow deploys. There is no `open`/`close` command interface.
|
|
24
|
+
|
|
25
|
+
**Per-instance connection ownership.** Each `agwpe-client` node manages one AGWPE TCP connection. All downstream nodes (`conn-out`, `conn-in`, `ui-in`, `ui-out`, `monitor-in`, `raw-in`, `raw-out`) bind to a specific `agwpe-client` instance by `instanceId`. Cross-instance routing is not allowed.
|
|
26
|
+
|
|
27
|
+
**Inbound data follows the last active node.** Received data for a connected session is routed to whichever `connect` or `send` node most recently processed a command for that session. When a `send` node handles a command, it takes over the data output for that session; when the session is first established by `connect`, data goes to `connect`'s output. This lets flows chain request/response steps naturally: each node in the chain receives the reply to its own send.
|
|
28
|
+
|
|
29
|
+
**Binary and line modes.** The `connect` node's `mode` setting (default: `line`) controls how inbound data is delivered on output port 2:
|
|
30
|
+
|
|
31
|
+
- **binary** — each received frame emitted immediately; `payload` is a Buffer.
|
|
32
|
+
- **line** — fragments are buffered and split on CR or CR+LF; each complete line is emitted with `payload` as a string. If `waitFor` (a regex) is set, lines accumulate until one matches; the output message then contains `payload` (array of preceding lines) and `match` (the matching line). If the timeout fires before a match, a `timeout` event is emitted on port 1 along with whatever has buffered so far.
|
|
33
|
+
|
|
34
|
+
`mode` and `waitFor` can be set on the node and overridden per-message via `msg.mode` and `msg.waitFor`.
|
|
35
|
+
|
|
36
|
+
**Payload output type.** Several nodes have a `payloadOutput` editor option (`"string"` or `"buffer"`, default `"string"`) that controls whether the AX.25 data payload is delivered as a UTF-8 string or a raw Buffer:
|
|
37
|
+
|
|
38
|
+
- `ui-in` — decoded AX.25 UI frame payload
|
|
39
|
+
- `decode` — decoded AX.25 frame payload
|
|
40
|
+
|
|
41
|
+
`raw-in` always emits payload as a Buffer (raw AX.25 wire bytes, leading AGWPE port byte stripped). `connect` and `send` payload type is governed by `mode` as described above.
|
|
42
|
+
|
|
43
|
+
**Outbound segmentation at 255 bytes.** Payloads larger than 255 bytes are segmented into multiple AGWPE frames before transmission.
|
|
44
|
+
|
|
45
|
+
**Optional auth.** `username` and `password` fields in the config node editor pass AGWPE-level credentials. Many LAN deployments omit them.
|
|
46
|
+
|
|
47
|
+
## Message contract summary
|
|
48
|
+
|
|
49
|
+
All output messages include `timestamp` (ISO-8601). Errors always include `errorCode` and `errorText`. Connected session events include `instanceId`, `sessionId`, and `event` (`connected`, `disconnecting`, `disconnected`, `failed`, `timeout`). Inbound data messages include `instanceId`, `sessionId`, `event: "data"`, `payload`, `source`, `destination`, and `via`. Outbound segmented sends attach `messageId`, `chunkIndex`, and `chunkCount` to each chunk.
|
|
50
|
+
|
|
51
|
+
Full contract details are in [README.md](README.md).
|
|
52
|
+
|
|
53
|
+
## Node list
|
|
54
|
+
|
|
55
|
+
| Node | Direction | Role |
|
|
56
|
+
|------|-----------|------|
|
|
57
|
+
| `agwpe-client` | config | Owns the AGWPE TCP connection; configured in the editor; connects on deploy |
|
|
58
|
+
| `connect` | in + 2 out | Establish an AX.25 session; output 1: lifecycle events; output 2: received data |
|
|
59
|
+
| `send` | in + 2 out | Send data or disconnect on an established session; output 1: events; output 2: received data |
|
|
60
|
+
| `ui-out` | data in | Encode and send AX.25 UI frames via AGWPE K raw transport |
|
|
61
|
+
| `ui-in` | data out | Decode incoming AGWPE K raw traffic to AX.25 UI frame fields |
|
|
62
|
+
| `monitor-in` | data out | Passive monitor stream (monitor mode must be enabled) |
|
|
63
|
+
| `raw-out` | data in | Send raw AGWPE frames (raw mode must be enabled) |
|
|
64
|
+
| `raw-in` | data out | Receive raw AGWPE frames (raw mode must be enabled) |
|
|
65
|
+
| `decode` | transform | AX.25 frame bytes → JSON (source, destination, via, control, PID, payload) |
|
|
66
|
+
| `encode` | transform | Structured JSON → raw AX.25 frame bytes |
|
|
67
|
+
|
|
68
|
+
## Test strategy
|
|
69
|
+
|
|
70
|
+
Three test layers under `test/`:
|
|
71
|
+
|
|
72
|
+
- **contract/** — message shape and field validation, independent of Node-RED wiring
|
|
73
|
+
- **integration/** — full node loading + wiring with `node-red-node-test-helper`; covers open/close flows, session lifecycle, routing, segmentation, mode toggling
|
|
74
|
+
- **unit/** — isolated lib module behavior (codec, segmentation, router, registry)
|
|
75
|
+
|
|
76
|
+
Transport boundaries are mocked; no live AGWPE server is required for any test.
|
|
77
|
+
|
|
78
|
+
## Project layout
|
|
79
|
+
|
|
80
|
+
```text
|
|
81
|
+
nodes/ runtime JS + editor HTML for all ten node types
|
|
82
|
+
lib/ shared internals (transport, router, registry, codec, segmentation)
|
|
83
|
+
test/ contract / integration / unit test suites
|
|
84
|
+
examples/ importable Node-RED example flows
|
|
85
|
+
docs/ development guidance (AI working agreement, Node-RED patterns)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Known limitations and deferred work
|
|
89
|
+
|
|
90
|
+
- No TLS support for the AGWPE TCP transport
|
package/MESSAGES.md
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
# Node Messages Reference
|
|
2
|
+
|
|
3
|
+
All messages include a `timestamp` (ISO 8601 string).
|
|
4
|
+
Messages with `status: "ok"` are built by `okEnvelope(fields)`.
|
|
5
|
+
Messages with `status: "error"` are built by `errorEnvelope(errorCode, errorText, fields)` and always include `errorCode` and `errorText`.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## agwpe-client
|
|
10
|
+
|
|
11
|
+
Configuration node. No inputs or outputs.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## connect
|
|
16
|
+
|
|
17
|
+
Initiates an AX.25 connected session. Two outputs: **output 1** (events), **output 2** (data).
|
|
18
|
+
|
|
19
|
+
### Input
|
|
20
|
+
|
|
21
|
+
| Field | Type | Required | Description |
|
|
22
|
+
|---|---|---|---|
|
|
23
|
+
| `source` | string | no¹ | Calling callsign. Falls back to node config, then first registered callsign. |
|
|
24
|
+
| `destination` | string | no¹ | Called callsign. Falls back to node config. |
|
|
25
|
+
| `via` | string \| string[] | no | Digipeater path. Falls back to node config. |
|
|
26
|
+
| `sessionId` | string | no | Override the auto-generated session ID. |
|
|
27
|
+
| `mode` | `"line"` \| `"binary"` | no | Data framing mode. Falls back to node config (default: `"line"`). |
|
|
28
|
+
| `timeout` | number | no | Inactivity timeout in milliseconds. Falls back to node config. |
|
|
29
|
+
| `waitFor` | string | no | Regex pattern. Buffers inbound lines until a match; then emits all at once. Falls back to node config. |
|
|
30
|
+
|
|
31
|
+
¹ Required unless provided by node config or registered callsign.
|
|
32
|
+
|
|
33
|
+
### Output 1 — events
|
|
34
|
+
|
|
35
|
+
#### `status: "ok"` messages
|
|
36
|
+
|
|
37
|
+
| `event` | Additional fields | When |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| `connecting` | `instanceId`, `sessionId`, `source`, `destination`, `via` | C frame sent to TNC |
|
|
40
|
+
| `connected` | `instanceId`, `sessionId`, `source`, `destination`, `called` | TNC confirmed connection |
|
|
41
|
+
| `disconnecting` | `instanceId`, `sessionId`, `source`, `destination` | Disconnect initiated |
|
|
42
|
+
| `disconnected` | `instanceId`, `sessionId`, `source`, `destination` | TNC confirmed disconnect |
|
|
43
|
+
|
|
44
|
+
#### `status: "error"` messages
|
|
45
|
+
|
|
46
|
+
| `errorCode` | `errorText` | Additional fields | When |
|
|
47
|
+
|---|---|---|---|
|
|
48
|
+
| `CLIENT_NOT_CONNECTED` | `AGWPE Client is not open` | `instanceId` | Input received while transport is not connected |
|
|
49
|
+
| `CONNECT_INVALID` | `connect requires source and destination` | `instanceId` | Source or destination could not be resolved |
|
|
50
|
+
| `SESSION_ID_CONFLICT` | `Session already exists` | `instanceId` | Supplied `sessionId` is already in use |
|
|
51
|
+
| `SESSION_ID_REUSED` | `Server session ID collision detected` | `instanceId`, `sessionId`, `serverSessionId` | TNC reused a session ID for a different session |
|
|
52
|
+
| `CONNECT_FAILED` | `Connection attempt failed` | `instanceId`, `sessionId`, `source`, `destination` | TNC sent a `d` frame before ever confirming with a `C` frame |
|
|
53
|
+
| `TIMEOUT` | `Inactivity timeout` | `instanceId`, `sessionId`, `event: "timeout"` | Inactivity timer expired (binary mode) |
|
|
54
|
+
| `TIMEOUT` | `Inactivity timeout` | `instanceId`, `sessionId`, `event: "timeout"`, `waitFor`, `lineBuffer`, `waitForBuffer` | waitFor timer expired before the regex matched (line mode) |
|
|
55
|
+
|
|
56
|
+
> **Note:** Timeout messages may be emitted by a **send** node instead of the connect node if a send node currently holds the output claim for the session.
|
|
57
|
+
|
|
58
|
+
### Output 2 — data
|
|
59
|
+
|
|
60
|
+
All data messages have `status: "ok"`, `event: "data"`, `instanceId`, `sessionId`, `source`, `destination`, `via`.
|
|
61
|
+
|
|
62
|
+
| Mode | `payload` type | `match` field | When |
|
|
63
|
+
|---|---|---|---|
|
|
64
|
+
| `binary` | `Buffer` | — | Inbound D frame received |
|
|
65
|
+
| `line` (no waitFor) | `string` | — | One complete line received (CR or CR+LF delimited) |
|
|
66
|
+
| `line` (waitFor active) | `string[]` — lines before the match | `string` — the matching line or fragment | First line matching the waitFor regex arrives |
|
|
67
|
+
| `line` (disconnect flush) | `string[]` — buffered lines | — | Session disconnects while lines are buffered |
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## send
|
|
72
|
+
|
|
73
|
+
Sends data over an existing AX.25 connected session. Two outputs: **output 1** (events), **output 2** (data).
|
|
74
|
+
|
|
75
|
+
### Input
|
|
76
|
+
|
|
77
|
+
| Field | Type | Required | Description |
|
|
78
|
+
|---|---|---|---|
|
|
79
|
+
| `sessionId` | string | yes | ID of the target session |
|
|
80
|
+
| `payload` | string \| Buffer \| Array<string\|Buffer> | yes | Data to send. Arrays are sent as separate D frames. |
|
|
81
|
+
| `waitFor` | string | no | Regex pattern. Claims data output and buffers until a match. Falls back to node config. |
|
|
82
|
+
| `timeout` | number | no | Override inactivity timeout in milliseconds. Falls back to node config. |
|
|
83
|
+
|
|
84
|
+
### Output 1 — events
|
|
85
|
+
|
|
86
|
+
#### `status: "ok"` messages
|
|
87
|
+
|
|
88
|
+
| `event` | Additional fields | When |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| `sent` | `instanceId`, `sessionId`, `messageId`, `chunkCount` | All payload items delivered to TNC |
|
|
91
|
+
|
|
92
|
+
#### `status: "error"` messages
|
|
93
|
+
|
|
94
|
+
| `errorCode` | `errorText` | Additional fields | When |
|
|
95
|
+
|---|---|---|---|
|
|
96
|
+
| `SESSION_NOT_FOUND` | `Session not found` | `sessionId` | `msg.sessionId` not in the session registry |
|
|
97
|
+
| `SESSION_NOT_CONNECTED` | `Session is not connected` | `sessionId` | Session exists but state is not `"connected"` |
|
|
98
|
+
| `PAYLOAD_INVALID` | `payload items must be string or Buffer` | `sessionId` | A payload item is not a string or Buffer |
|
|
99
|
+
| `TIMEOUT` | `Inactivity timeout` | (same as connect timeout) | Timer fired while this node holds the output claim |
|
|
100
|
+
|
|
101
|
+
### Output 2 — data
|
|
102
|
+
|
|
103
|
+
Same shape as connect output 2. The send node takes the data output claim when it processes an input; data arrives on this output until the session disconnects or a different send node claims it.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## disconnect
|
|
108
|
+
|
|
109
|
+
Initiates graceful disconnect of an existing session. One output (events).
|
|
110
|
+
|
|
111
|
+
### Input
|
|
112
|
+
|
|
113
|
+
| Field | Type | Required | Description |
|
|
114
|
+
|---|---|---|---|
|
|
115
|
+
| `sessionId` | string | yes | ID of the session to disconnect |
|
|
116
|
+
|
|
117
|
+
### Output — events
|
|
118
|
+
|
|
119
|
+
#### `status: "ok"` messages
|
|
120
|
+
|
|
121
|
+
| `event` | Additional fields | When |
|
|
122
|
+
|---|---|---|
|
|
123
|
+
| `disconnecting` | `instanceId`, `sessionId` | Disconnect frame sent to TNC |
|
|
124
|
+
|
|
125
|
+
#### `status: "error"` messages
|
|
126
|
+
|
|
127
|
+
| `errorCode` | `errorText` | Additional fields | When |
|
|
128
|
+
|---|---|---|---|
|
|
129
|
+
| `SESSION_NOT_FOUND` | `Session not found` | `sessionId` | `msg.sessionId` not in the session registry |
|
|
130
|
+
|
|
131
|
+
> The corresponding `disconnected` event is emitted by the **connect** node when the TNC confirms with a `d` frame.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## raw-in
|
|
136
|
+
|
|
137
|
+
Receives raw AX.25 frames (K frames) from the TNC. No input. One output.
|
|
138
|
+
|
|
139
|
+
Raw mode must be enabled on the agwpe-client node.
|
|
140
|
+
|
|
141
|
+
### Output
|
|
142
|
+
|
|
143
|
+
| `status` | `event` | Fields | Description |
|
|
144
|
+
|---|---|---|---|
|
|
145
|
+
| `ok` | `raw` | `instanceId`, `payload` (Buffer), `agwpePort`, `source`, `destination`, `via` | Raw AX.25 wire frame received from TNC |
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## raw-out
|
|
150
|
+
|
|
151
|
+
Transmits a raw AX.25 frame (K frame) via the TNC. One output.
|
|
152
|
+
|
|
153
|
+
Raw mode must be enabled on the agwpe-client node.
|
|
154
|
+
|
|
155
|
+
### Input
|
|
156
|
+
|
|
157
|
+
| Field | Type | Required | Description |
|
|
158
|
+
|---|---|---|---|
|
|
159
|
+
| `payload` | Buffer \| Uint8Array \| byte array \| hex string \| encode envelope | yes | AX.25 wire frame to transmit |
|
|
160
|
+
| `agwpePort` | number | no | AGWPE port (0–255). Falls back to `msg.flag`, then node config (default: `0`). |
|
|
161
|
+
|
|
162
|
+
### Output
|
|
163
|
+
|
|
164
|
+
#### `status: "ok"` messages
|
|
165
|
+
|
|
166
|
+
| `event` | Additional fields | When |
|
|
167
|
+
|---|---|---|
|
|
168
|
+
| `raw-sent` | `instanceId` | Frame handed to transport |
|
|
169
|
+
|
|
170
|
+
#### `status: "error"` messages
|
|
171
|
+
|
|
172
|
+
| `errorCode` | `errorText` | When |
|
|
173
|
+
|---|---|---|
|
|
174
|
+
| `CLIENT_NOT_FOUND` | `AGWPE Client instance not found` | agwpe-client config node is missing |
|
|
175
|
+
| `RAW_MODE_DISABLED` | `Raw mode is disabled` | Raw mode not enabled on agwpe-client |
|
|
176
|
+
| `RAW_FRAME_INVALID` | `Raw frame payload/agwpePort is invalid; payload must be Buffer, byte array, Uint8Array, hex string, or encode envelope` | Payload could not be parsed or `agwpePort` is out of range |
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## monitor-in
|
|
181
|
+
|
|
182
|
+
Receives monitored AX.25 frames from the TNC (all traffic, not just connected sessions). No input. One output.
|
|
183
|
+
|
|
184
|
+
Monitor mode must be enabled on the agwpe-client node.
|
|
185
|
+
|
|
186
|
+
### Output
|
|
187
|
+
|
|
188
|
+
| `status` | `event` | Fields | Description |
|
|
189
|
+
|---|---|---|---|
|
|
190
|
+
| `ok` | `monitor` | `instanceId`, `payload` (Buffer), `source`, `destination`, `via` | Monitored frame received |
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## encode
|
|
195
|
+
|
|
196
|
+
Encodes an AX.25 frame from field inputs into a wire-format Buffer. One output.
|
|
197
|
+
|
|
198
|
+
### Input
|
|
199
|
+
|
|
200
|
+
| Field | Type | Required | Description |
|
|
201
|
+
|---|---|---|---|
|
|
202
|
+
| `source` | string | no¹ | Source callsign. Falls back to node config (default: `"N0CALL"`). |
|
|
203
|
+
| `destination` | string | no¹ | Destination callsign. Falls back to node config (default: `"CQ"`). |
|
|
204
|
+
| `via` | string \| string[] \| object[] | no | Digipeater path. Falls back to node config. |
|
|
205
|
+
| `frameType` | `"I"` \| `"S"` \| `"U"` | no | Determines control byte if `control` is not set. Falls back to node config (default: `"U"`). |
|
|
206
|
+
| `control` | number (0–255) | no | Explicit control byte. Overrides `frameType`. Falls back to node config (default: `3`). |
|
|
207
|
+
| `pid` | number (0–255) | no | Protocol identifier byte. Falls back to node config (default: `240` / `0xF0`). |
|
|
208
|
+
| `payload` | string \| Buffer | no | Frame payload. Falls back to node config. |
|
|
209
|
+
| `agwpePort` | number | no | Passed through to output for use by raw-out. |
|
|
210
|
+
|
|
211
|
+
¹ Required unless provided by node config.
|
|
212
|
+
|
|
213
|
+
### Output
|
|
214
|
+
|
|
215
|
+
#### `status: "ok"` messages
|
|
216
|
+
|
|
217
|
+
| `event` | Additional fields | When |
|
|
218
|
+
|---|---|---|
|
|
219
|
+
| `encoded` | `payload` (Buffer), `agwpePort` | Frame successfully encoded |
|
|
220
|
+
|
|
221
|
+
#### `status: "error"` messages
|
|
222
|
+
|
|
223
|
+
| `errorCode` | `errorText` | When |
|
|
224
|
+
|---|---|---|
|
|
225
|
+
| `ENCODE_INPUT_INVALID` | `source, destination, and control/frameType are required` | Required fields missing |
|
|
226
|
+
| `ENCODE_FAILED` | codec error message | Codec rejected the input |
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## decode
|
|
231
|
+
|
|
232
|
+
Decodes a wire-format AX.25 frame Buffer into structured fields. One output.
|
|
233
|
+
|
|
234
|
+
### Input
|
|
235
|
+
|
|
236
|
+
| Field | Type | Required | Description |
|
|
237
|
+
|---|---|---|---|
|
|
238
|
+
| `payload` | Buffer | yes | AX.25 wire-format frame |
|
|
239
|
+
| `agwpePort` | number | no | Passed through to output. Falls back to `msg.agwpePrefix`, then `0`. |
|
|
240
|
+
|
|
241
|
+
### Output
|
|
242
|
+
|
|
243
|
+
#### `status: "ok"` messages
|
|
244
|
+
|
|
245
|
+
Fields from the decoded frame, plus:
|
|
246
|
+
|
|
247
|
+
| Field | Type | Description |
|
|
248
|
+
|---|---|---|
|
|
249
|
+
| `status` | `"ok"` | |
|
|
250
|
+
| `agwpePort` | number | Normalized AGWPE port |
|
|
251
|
+
| `source` | string | Source callsign |
|
|
252
|
+
| `destination` | string | Destination callsign |
|
|
253
|
+
| `via` | object[] | Digipeater path |
|
|
254
|
+
| `control` | number | Control byte |
|
|
255
|
+
| `pid` | number | PID byte |
|
|
256
|
+
| `payload` | string \| Buffer | Frame payload (string if node config `payloadOutput` is `"string"`) |
|
|
257
|
+
|
|
258
|
+
#### `status: "error"` messages
|
|
259
|
+
|
|
260
|
+
| `errorCode` | `errorText` | When |
|
|
261
|
+
|---|---|---|
|
|
262
|
+
| `DECODE_INPUT_INVALID` | `payload must be Buffer` | Input payload is not a Buffer |
|
|
263
|
+
| `DECODE_FAILED` | codec error message | Codec rejected the frame |
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## ui-in
|
|
268
|
+
|
|
269
|
+
Receives UI (unproto) AX.25 frames from raw traffic. No input. One output.
|
|
270
|
+
|
|
271
|
+
Raw mode must be enabled on the agwpe-client node.
|
|
272
|
+
|
|
273
|
+
### Output
|
|
274
|
+
|
|
275
|
+
| `status` | `event` | Fields | Description |
|
|
276
|
+
|---|---|---|---|
|
|
277
|
+
| `ok` | `ui` | `instanceId`, `source`, `destination`, `via`, `payload` (string or Buffer) | UI frame received (`payload` is string if node config `payloadOutput` is `"string"`) |
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## ui-out
|
|
282
|
+
|
|
283
|
+
Transmits a UI (unproto) AX.25 frame via the TNC. One output.
|
|
284
|
+
|
|
285
|
+
Raw mode must be enabled on the agwpe-client node.
|
|
286
|
+
|
|
287
|
+
### Input
|
|
288
|
+
|
|
289
|
+
| Field | Type | Required | Description |
|
|
290
|
+
|---|---|---|---|
|
|
291
|
+
| `source` | string | no¹ | Source callsign. Falls back to node config. |
|
|
292
|
+
| `destination` | string | no¹ | Destination callsign. Falls back to node config. |
|
|
293
|
+
| `via` | string \| string[] \| object[] | no | Digipeater path. Falls back to node config. |
|
|
294
|
+
| `payload` | string \| Buffer | no¹ | Frame payload. Falls back to node config. |
|
|
295
|
+
| `agwpePort` | number | no | AGWPE port. Falls back to `0`. |
|
|
296
|
+
|
|
297
|
+
¹ Required unless provided by node config.
|
|
298
|
+
|
|
299
|
+
### Output
|
|
300
|
+
|
|
301
|
+
#### `status: "ok"` messages
|
|
302
|
+
|
|
303
|
+
| `event` | Additional fields | When |
|
|
304
|
+
|---|---|---|
|
|
305
|
+
| `ui-sent` | `instanceId` | UI frame handed to transport |
|
|
306
|
+
|
|
307
|
+
#### `status: "error"` messages
|
|
308
|
+
|
|
309
|
+
| `errorCode` | `errorText` | When |
|
|
310
|
+
|---|---|---|
|
|
311
|
+
| `CLIENT_NOT_FOUND` | `AGWPE Client instance not found` | agwpe-client config node is missing |
|
|
312
|
+
| `RAW_MODE_DISABLED` | `Raw mode is disabled` | Raw mode not enabled on agwpe-client |
|
|
313
|
+
| `UI_SEND_INVALID` | `ui-out requires source, destination, and payload (set in editor or input message)` | Required fields missing or empty |
|
|
314
|
+
| `UI_SEND_INVALID` | codec error message | Frame encoding failed |
|