@ham2k/extension-sdk 0.12.13 → 0.12.15
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 +126 -5
- package/dist/index.js +18 -0
- package/docs/distribution.md +8 -1
- package/docs/hooks.md +150 -10
- package/package.json +1 -1
- package/samples/k2hrc-panafall/README.md +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @ham2k/extension-sdk 0.12.
|
|
1
|
+
// @ham2k/extension-sdk 0.12.15
|
|
2
2
|
/** Experimental v1 panel scene contract. All coordinates are in the scene's viewBox. */
|
|
3
3
|
export interface PanelScene {
|
|
4
4
|
version: 1;
|
|
@@ -21,6 +21,20 @@ export interface PanelScene {
|
|
|
21
21
|
radio?: {
|
|
22
22
|
node?: string;
|
|
23
23
|
};
|
|
24
|
+
/**
|
|
25
|
+
* A scene longer than its pane along `axis` (default `y`), scrolled by the app itself — wheel,
|
|
26
|
+
* trackpad and drag, a drag begun on a button included — with no call to the extension. Scaled
|
|
27
|
+
* to fill the pane across, it runs on past it along the axis. The position, in scene units from
|
|
28
|
+
* the start, is written to the scene value `value` as it scrolls; a render that changes that
|
|
29
|
+
* value moves the view there, and one that leaves it alone leaves the operator's scroll alone.
|
|
30
|
+
* `event`, when given, is sent as a `commit` with the position as `value` once scrolling comes
|
|
31
|
+
* to rest. Layers and controls marked `fixed` stay put over the pane. No `layout`. Extension API 12.
|
|
32
|
+
*/
|
|
33
|
+
scroll?: {
|
|
34
|
+
axis?: "x" | "y";
|
|
35
|
+
value: string;
|
|
36
|
+
event?: string;
|
|
37
|
+
};
|
|
24
38
|
}
|
|
25
39
|
/** Host-fed values of a scene with `radio`: its meters, and `radio.transmitting` as 1 or 0. Reserved: a scene may not declare them. */
|
|
26
40
|
export type PanelSceneRadioValue = "radio.signalDbm" | "radio.powerOut" | "radio.swr" | "radio.alc" | "radio.supplyVolts" | "radio.transmitting"
|
|
@@ -52,13 +66,28 @@ export interface PanelSceneLayer {
|
|
|
52
66
|
y: number;
|
|
53
67
|
width: number;
|
|
54
68
|
height: number;
|
|
55
|
-
/** Supply exactly one of svg or
|
|
69
|
+
/** Supply exactly one of svg, text or icon. SVG must be self-contained. */
|
|
56
70
|
svg?: string;
|
|
71
|
+
/**
|
|
72
|
+
* A named icon — MDI, or FontAwesome with `fa-` — drawn by the app to fit the layer, in `color`
|
|
73
|
+
* (`#RRGGBB`) or the theme's. Extension API 12.
|
|
74
|
+
*/
|
|
75
|
+
icon?: {
|
|
76
|
+
name: string;
|
|
77
|
+
color?: string;
|
|
78
|
+
};
|
|
57
79
|
/**
|
|
58
80
|
* Take the clicks that land on this layer. Off by default: layers are click-through, so
|
|
59
81
|
* artwork painted over a control does not stop it working. On, it covers the controls under it.
|
|
60
82
|
*/
|
|
61
83
|
blocksPointer?: boolean;
|
|
84
|
+
/** In a scene with `scroll`: drawn over the pane where it is, in pane coordinates, not scrolled. Extension API 12. */
|
|
85
|
+
fixed?: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* While a `zoom` strip is dragged, scaled along the scroll about the pane's middle, rather than
|
|
88
|
+
* moved there at its own size: for a background, or a line that runs the whole way. Extension API 12.
|
|
89
|
+
*/
|
|
90
|
+
stretch?: boolean;
|
|
62
91
|
/** Exactly one of literal/value. A value naming a string shows it as-is. Use text layers, not SVG <text>, for font support.
|
|
63
92
|
* Size is unscaled; the host applies OS text scaling once. */
|
|
64
93
|
text?: {
|
|
@@ -121,10 +150,16 @@ export interface PanelSceneLayer {
|
|
|
121
150
|
* reach the extension. Needs `requiresRadioRead`; a host without the feature, or a radio
|
|
122
151
|
* without a scope, shows why instead. A click sends `commit` with the frequency as `value`.
|
|
123
152
|
* Takes no `value`; see `PanelSceneSpectrum` for the rest. Extension API 6: declare `"api": 6`.
|
|
153
|
+
* - `zoom`: a strip of a scene with `scroll`, drawn by nothing, where a drag along the scroll zooms
|
|
154
|
+
* about the pane's middle — away from it in, toward it out, by the ratio of the distances. While
|
|
155
|
+
* the drag lasts the app moves each layer and control to where the zoom puts its middle, at its
|
|
156
|
+
* own size (a `stretch` layer scales instead), and on release sends `commit` with the factor as
|
|
157
|
+
* `value`; `min` and `max` are the factors allowed. A tap on it still reaches what lies there,
|
|
158
|
+
* and a drag elsewhere still scrolls. Needs `event`. Extension API 12.
|
|
124
159
|
*
|
|
125
160
|
* `label` is every control's accessible name. There are no tooltips.
|
|
126
161
|
*/
|
|
127
|
-
export type PanelSceneControlKind = "button" | "slider" | "knob" | "nativeButton" | "nativeSlider" | "nativeTextField" | "nativeDropdown" | "nativeSwitch" | "nativeCheckbox" | "nativeRadio" | "nativeSegmented" | "nativeChip" | "nativeText" | "spectrum";
|
|
162
|
+
export type PanelSceneControlKind = "button" | "slider" | "knob" | "nativeButton" | "nativeSlider" | "nativeTextField" | "nativeDropdown" | "nativeSwitch" | "nativeCheckbox" | "nativeRadio" | "nativeSegmented" | "nativeChip" | "nativeText" | "spectrum" | "zoom";
|
|
128
163
|
/** A literal, or the name of a scene value — which a slider with no `event` retunes with no bridge call. */
|
|
129
164
|
export type PanelSceneSpectrumNumber = number | string;
|
|
130
165
|
/**
|
|
@@ -361,6 +396,18 @@ export interface PanelSceneControl extends PanelSceneSpectrum {
|
|
|
361
396
|
multi?: boolean;
|
|
362
397
|
/** Shown but inert: no clicks, drags, keys or screen-reader adjustments act on it. */
|
|
363
398
|
disabled?: boolean;
|
|
399
|
+
/** In a scene with `scroll`: placed over the pane, in pane coordinates, not scrolled. Needs a rect. Extension API 12. */
|
|
400
|
+
fixed?: boolean;
|
|
401
|
+
/**
|
|
402
|
+
* A drawn control only (`button`, `slider`, `knob`): while the pointer is over it, the scene value
|
|
403
|
+
* `value` reads `to`, set by the host with no call to the extension; leaving puts back the scene's
|
|
404
|
+
* own value. Artwork bound to it lights up under the pointer — a spot's line with its call.
|
|
405
|
+
* Survives renders while the pointer stays. Extension API 12.
|
|
406
|
+
*/
|
|
407
|
+
hoverSet?: {
|
|
408
|
+
value: string;
|
|
409
|
+
to: number;
|
|
410
|
+
};
|
|
364
411
|
/**
|
|
365
412
|
* 0 to 1, fixed or bound like a layer's. At 0 the control is gone: it takes no clicks,
|
|
366
413
|
* keyboard focus or screen-reader node. Anything between is only drawn fainter.
|
|
@@ -387,6 +434,17 @@ export interface PanelSceneControl extends PanelSceneSpectrum {
|
|
|
387
434
|
valueLabels?: string[];
|
|
388
435
|
/** Knobs use relative horizontal/upward dragging. Value per scene unit; default 1. */
|
|
389
436
|
sensitivity?: number;
|
|
437
|
+
/**
|
|
438
|
+
* A knob only: a drag moves the value the other way — content held under the finger, scrolled
|
|
439
|
+
* by dragging, rather than a dial turned. Scrolling is unchanged. Extension API 12.
|
|
440
|
+
*/
|
|
441
|
+
invertDrag?: boolean;
|
|
442
|
+
/**
|
|
443
|
+
* A slider or knob only: it also takes the scrolls that land on drawn buttons over it — labels
|
|
444
|
+
* drawn over a knob that scrolls the artwork. A native control, a `blocksPointer` layer or another
|
|
445
|
+
* slider or knob in between keeps the scroll. Extension API 12.
|
|
446
|
+
*/
|
|
447
|
+
scrollThrough?: boolean;
|
|
390
448
|
/**
|
|
391
449
|
* A knob's second axis: with `valueY`, horizontal scrolling, dragging and the left/right keys set
|
|
392
450
|
* `value`, and vertical ones (and up/down) set `valueY` — sent as its own event, `eventY`, which a
|
|
@@ -687,8 +745,22 @@ export interface GetSpotsOptions {
|
|
|
687
745
|
annotate?: boolean;
|
|
688
746
|
operationUuid?: string;
|
|
689
747
|
}
|
|
748
|
+
export interface SuggestedQso {
|
|
749
|
+
their: {
|
|
750
|
+
call: string;
|
|
751
|
+
};
|
|
752
|
+
freq?: number;
|
|
753
|
+
band?: string;
|
|
754
|
+
mode?: string;
|
|
755
|
+
refs?: SpotRef[];
|
|
756
|
+
}
|
|
690
757
|
export interface BoardSpot extends Spot {
|
|
691
758
|
hookKey?: string;
|
|
759
|
+
display?: {
|
|
760
|
+
icon: string;
|
|
761
|
+
iconColor?: string;
|
|
762
|
+
emoji?: string;
|
|
763
|
+
};
|
|
692
764
|
annotations?: SpotAnnotations;
|
|
693
765
|
}
|
|
694
766
|
export interface SpotAnnotations {
|
|
@@ -1537,6 +1609,7 @@ export interface ExtensionManifest {
|
|
|
1537
1609
|
requiresLocation?: boolean;
|
|
1538
1610
|
requiresRadioRead?: boolean;
|
|
1539
1611
|
requiresRadioWrite?: boolean;
|
|
1612
|
+
requiresRadioRaw?: boolean;
|
|
1540
1613
|
requiresClusterRead?: boolean;
|
|
1541
1614
|
requiresClusterSpot?: boolean;
|
|
1542
1615
|
requiresClusterCommand?: boolean;
|
|
@@ -1993,7 +2066,7 @@ export interface PanelRadioState {
|
|
|
1993
2066
|
endedAtMillis: number;
|
|
1994
2067
|
} | null;
|
|
1995
2068
|
/**
|
|
1996
|
-
* The radio's scope
|
|
2069
|
+
* The radio's scope: absent on an app without a panadapter, and null
|
|
1997
2070
|
* for a radio without one. The sweeps are never sent — a `spectrum` scene control draws them.
|
|
1998
2071
|
*/
|
|
1999
2072
|
spectrum?: PanelRadioSpectrum | null;
|
|
@@ -2095,15 +2168,44 @@ export interface PanelRadioSet extends PanelRadioTarget {
|
|
|
2095
2168
|
}
|
|
2096
2169
|
/** @deprecated `setRadio`'s argument through extension API 6. */
|
|
2097
2170
|
export type PanelRadioTune = PanelRadioSet;
|
|
2171
|
+
/**
|
|
2172
|
+
* Commands in the radio's own protocol — exactly one of `text` or `bytes`, up to 4096 bytes
|
|
2173
|
+
* (extension API 11). Where the app's driver keeps one question on the wire (CI-V, Yaesu binary,
|
|
2174
|
+
* HRD, Commander, flrig) each command waits its turn in that queue, and on a FlexRadio is numbered
|
|
2175
|
+
* like the driver's own; the reply is then the answers to these commands alone. Elsewhere the bytes
|
|
2176
|
+
* are written as given. See hooks.md for what each protocol takes.
|
|
2177
|
+
*/
|
|
2178
|
+
export interface PanelRadioRaw extends PanelRadioTarget {
|
|
2179
|
+
/** Sent as UTF-8: Kenwood, Yaesu and Elecraft ASCII, rigctld, N3FJP, SmartSDR, HRD, Commander, flrig. */
|
|
2180
|
+
text?: string;
|
|
2181
|
+
/** Each 0…255: Icom CI-V, Yaesu's binary protocol. */
|
|
2182
|
+
bytes?: number[];
|
|
2183
|
+
/**
|
|
2184
|
+
* How long to wait for the reply, up to 2000 ms; omitted or 0, the call answers once the commands
|
|
2185
|
+
* are handed to the driver. Where they are written as given, the reply is everything the radio
|
|
2186
|
+
* sent in that time — the answers to the app's own polls included.
|
|
2187
|
+
*/
|
|
2188
|
+
replyMs?: number;
|
|
2189
|
+
/** Ends the reply early, just after the first occurrence of this text or these bytes. Needs `replyMs`. */
|
|
2190
|
+
until?: string | number[];
|
|
2191
|
+
}
|
|
2192
|
+
export interface PanelRadioRawResult extends PanelRadioResult {
|
|
2193
|
+
/** What came back within `replyMs`, possibly nothing, at most 64 KiB; absent without `replyMs`. `text` is it as UTF-8. */
|
|
2194
|
+
reply?: {
|
|
2195
|
+
text: string;
|
|
2196
|
+
bytes: number[];
|
|
2197
|
+
};
|
|
2198
|
+
}
|
|
2098
2199
|
export interface PanelRadioResult {
|
|
2099
2200
|
/** Accepted by the host; confirmation still comes from reported state. */
|
|
2100
2201
|
accepted: boolean;
|
|
2101
2202
|
/**
|
|
2102
2203
|
* `stopped`: a scope change while no display has the scope running — nothing was sent.
|
|
2204
|
+
* `busy`: eight raw commands to this radio are already waiting (extension API 11).
|
|
2103
2205
|
* `unsupported`: this radio, or the app's keying, cannot do it — keying calls on a radio that
|
|
2104
2206
|
* is not the logging radio, or with the app's keying off.
|
|
2105
2207
|
*/
|
|
2106
|
-
reason?: "changed" | "disconnected" | "transmitting" | "invalid" | "stopped" | "unsupported";
|
|
2208
|
+
reason?: "changed" | "disconnected" | "transmitting" | "invalid" | "stopped" | "unsupported" | "busy";
|
|
2107
2209
|
state: PanelRadioState | null;
|
|
2108
2210
|
}
|
|
2109
2211
|
/** @deprecated `PanelRadioResult`'s name through extension API 6. */
|
|
@@ -2565,6 +2667,11 @@ export declare const host: {
|
|
|
2565
2667
|
}): Promise<PanelRadioResult>;
|
|
2566
2668
|
/** 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
2669
|
stopRadioTransmit(args: PanelRadioTarget): Promise<PanelRadioResult>;
|
|
2670
|
+
/**
|
|
2671
|
+
* Writes any command in the radio's own protocol — one that transmits included — and collects the
|
|
2672
|
+
* reply for `replyMs`. Needs `requiresRadioRaw` and extension API 11.
|
|
2673
|
+
*/
|
|
2674
|
+
sendRadioRaw(args: PanelRadioRaw): Promise<PanelRadioRawResult>;
|
|
2568
2675
|
/** The app's DX cluster connection, as Settings shows it. Needs `requiresClusterRead` and extension API 10. */
|
|
2569
2676
|
clusterStatus(): Promise<ClusterStatus>;
|
|
2570
2677
|
/** The node's recent lines after `after`, oldest first, at most `limit` of the newest. Needs `requiresClusterRead` and extension API 10. */
|
|
@@ -2579,6 +2686,20 @@ export declare const host: {
|
|
|
2579
2686
|
clusterPostSpot(args: ClusterPostSpotRequest): Promise<ClusterReply>;
|
|
2580
2687
|
/** Sends any one command line, in the operator's name. Needs `requiresClusterCommand` and extension API 10. */
|
|
2581
2688
|
clusterSend(command: string): Promise<ClusterReply>;
|
|
2689
|
+
/**
|
|
2690
|
+
* Puts a station into the logging fields of the operation in front, as tapping its spot in the
|
|
2691
|
+
* Spots Panel does — call, frequency, band, mode and references; nothing else of `qso` is used.
|
|
2692
|
+
* With `operationUuid` (a panel's `operation.uuid`), only that operation's: `accepted` is false
|
|
2693
|
+
* when it is not the one in front, or none is open. It tunes the radio as a tap does, so it needs
|
|
2694
|
+
* `requiresRadioWrite`, and it is refused outside the extension's own panel `onEvent`. Extension
|
|
2695
|
+
* API 12.
|
|
2696
|
+
*/
|
|
2697
|
+
suggestQso(args: {
|
|
2698
|
+
qso: SuggestedQso;
|
|
2699
|
+
operationUuid?: string;
|
|
2700
|
+
}): Promise<{
|
|
2701
|
+
accepted: boolean;
|
|
2702
|
+
}>;
|
|
2582
2703
|
fetch(url: string, options?: FetchOptions): Promise<FetchResponse>;
|
|
2583
2704
|
webSocket(url: string, options?: WebSocketOptions): ExtensionWebSocket;
|
|
2584
2705
|
kvGet(key: string): Promise<JSONValue | null>;
|
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", {});
|
|
@@ -220,6 +227,17 @@ const host = {
|
|
|
220
227
|
async clusterSend(command) {
|
|
221
228
|
return await hostCallFn()("clusterSend", { command });
|
|
222
229
|
},
|
|
230
|
+
/**
|
|
231
|
+
* Puts a station into the logging fields of the operation in front, as tapping its spot in the
|
|
232
|
+
* Spots Panel does — call, frequency, band, mode and references; nothing else of `qso` is used.
|
|
233
|
+
* With `operationUuid` (a panel's `operation.uuid`), only that operation's: `accepted` is false
|
|
234
|
+
* when it is not the one in front, or none is open. It tunes the radio as a tap does, so it needs
|
|
235
|
+
* `requiresRadioWrite`, and it is refused outside the extension's own panel `onEvent`. Extension
|
|
236
|
+
* API 12.
|
|
237
|
+
*/
|
|
238
|
+
async suggestQso(args) {
|
|
239
|
+
return await hostCallFn()("suggestQso", { ...args });
|
|
240
|
+
},
|
|
223
241
|
async fetch(url, options) {
|
|
224
242
|
return await hostCallFn()("fetch", { url, ...options });
|
|
225
243
|
},
|
package/docs/distribution.md
CHANGED
|
@@ -540,6 +540,13 @@ catalog's own bundle links (`…/api/v1/extensions/<key>/versions/<version>/bund
|
|
|
540
540
|
take this verified path when they arrive as a deep link; every other host
|
|
541
541
|
takes the plain one above.
|
|
542
542
|
|
|
543
|
+
An extension's screenshots — published beside its bundles, never inside them
|
|
544
|
+
(the tools README says how) — show as a strip of thumbnails on its row in the
|
|
545
|
+
operation sheet's activity search, on the consent screen of a catalog install,
|
|
546
|
+
and in an installed extension's details; a tap opens them full size. The app
|
|
547
|
+
loads them only from the catalog's own host: a listing naming any other is
|
|
548
|
+
shown without them.
|
|
549
|
+
|
|
543
550
|
### Release tracks
|
|
544
551
|
|
|
545
552
|
A catalog channel — `stable`, `unstable` or `bleeding` — says how finished a
|
|
@@ -573,7 +580,7 @@ counts as the catalog's own here. The row and the details show the track, and
|
|
|
573
580
|
the details' **Follow stable releases only** puts it back on stable; an
|
|
574
581
|
uninstall or a file install clears it with the rest of the record.
|
|
575
582
|
|
|
576
|
-
At most once
|
|
583
|
+
At most once a day — on launch and on resume — the app asks the
|
|
577
584
|
catalog whether anything installed has a newer release, and says so in the
|
|
578
585
|
status bar and on the row; the notice comes down with the last update taken.
|
|
579
586
|
It never installs unasked: the notice's **Update all** is the panel's own,
|
package/docs/hooks.md
CHANGED
|
@@ -73,6 +73,17 @@ hook method receives `(args, ctx)` where `ctx: HookContext` is:
|
|
|
73
73
|
reads above it is cheap enough for the lookup path; it resolves `[]`
|
|
74
74
|
whenever the cache is cold or the host keeps no spots. Used by
|
|
75
75
|
`spot-history` to pull a hunted park or summit into the QSO being logged.
|
|
76
|
+
* `host.suggestQso({qso, operationUuid?})` (extension API 12) — puts a
|
|
77
|
+
station into the logging fields of the operation in front, as tapping its
|
|
78
|
+
spot in the Spots Panel does: `qso.their.call`, and `freq`, `band`, `mode`
|
|
79
|
+
and `refs` where given; nothing else of `qso` reaches the fields. With
|
|
80
|
+
`operationUuid` — a panel's own `operation.uuid` — only that operation's
|
|
81
|
+
fields, so a panel never fills a log the operator is not looking at.
|
|
82
|
+
Answers `{accepted}`, false when no such operation is open. It tunes the
|
|
83
|
+
radio as a tap does, so it needs `requiresRadioWrite`, and it is refused
|
|
84
|
+
except from inside the extension's own panel `onEvent`: it answers the
|
|
85
|
+
operator clicking something, never a timer or a render, which would
|
|
86
|
+
overwrite what they are typing.
|
|
76
87
|
* `getSpots?(options?)` — the whole spots board the host already holds,
|
|
77
88
|
for a panel showing spots its own way. Like `getSpotsForCall` it reads the
|
|
78
89
|
cache and never fetches, and resolves `[]` while the cache is cold. By
|
|
@@ -92,7 +103,12 @@ hook method receives `(args, ctx)` where `ctx: HookContext` is:
|
|
|
92
103
|
ones the app already holds for the operation open on screen, so the call
|
|
93
104
|
never waits on a scorer; another operation's spots, and that one's until
|
|
94
105
|
its first verdicts land, come back unannotated rather than guessed.
|
|
95
|
-
|
|
106
|
+
From extension API 12 each also carries `display` — `icon` (an MDI or
|
|
107
|
+
`fa-` name), `iconColor` (`#RRGGBB`, where the activity declares one) and
|
|
108
|
+
`emoji` (the call's note emoji, where it has one): what the Spots Panel's row
|
|
109
|
+
shows beside the call, the activity's icon and tint for the spot's first
|
|
110
|
+
reference, else the source's own icon. `annotate: false` returns the spots
|
|
111
|
+
as their sources gave them.
|
|
96
112
|
A panel's `'spots'` trigger fires whenever this answer may have moved: a
|
|
97
113
|
refresh, new verdicts, the operator's spot filters or bands, or the radio's
|
|
98
114
|
band or mode. A malformed or unknown option rejects the call. The CLI host
|
|
@@ -287,6 +303,14 @@ interface LookupServiceHook {
|
|
|
287
303
|
unset or name itself; tagging one `'prefix'` gets its coordinate
|
|
288
304
|
discarded.
|
|
289
305
|
|
|
306
|
+
- **A guess field of the wrong type is dropped.** The bridge enforces none of
|
|
307
|
+
`CallInfo`'s declared types, so the app normalizes the answer
|
|
308
|
+
(`normalizeLookupResult`, halo_core) before anything reads it: a field its
|
|
309
|
+
readers cannot use — a numeric `continent`, a boolean `emoji` — is dropped
|
|
310
|
+
and reported once by name, and the rest of the guess is still used. A
|
|
311
|
+
numeric string where a number is read (`cqZone: '05'`) is taken as that
|
|
312
|
+
number.
|
|
313
|
+
|
|
290
314
|
Implemented by: `lookups` (key `core`). The merge algorithm itself
|
|
291
315
|
(`mergeLookupIntoGuess`/`runLookupPipeline` in
|
|
292
316
|
`extensions/core/lookups/src/mergeLookup.ts`) is a pure, host-independent
|
|
@@ -1876,9 +1900,10 @@ drawing part of it.
|
|
|
1876
1900
|
| `width`, `height` | The artwork's viewBox, in scene units. Size it to `environment.width`/`height` and a scene unit is a logical pixel. |
|
|
1877
1901
|
| `values` | `{name: number}`: what bindings, numeric text and numeric controls read. At most 256. |
|
|
1878
1902
|
| `strings` | `{name: string}`: what text fields and choices hold. At most 256, each up to 4096 characters. A name is in `values` or `strings`, never both. |
|
|
1879
|
-
| `layers` | Painted in order, each over the ones before: `svg` artwork or `
|
|
1880
|
-
| `controls` | At most 64. Drawn or native; see below. |
|
|
1903
|
+
| `layers` | Painted in order, each over the ones before: `svg` artwork, `text`, or an `icon`. At most 512 (128 before extension API 12), and 1 MiB of artwork and text in all. |
|
|
1904
|
+
| `controls` | At most 256 (64 before extension API 12). Drawn or native; see below. |
|
|
1881
1905
|
| `layout` | Optional. Where rect-less native controls and the artwork go; omitted, the artwork fills the pane. |
|
|
1906
|
+
| `scroll` | Optional, `{axis?, value, event?}` (extension API 12): a scene longer than its pane along `axis` (`y` by default), scrolled natively by the app — wheel, trackpad and drag, a drag begun on a button included — with no call to the extension. It fills the pane across and runs on past it. The position, in scene units, is written to the scene value `value`; a render that changes that value moves the view there, one that leaves it alone leaves the operator's scroll alone. `event` is sent as a `commit`, the position as `value`, once a scroll comes to rest. Layers and controls with `fixed: true` stay put over the pane, in pane coordinates. A drag along the scroll on a slider or knob that scrolls with the content is the scroll's: make one that must be dragged `fixed`. Not with `layout`. |
|
|
1882
1907
|
| `radio` | Optional, `{node?}`: the host feeds that radio's live readings into the reserved `radio.*` values. `node` is an id from `listRadios`; omitted, the logging radio. See *Radio values*. |
|
|
1883
1908
|
|
|
1884
1909
|
**Controls.** Every control has an `id` and a `label`, which is its accessible
|
|
@@ -1889,8 +1914,8 @@ the extension. `value` names what it shows and sets.
|
|
|
1889
1914
|
| Kind | Binds to | Sends | Also takes |
|
|
1890
1915
|
|---|---|---|---|
|
|
1891
1916
|
| `button` | — | `activate` | `menu` |
|
|
1892
|
-
| `slider` | number | `commit` | `min`, `max`, `step`, `discrete`, `hover`, `valueLabels`, `continuous` |
|
|
1893
|
-
| `knob` | number | `commit` | `min`, `max`, `step`, `sensitivity`, `valueLabels`, `continuous`, `menu`, a second axis (`valueY`, `eventY`, `minY`, `maxY`, `stepY`, `sensitivityY`) |
|
|
1917
|
+
| `slider` | number | `commit` | `min`, `max`, `step`, `discrete`, `hover`, `valueLabels`, `continuous`, `scrollThrough` |
|
|
1918
|
+
| `knob` | number | `commit` | `min`, `max`, `step`, `sensitivity`, `invertDrag`, `scrollThrough`, `valueLabels`, `continuous`, `menu`, a second axis (`valueY`, `eventY`, `minY`, `maxY`, `stepY`, `sensitivityY`) |
|
|
1894
1919
|
| `nativeButton` | — | `activate` | `variant` (`filled`, `tonal`, `outlined`, `text`), `icon`, `menu`; needs `event` or `menu` |
|
|
1895
1920
|
| `nativeSlider` | number | `commit` | `min`, `max`, `step`, `valueLabels`, `continuous` |
|
|
1896
1921
|
| `nativeTextField` | string | `commit` on Enter, or on leaving it after typing | `placeholder`, `continuous` |
|
|
@@ -1901,6 +1926,7 @@ the extension. `value` names what it shows and sets.
|
|
|
1901
1926
|
| `nativeChip` | number, 1 or 0; or none | `commit`; `activate` without a value | `icon`; needs `event` without a value |
|
|
1902
1927
|
| `nativeText` | number, string, or none | nothing | `style` (a typography role), `align` (`start`, `center`, `end`) |
|
|
1903
1928
|
| `spectrum` | — | `commit` with the frequency clicked; `activate` from an annotation | see *Spectrum* below |
|
|
1929
|
+
| `zoom` | — | `commit` with the factor, on release | `min`, `max` (the factors allowed); only in a scene with `scroll`; extension API 12 |
|
|
1904
1930
|
|
|
1905
1931
|
The three drawn kinds are invisible hit areas over artwork the extension paints;
|
|
1906
1932
|
the native kinds are Material widgets the app paints and themes. Every control
|
|
@@ -1912,12 +1938,37 @@ may also take:
|
|
|
1912
1938
|
- `after`: with a rect, the layer it paints directly after.
|
|
1913
1939
|
- `opacity`: 0 to 1, fixed or bound like a layer's. At 0 the control is gone.
|
|
1914
1940
|
- `disabled`: shown, but no click, drag, key or screen-reader adjustment acts on it.
|
|
1941
|
+
- `hoverSet` (drawn kinds only; extension API 12): `{value, to}`. While the
|
|
1942
|
+
pointer is over the control, the scene value `value` reads `to` — set by the
|
|
1943
|
+
host, with no call to the extension — and leaving puts back the scene's own
|
|
1944
|
+
value. A layer bound to it lights up under the pointer, and several controls
|
|
1945
|
+
naming one value with different `to`s light up one thing each. It survives
|
|
1946
|
+
renders while the pointer stays.
|
|
1915
1947
|
|
|
1916
1948
|
`options` is `[{label, value}]`, up to 64, with each `value` unique. A choice's
|
|
1917
1949
|
string is `""` or one of them; a `multi` choice holds its values joined with
|
|
1918
1950
|
commas in option order, so no option may contain one. `menu` is
|
|
1919
1951
|
`[{label, event}]`, up to 32: picking an item sends its `event` as the action.
|
|
1920
1952
|
|
|
1953
|
+
A `zoom` strip is drawn by nothing: it claims a drag that begins on it along a scrolling
|
|
1954
|
+
scene's axis. Away from the pane's middle zooms in and toward it zooms out, by the ratio of
|
|
1955
|
+
the two distances from the middle, clamped to `min`…`max`. While the drag lasts the app moves
|
|
1956
|
+
each layer and control to where the zoom puts its middle, keeping its size, so text and dots
|
|
1957
|
+
stay as drawn and only spread apart or close in; a layer with `stretch: true` — a background,
|
|
1958
|
+
a line the length of the scene — scales along the scroll instead. That preview holds until
|
|
1959
|
+
the render that answers the `commit` arrives, so a scene meant to zoom draws its pieces as
|
|
1960
|
+
layers of their own rather than as one picture. A tap on the strip still reaches the button there, and a drag that starts off it
|
|
1961
|
+
still scrolls.
|
|
1962
|
+
|
|
1963
|
+
A scroll steps a slider or knob by its `step`, down and left lowering it; a one-axis
|
|
1964
|
+
control takes a sideways scroll by its own direction. A slider or knob with
|
|
1965
|
+
`scrollThrough` (API 12) also takes the scrolls landing on drawn buttons over it, so labels
|
|
1966
|
+
drawn over a knob that scrolls the artwork do not stop the scroll; anything else in the
|
|
1967
|
+
way — a native control, a `blocksPointer` layer, another slider or knob — keeps the scroll
|
|
1968
|
+
from it.
|
|
1969
|
+
A knob with `invertDrag` (API 12) moves the other way to a drag, as content held under
|
|
1970
|
+
the finger does — what a knob that scrolls the artwork wants — while its scroll is unchanged.
|
|
1971
|
+
|
|
1921
1972
|
A knob with `valueY` sets two numbers from one patch of artwork — a passband's
|
|
1922
1973
|
shift across and its width up, say. Horizontal dragging, horizontal scrolling
|
|
1923
1974
|
and the left/right keys set `value`; vertical ones and the up/down keys set
|
|
@@ -2029,9 +2080,8 @@ control faded in over the artwork, and a click-through layer painted over it.
|
|
|
2029
2080
|
#### Spectrum
|
|
2030
2081
|
|
|
2031
2082
|
A `spectrum` control is a radio's panadapter and waterfall, drawn by the host
|
|
2032
|
-
from sweeps that never reach the extension
|
|
2033
|
-
|
|
2034
|
-
using either declares `"api": 6`, and an app speaking less refuses it at install
|
|
2083
|
+
from sweeps that never reach the extension. It is **extension API 6**, as is
|
|
2084
|
+
`host.setRadioSpectrum`: a bundle using either declares `"api": 6`, and an app speaking less refuses it at install
|
|
2035
2085
|
rather than failing to render the scene. It needs `requiresRadioRead`; without it, or on a host or radio
|
|
2036
2086
|
without a spectrum, the control says why instead. It is placed like a native
|
|
2037
2087
|
control and takes the fields of `PanelSceneSpectrum` in the SDK:
|
|
@@ -2115,7 +2165,9 @@ same shape as `requiresLocation`, and shown to the operator at install:
|
|
|
2115
2165
|
`host.setRadioConnection()` and `host.setRadioSpectrum()` — and transmitting
|
|
2116
2166
|
with them: `host.sendRadioCw()`, `host.playRadioVoice()`, `host.setRadioPtt()`
|
|
2117
2167
|
and `host.stopRadioTransmit()` — and implies read, since a command answers with the
|
|
2118
|
-
radio's state.
|
|
2168
|
+
radio's state. `"requiresRadioRaw": true` (extension API 11) also allows
|
|
2169
|
+
`host.sendRadioRaw()`, any command in the radio's own protocol, and implies
|
|
2170
|
+
write. An undeclared call rejects rather than answering null.
|
|
2119
2171
|
A capability belongs to the bundle that declared it, and to the sub-extensions
|
|
2120
2172
|
that bundle defines under its own key. A separately installed bundle never
|
|
2121
2173
|
inherits one through its name: `rigpanel-themes` gets nothing from `rigpanel`.
|
|
@@ -2231,7 +2283,62 @@ not paced, and not refused for a transmission in progress.
|
|
|
2231
2283
|
connection using the same selection and identity checks. Acceptance starts the
|
|
2232
2284
|
operation; follow reported connection state for completion. It does not switch
|
|
2233
2285
|
the hardware's mains power or initiate transmission.
|
|
2234
|
-
|
|
2286
|
+
|
|
2287
|
+
`host.sendRadioRaw({id, selection?, text | bytes, replyMs?, until?})`
|
|
2288
|
+
(extension API 11, `requiresRadioRaw`) sends commands in the radio's own
|
|
2289
|
+
protocol — exactly one of `text`, sent as UTF-8, or `bytes`, each 0…255, up
|
|
2290
|
+
to 4096 bytes. **Nothing about them is checked.** They may transmit, change
|
|
2291
|
+
any setting or put the radio somewhere the app's own controls never would;
|
|
2292
|
+
that is what the operator agreed to at install, where the grant is shown in
|
|
2293
|
+
those words. It reaches any local radio through the same `id`/`selection`
|
|
2294
|
+
check as `setRadio`, with keying on or off, and is neither paced nor refused
|
|
2295
|
+
for a transmission in progress. One radio's calls go one at a time, in order,
|
|
2296
|
+
so that one call's reply window never holds the next one's answer, and past
|
|
2297
|
+
eight waiting the next is refused as `busy`; the app's own polls may come
|
|
2298
|
+
between two calls.
|
|
2299
|
+
|
|
2300
|
+
- `replyMs`, up to 2000: how long to wait for the reply. Omitted or 0, the
|
|
2301
|
+
call answers as soon as the commands are handed to the driver, with no
|
|
2302
|
+
`reply`.
|
|
2303
|
+
- `until`, text or bytes, needs `replyMs`: the reply ends just after its first
|
|
2304
|
+
occurrence.
|
|
2305
|
+
- The answer carries `reply: {text, bytes}`, possibly empty, at most 64 KiB,
|
|
2306
|
+
with `text` its UTF-8 reading.
|
|
2307
|
+
|
|
2308
|
+
What the commands are, and what the reply holds, is the protocol's. Where the
|
|
2309
|
+
app's driver keeps one question on the wire and reads the next reply as its
|
|
2310
|
+
answer, a raw command waits its turn in that queue — written beside it, its
|
|
2311
|
+
answer would be read as the answer to the driver's question — or, on a
|
|
2312
|
+
FlexRadio, is numbered as the driver's own are. The reply is then the answers
|
|
2313
|
+
to the raw commands alone, as the radio sent them, once each has come or the
|
|
2314
|
+
window closes:
|
|
2315
|
+
|
|
2316
|
+
| Radio | Commands | Reply |
|
|
2317
|
+
|---|---|---|
|
|
2318
|
+
| 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) |
|
|
2319
|
+
| 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 |
|
|
2320
|
+
| FlexRadio | `text`: one command per line, without `C<seq>\|` — the app numbers each like its own | each one's `R` line |
|
|
2321
|
+
| 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 |
|
|
2322
|
+
| 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 |
|
|
2323
|
+
| flrig | `text`: one XML-RPC call (`<?xml …?><methodCall>…`) — the app posts it | the response's body |
|
|
2324
|
+
|
|
2325
|
+
Every other radio's commands are written as given — several back to back
|
|
2326
|
+
(`FA00014074000;MD2;`) in one write, none of the app's own between them —
|
|
2327
|
+
since each of their answers names what it answers: Kenwood, Elecraft and
|
|
2328
|
+
Yaesu ASCII, rigctld, N3FJP, Guohetec. The reply there is everything the
|
|
2329
|
+
connection received in the window, the answers to the app's own polls
|
|
2330
|
+
included, so `until` is worth setting.
|
|
2331
|
+
|
|
2332
|
+
The app's driver still reads what the radio sends, so a raw command's effect
|
|
2333
|
+
shows in `readRadio` as a change made at the front panel would — and a command
|
|
2334
|
+
that confuses the driver confuses the app. A radio that is not connected
|
|
2335
|
+
answers `disconnected`; one whose driver takes no raw commands (a placeholder
|
|
2336
|
+
radio), `unsupported`; a malformed call, or commands that are not the
|
|
2337
|
+
protocol's (CI-V bytes that are not whole frames), `invalid`. A refusal heard
|
|
2338
|
+
within a second of a raw command on a protocol whose refusals name no command
|
|
2339
|
+
(`?;`, rigctld's `RPRT`) is taken as the raw command's, not counted against the
|
|
2340
|
+
app's own reads.
|
|
2341
|
+
`readRadio()` carries `spectrum` (absent on an app without a panadapter): null
|
|
2235
2342
|
for a radio without a scope, otherwise its settings as the radio reports them
|
|
2236
2343
|
(`mode`, `spanHz`, `lowHz`/`highHz`, `refLevelDb`, `speed`), what it accepts
|
|
2237
2344
|
(`spansHz`, `refMinDb`…`refStepDb`, `modes`, `speeds`) and `problem`, why it
|
|
@@ -2422,6 +2529,39 @@ row/entry→`LookupRow` transform. See `DataFileManager`
|
|
|
2422
2529
|
(`fetchType: 'raw'` — the parsed Hams of Note index is cached as JSON and
|
|
2423
2530
|
replayed through `onLoadRawData` at startup, issue #17).
|
|
2424
2531
|
|
|
2532
|
+
### N1MM call history files
|
|
2533
|
+
|
|
2534
|
+
N1MM Logger+'s call history files are uploaded to
|
|
2535
|
+
[n1mmwp.hamdocs.com](https://n1mmwp.hamdocs.com/mmfiles/categories/callhistory/)
|
|
2536
|
+
under a new name with every update (`CWOPS_4002.txt`), and downloaded by a
|
|
2537
|
+
form POST that carries a nonce from the file's own page, so no URL points at
|
|
2538
|
+
the current one. A `url` of `n1mm-history:<stem>` asks the host to find it
|
|
2539
|
+
(extension API 11):
|
|
2540
|
+
|
|
2541
|
+
```ts
|
|
2542
|
+
{ key: 'cwops-history', url: 'n1mm-history:CWOPS', fetchType: 'raw', maxAgeInDays: 7, /* … */ }
|
|
2543
|
+
```
|
|
2544
|
+
|
|
2545
|
+
- **Which file:** the newest one, by publish date, whose name is the stem or
|
|
2546
|
+
the stem followed by `-` or `_` (extension and case aside). `CWOPS` takes
|
|
2547
|
+
`CWOPS_4002.txt` and `CWOPS_4000-CCC.txt`, never `CWOPSOPEN_2026-002.txt`,
|
|
2548
|
+
though N1MM's search lists all three; to follow one variant only, name it
|
|
2549
|
+
(`n1mm-history:CWOPS_4000-CCC`). The search is read newest first, page by
|
|
2550
|
+
page, until a page lists one of the stem's files.
|
|
2551
|
+
- **When:** N1MM sends no ETag; the chosen file's name, publish date and page
|
|
2552
|
+
stand in for one, so a re-upload under the same name still counts as new. A
|
|
2553
|
+
refresh whose newest file is the one last downloaded stops after the search,
|
|
2554
|
+
like a 304. A forced refresh (RELOAD, Refresh) downloads it regardless.
|
|
2555
|
+
- **What arrives:** the file as served, in N1MM's format — a `!!Order!!,…`
|
|
2556
|
+
column line, `#` comment lines, then CSV rows. `fetchType: 'raw'` is the one
|
|
2557
|
+
that fits; `rawToJSONData` parses it. `url` in its arguments is the
|
|
2558
|
+
`n1mm-history:` string, not the file's address.
|
|
2559
|
+
- **Failures:** a stem nothing matches, a refused download, or a page that no
|
|
2560
|
+
longer has the shape the host reads fails the download with
|
|
2561
|
+
`N1mmCallHistoryUnavailable` — a notice on a refresh the operator asked for,
|
|
2562
|
+
an upload for the background passes. The requests and parsing are
|
|
2563
|
+
`halo_core`'s `n1mm_call_history.dart`, shared by the app and the CLI.
|
|
2564
|
+
|
|
2425
2565
|
### How CSV rows reach `csvToLookupEntry`
|
|
2426
2566
|
|
|
2427
2567
|
An extension writes a **per-row** mapper; the kernel synthesizes the batch
|
package/package.json
CHANGED
|
@@ -4,9 +4,9 @@ The radio's panadapter and waterfall in a scene panel, with its span, reference
|
|
|
4
4
|
level and sweep speed, a local contrast control, and click-to-tune. The host
|
|
5
5
|
draws the sweeps in the `spectrum` control; they never reach the extension.
|
|
6
6
|
|
|
7
|
-
Needs a
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
Needs a radio whose CAT protocol carries its scope: an Icom IC-705/7300/7610 (at
|
|
8
|
+
115200 bps on a serial line), or a FlexRadio, which is mirrored through a DAX IQ
|
|
9
|
+
channel.
|
|
10
10
|
|
|
11
11
|
In this repository:
|
|
12
12
|
|