@ham2k/extension-sdk 0.12.13 → 0.12.14

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/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- // @ham2k/extension-sdk 0.12.13
1
+ // @ham2k/extension-sdk 0.12.14
2
2
  /** Experimental v1 panel scene contract. All coordinates are in the scene's viewBox. */
3
3
  export interface PanelScene {
4
4
  version: 1;
@@ -1537,6 +1537,7 @@ export interface ExtensionManifest {
1537
1537
  requiresLocation?: boolean;
1538
1538
  requiresRadioRead?: boolean;
1539
1539
  requiresRadioWrite?: boolean;
1540
+ requiresRadioRaw?: boolean;
1540
1541
  requiresClusterRead?: boolean;
1541
1542
  requiresClusterSpot?: boolean;
1542
1543
  requiresClusterCommand?: boolean;
@@ -2095,15 +2096,44 @@ export interface PanelRadioSet extends PanelRadioTarget {
2095
2096
  }
2096
2097
  /** @deprecated `setRadio`'s argument through extension API 6. */
2097
2098
  export type PanelRadioTune = PanelRadioSet;
2099
+ /**
2100
+ * Commands in the radio's own protocol — exactly one of `text` or `bytes`, up to 4096 bytes
2101
+ * (extension API 11). Where the app's driver keeps one question on the wire (CI-V, Yaesu binary,
2102
+ * HRD, Commander, flrig) each command waits its turn in that queue, and on a FlexRadio is numbered
2103
+ * like the driver's own; the reply is then the answers to these commands alone. Elsewhere the bytes
2104
+ * are written as given. See hooks.md for what each protocol takes.
2105
+ */
2106
+ export interface PanelRadioRaw extends PanelRadioTarget {
2107
+ /** Sent as UTF-8: Kenwood, Yaesu and Elecraft ASCII, rigctld, N3FJP, SmartSDR, HRD, Commander, flrig. */
2108
+ text?: string;
2109
+ /** Each 0…255: Icom CI-V, Yaesu's binary protocol. */
2110
+ bytes?: number[];
2111
+ /**
2112
+ * How long to wait for the reply, up to 2000 ms; omitted or 0, the call answers once the commands
2113
+ * are handed to the driver. Where they are written as given, the reply is everything the radio
2114
+ * sent in that time — the answers to the app's own polls included.
2115
+ */
2116
+ replyMs?: number;
2117
+ /** Ends the reply early, just after the first occurrence of this text or these bytes. Needs `replyMs`. */
2118
+ until?: string | number[];
2119
+ }
2120
+ export interface PanelRadioRawResult extends PanelRadioResult {
2121
+ /** What came back within `replyMs`, possibly nothing, at most 64 KiB; absent without `replyMs`. `text` is it as UTF-8. */
2122
+ reply?: {
2123
+ text: string;
2124
+ bytes: number[];
2125
+ };
2126
+ }
2098
2127
  export interface PanelRadioResult {
2099
2128
  /** Accepted by the host; confirmation still comes from reported state. */
2100
2129
  accepted: boolean;
2101
2130
  /**
2102
2131
  * `stopped`: a scope change while no display has the scope running — nothing was sent.
2132
+ * `busy`: eight raw commands to this radio are already waiting (extension API 11).
2103
2133
  * `unsupported`: this radio, or the app's keying, cannot do it — keying calls on a radio that
2104
2134
  * is not the logging radio, or with the app's keying off.
2105
2135
  */
2106
- reason?: "changed" | "disconnected" | "transmitting" | "invalid" | "stopped" | "unsupported";
2136
+ reason?: "changed" | "disconnected" | "transmitting" | "invalid" | "stopped" | "unsupported" | "busy";
2107
2137
  state: PanelRadioState | null;
2108
2138
  }
2109
2139
  /** @deprecated `PanelRadioResult`'s name through extension API 6. */
@@ -2565,6 +2595,11 @@ export declare const host: {
2565
2595
  }): Promise<PanelRadioResult>;
2566
2596
  /** Stops all CW, voice and PTT the app is keying, and a tune it started (extension API 9), as Escape does. Needs `requiresRadioWrite` and extension API 7. */
2567
2597
  stopRadioTransmit(args: PanelRadioTarget): Promise<PanelRadioResult>;
2598
+ /**
2599
+ * Writes any command in the radio's own protocol — one that transmits included — and collects the
2600
+ * reply for `replyMs`. Needs `requiresRadioRaw` and extension API 11.
2601
+ */
2602
+ sendRadioRaw(args: PanelRadioRaw): Promise<PanelRadioRawResult>;
2568
2603
  /** The app's DX cluster connection, as Settings shows it. Needs `requiresClusterRead` and extension API 10. */
2569
2604
  clusterStatus(): Promise<ClusterStatus>;
2570
2605
  /** The node's recent lines after `after`, oldest first, at most `limit` of the newest. Needs `requiresClusterRead` and extension API 10. */
package/dist/index.js CHANGED
@@ -197,6 +197,13 @@ const host = {
197
197
  async stopRadioTransmit(args) {
198
198
  return await hostCallFn()("stopRadioTransmit", { ...args });
199
199
  },
200
+ /**
201
+ * Writes any command in the radio's own protocol — one that transmits included — and collects the
202
+ * reply for `replyMs`. Needs `requiresRadioRaw` and extension API 11.
203
+ */
204
+ async sendRadioRaw(args) {
205
+ return await hostCallFn()("sendRadioRaw", { ...args });
206
+ },
200
207
  /** The app's DX cluster connection, as Settings shows it. Needs `requiresClusterRead` and extension API 10. */
201
208
  async clusterStatus() {
202
209
  return await hostCallFn()("clusterStatus", {});
@@ -573,7 +573,7 @@ counts as the catalog's own here. The row and the details show the track, and
573
573
  the details' **Follow stable releases only** puts it back on stable; an
574
574
  uninstall or a file install clears it with the rest of the record.
575
575
 
576
- At most once every six hours — on launch and on resume — the app asks the
576
+ At most once a day — on launch and on resume — the app asks the
577
577
  catalog whether anything installed has a newer release, and says so in the
578
578
  status bar and on the row; the notice comes down with the last update taken.
579
579
  It never installs unasked: the notice's **Update all** is the panel's own,
package/docs/hooks.md CHANGED
@@ -2115,7 +2115,9 @@ same shape as `requiresLocation`, and shown to the operator at install:
2115
2115
  `host.setRadioConnection()` and `host.setRadioSpectrum()` — and transmitting
2116
2116
  with them: `host.sendRadioCw()`, `host.playRadioVoice()`, `host.setRadioPtt()`
2117
2117
  and `host.stopRadioTransmit()` — and implies read, since a command answers with the
2118
- radio's state. An undeclared call rejects rather than answering null.
2118
+ radio's state. `"requiresRadioRaw": true` (extension API 11) also allows
2119
+ `host.sendRadioRaw()`, any command in the radio's own protocol, and implies
2120
+ write. An undeclared call rejects rather than answering null.
2119
2121
  A capability belongs to the bundle that declared it, and to the sub-extensions
2120
2122
  that bundle defines under its own key. A separately installed bundle never
2121
2123
  inherits one through its name: `rigpanel-themes` gets nothing from `rigpanel`.
@@ -2231,6 +2233,61 @@ not paced, and not refused for a transmission in progress.
2231
2233
  connection using the same selection and identity checks. Acceptance starts the
2232
2234
  operation; follow reported connection state for completion. It does not switch
2233
2235
  the hardware's mains power or initiate transmission.
2236
+
2237
+ `host.sendRadioRaw({id, selection?, text | bytes, replyMs?, until?})`
2238
+ (extension API 11, `requiresRadioRaw`) sends commands in the radio's own
2239
+ protocol — exactly one of `text`, sent as UTF-8, or `bytes`, each 0…255, up
2240
+ to 4096 bytes. **Nothing about them is checked.** They may transmit, change
2241
+ any setting or put the radio somewhere the app's own controls never would;
2242
+ that is what the operator agreed to at install, where the grant is shown in
2243
+ those words. It reaches any local radio through the same `id`/`selection`
2244
+ check as `setRadio`, with keying on or off, and is neither paced nor refused
2245
+ for a transmission in progress. One radio's calls go one at a time, in order,
2246
+ so that one call's reply window never holds the next one's answer, and past
2247
+ eight waiting the next is refused as `busy`; the app's own polls may come
2248
+ between two calls.
2249
+
2250
+ - `replyMs`, up to 2000: how long to wait for the reply. Omitted or 0, the
2251
+ call answers as soon as the commands are handed to the driver, with no
2252
+ `reply`.
2253
+ - `until`, text or bytes, needs `replyMs`: the reply ends just after its first
2254
+ occurrence.
2255
+ - The answer carries `reply: {text, bytes}`, possibly empty, at most 64 KiB,
2256
+ with `text` its UTF-8 reading.
2257
+
2258
+ What the commands are, and what the reply holds, is the protocol's. Where the
2259
+ app's driver keeps one question on the wire and reads the next reply as its
2260
+ answer, a raw command waits its turn in that queue — written beside it, its
2261
+ answer would be read as the answer to the driver's question — or, on a
2262
+ FlexRadio, is numbered as the driver's own are. The reply is then the answers
2263
+ to the raw commands alone, as the radio sent them, once each has come or the
2264
+ window closes:
2265
+
2266
+ | Radio | Commands | Reply |
2267
+ |---|---|---|
2268
+ | Icom CI-V | `bytes`: whole frames, `FE FE` … `FD`, one at a time; the app addresses each to the radio from itself, whatever its address bytes say | each frame's answer frame (`FB`/`FA` or its data) |
2269
+ | Yaesu binary (FT-817/857/897) | `bytes`: five-byte commands | what the radio sends for each before it falls quiet; not read by the app |
2270
+ | FlexRadio | `text`: one command per line, without `C<seq>\|` — the app numbers each like its own | each one's `R` line |
2271
+ | Ham Radio Deluxe | `text`: one command per line, without the radio's context — the app adds it | each one's reply text, one per line |
2272
+ | DXLab Commander | `text`: one whole message (`<command:10>CmdGetFreq<parameters:0>`) | the field it is answered with; without `replyMs` it goes out as a set, waiting for nothing |
2273
+ | flrig | `text`: one XML-RPC call (`<?xml …?><methodCall>…`) — the app posts it | the response's body |
2274
+
2275
+ Every other radio's commands are written as given — several back to back
2276
+ (`FA00014074000;MD2;`) in one write, none of the app's own between them —
2277
+ since each of their answers names what it answers: Kenwood, Elecraft and
2278
+ Yaesu ASCII, rigctld, N3FJP, Guohetec. The reply there is everything the
2279
+ connection received in the window, the answers to the app's own polls
2280
+ included, so `until` is worth setting.
2281
+
2282
+ The app's driver still reads what the radio sends, so a raw command's effect
2283
+ shows in `readRadio` as a change made at the front panel would — and a command
2284
+ that confuses the driver confuses the app. A radio that is not connected
2285
+ answers `disconnected`; one whose driver takes no raw commands (a placeholder
2286
+ radio), `unsupported`; a malformed call, or commands that are not the
2287
+ protocol's (CI-V bytes that are not whole frames), `invalid`. A refusal heard
2288
+ within a second of a raw command on a protocol whose refusals name no command
2289
+ (`?;`, rigctld's `RPRT`) is taken as the raw command's, not counted against the
2290
+ app's own reads.
2234
2291
  On a host with the panadapter enabled, `readRadio()` carries `spectrum`: null
2235
2292
  for a radio without a scope, otherwise its settings as the radio reports them
2236
2293
  (`mode`, `spanHz`, `lowHz`/`highHz`, `refLevelDb`, `speed`), what it accepts
@@ -2422,6 +2479,39 @@ row/entry→`LookupRow` transform. See `DataFileManager`
2422
2479
  (`fetchType: 'raw'` — the parsed Hams of Note index is cached as JSON and
2423
2480
  replayed through `onLoadRawData` at startup, issue #17).
2424
2481
 
2482
+ ### N1MM call history files
2483
+
2484
+ N1MM Logger+'s call history files are uploaded to
2485
+ [n1mmwp.hamdocs.com](https://n1mmwp.hamdocs.com/mmfiles/categories/callhistory/)
2486
+ under a new name with every update (`CWOPS_4002.txt`), and downloaded by a
2487
+ form POST that carries a nonce from the file's own page, so no URL points at
2488
+ the current one. A `url` of `n1mm-history:<stem>` asks the host to find it
2489
+ (extension API 11):
2490
+
2491
+ ```ts
2492
+ { key: 'cwops-history', url: 'n1mm-history:CWOPS', fetchType: 'raw', maxAgeInDays: 7, /* … */ }
2493
+ ```
2494
+
2495
+ - **Which file:** the newest one, by publish date, whose name is the stem or
2496
+ the stem followed by `-` or `_` (extension and case aside). `CWOPS` takes
2497
+ `CWOPS_4002.txt` and `CWOPS_4000-CCC.txt`, never `CWOPSOPEN_2026-002.txt`,
2498
+ though N1MM's search lists all three; to follow one variant only, name it
2499
+ (`n1mm-history:CWOPS_4000-CCC`). The search is read newest first, page by
2500
+ page, until a page lists one of the stem's files.
2501
+ - **When:** N1MM sends no ETag; the chosen file's name, publish date and page
2502
+ stand in for one, so a re-upload under the same name still counts as new. A
2503
+ refresh whose newest file is the one last downloaded stops after the search,
2504
+ like a 304. A forced refresh (RELOAD, Refresh) downloads it regardless.
2505
+ - **What arrives:** the file as served, in N1MM's format — a `!!Order!!,…`
2506
+ column line, `#` comment lines, then CSV rows. `fetchType: 'raw'` is the one
2507
+ that fits; `rawToJSONData` parses it. `url` in its arguments is the
2508
+ `n1mm-history:` string, not the file's address.
2509
+ - **Failures:** a stem nothing matches, a refused download, or a page that no
2510
+ longer has the shape the host reads fails the download with
2511
+ `N1mmCallHistoryUnavailable` — a notice on a refresh the operator asked for,
2512
+ an upload for the background passes. The requests and parsing are
2513
+ `halo_core`'s `n1mm_call_history.dart`, shared by the app and the CLI.
2514
+
2425
2515
  ### How CSV rows reach `csvToLookupEntry`
2426
2516
 
2427
2517
  An extension writes a **per-row** mapper; the kernel synthesizes the batch
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ham2k/extension-sdk",
3
- "version": "0.12.13",
3
+ "version": "0.12.14",
4
4
  "description": "Write extensions for the Ham2K Logger: typed hook contracts and the host API",
5
5
  "keywords": [
6
6
  "ham2k",