@ham2k/extension-sdk 0.12.7 → 0.12.8
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 +182 -14
- package/dist/index.js +21 -0
- package/docs/distribution.md +7 -2
- package/docs/hooks.md +165 -22
- package/package.json +1 -1
- package/samples/README.md +1 -1
- package/samples/k2hrc-panafall/manifest.json +1 -1
- package/samples/k2hrc-panafall/src/panafall.ts +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @ham2k/extension-sdk 0.12.
|
|
1
|
+
// @ham2k/extension-sdk 0.12.8
|
|
2
2
|
/** Experimental v1 panel scene contract. All coordinates are in the scene's viewBox. */
|
|
3
3
|
export interface PanelScene {
|
|
4
4
|
version: 1;
|
|
@@ -12,7 +12,22 @@ export interface PanelScene {
|
|
|
12
12
|
controls?: PanelSceneControl[];
|
|
13
13
|
/** Where rect-less native controls and the artwork go. Omitted, the artwork fills the panel. */
|
|
14
14
|
layout?: PanelSceneLayoutNode;
|
|
15
|
+
/**
|
|
16
|
+
* Have the host feed a radio's live readings into `values` — the `PanelSceneRadioValue` names,
|
|
17
|
+
* updated as the radio reports them, with no render. `node` is a radio's id; omitted, the
|
|
18
|
+
* logging radio. Needs `requiresRadioRead` and extension API 7; without either, every reading
|
|
19
|
+
* is missing. A missing reading binds as the bottom of a binding's range and shows as `—`.
|
|
20
|
+
*/
|
|
21
|
+
radio?: {
|
|
22
|
+
node?: string;
|
|
23
|
+
};
|
|
15
24
|
}
|
|
25
|
+
/** 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
|
+
export type PanelSceneRadioValue = "radio.signalDbm" | "radio.powerOut" | "radio.swr" | "radio.alc" | "radio.supplyVolts" | "radio.transmitting"
|
|
27
|
+
/** The SWR averaged over the transmission under way, or else the last one. Extension API 7. */
|
|
28
|
+
| "radio.swrAverage"
|
|
29
|
+
/** 0 while transmitting, then counting up; a long time when there has been none. Extension API 7. */
|
|
30
|
+
| "radio.secondsSinceTransmit";
|
|
16
31
|
/** A clamped, linear mapping. No expressions or extension code run per frame. */
|
|
17
32
|
export interface SceneBinding {
|
|
18
33
|
value: string;
|
|
@@ -54,6 +69,8 @@ export interface PanelSceneLayer {
|
|
|
54
69
|
decimals?: number;
|
|
55
70
|
size?: number;
|
|
56
71
|
color?: string;
|
|
72
|
+
/** `#RRGGBB`: a soft halo of this colour around the letters, to keep them legible over artwork. Extension API 7. */
|
|
73
|
+
glow?: string;
|
|
57
74
|
samples?: (number | string)[];
|
|
58
75
|
/** Multiply numeric text values before formatting. Defaults to 1. */
|
|
59
76
|
scale?: number;
|
|
@@ -71,8 +88,11 @@ export interface PanelSceneLayer {
|
|
|
71
88
|
rotation?: SceneBinding;
|
|
72
89
|
translateX?: SceneBinding;
|
|
73
90
|
translateY?: SceneBinding;
|
|
91
|
+
/** Scale about `pivot` — a bar's fill with `pivot: [0, 0.5]`. Extension API 7. */
|
|
92
|
+
scaleX?: SceneBinding;
|
|
93
|
+
scaleY?: SceneBinding;
|
|
74
94
|
opacity?: SceneBinding;
|
|
75
|
-
/** Rotation origin as fractions of this layer's dimensions; default [0.5, 0.5]. */
|
|
95
|
+
/** Rotation and scale origin as fractions of this layer's dimensions; default [0.5, 0.5]. */
|
|
76
96
|
pivot?: [
|
|
77
97
|
number,
|
|
78
98
|
number
|
|
@@ -157,6 +177,19 @@ export interface PanelSceneSpectrum {
|
|
|
157
177
|
gridColor?: string;
|
|
158
178
|
/** The frequency lines carried down through the waterfall. Omitted, none. */
|
|
159
179
|
waterfallGridColor?: string;
|
|
180
|
+
/**
|
|
181
|
+
* Keeps the tuned frequency at least this share of the width (0–0.45) from either edge, by
|
|
182
|
+
* moving the radio's scope just far enough to put it at that margin. Only where the radio lets
|
|
183
|
+
* the app place its scope — a FlexRadio, whose panadapter in SmartSDR moves with it — and the
|
|
184
|
+
* panel declares `requiresRadioWrite`; elsewhere it changes nothing. Extension API 7.
|
|
185
|
+
*/
|
|
186
|
+
follow?: number;
|
|
187
|
+
/**
|
|
188
|
+
* Hz shown across the control, from the middle of each sweep. A sweep wider than this is cropped
|
|
189
|
+
* to it, so a radio whose scope comes in a few widths can be shown at any narrower one — set the
|
|
190
|
+
* scope (`setRadioSpectrum`) to the narrowest that covers it. Extension API 7.
|
|
191
|
+
*/
|
|
192
|
+
span?: PanelSceneSpectrumNumber;
|
|
160
193
|
/** A click tunes to the nearest multiple of this many Hz. Omitted, wherever it lands. */
|
|
161
194
|
tuneStep?: PanelSceneSpectrumNumber;
|
|
162
195
|
/** Dashes the panadapter's grid lines. */
|
|
@@ -195,6 +228,8 @@ export interface PanelSceneSpectrumScale {
|
|
|
195
228
|
labels?: boolean | {
|
|
196
229
|
/** Hz between labels. Omitted, the gridlines' step. */
|
|
197
230
|
interval?: PanelSceneSpectrumNumber;
|
|
231
|
+
/** Decimals of MHz every label shows, 0–6. Omitted, as many as tell one label from the next. Extension API 7. */
|
|
232
|
+
decimals?: number;
|
|
198
233
|
fontFamily?: string;
|
|
199
234
|
size?: PanelSceneSpectrumNumber;
|
|
200
235
|
/** 100–900. */
|
|
@@ -287,7 +322,10 @@ export interface PanelSceneControl extends PanelSceneSpectrum {
|
|
|
287
322
|
id: string;
|
|
288
323
|
label: string;
|
|
289
324
|
kind: PanelSceneControlKind;
|
|
290
|
-
/**
|
|
325
|
+
/**
|
|
326
|
+
* A button can open a native dropdown. Picking an item sends its event as the action. On a knob
|
|
327
|
+
* (extension API 7) a click opens it, while a scroll or a drag still turns the knob.
|
|
328
|
+
*/
|
|
291
329
|
menu?: {
|
|
292
330
|
label: string;
|
|
293
331
|
event: string;
|
|
@@ -349,6 +387,18 @@ export interface PanelSceneControl extends PanelSceneSpectrum {
|
|
|
349
387
|
valueLabels?: string[];
|
|
350
388
|
/** Knobs use relative horizontal/upward dragging. Value per scene unit; default 1. */
|
|
351
389
|
sensitivity?: number;
|
|
390
|
+
/**
|
|
391
|
+
* A knob's second axis: with `valueY`, horizontal scrolling, dragging and the left/right keys set
|
|
392
|
+
* `value`, and vertical ones (and up/down) set `valueY` — sent as its own event, `eventY`, which a
|
|
393
|
+
* knob with an `event` must give, different from it. Its range and feel are `minY`, `maxY`, `stepY`
|
|
394
|
+
* and `sensitivityY`. Extension API 7.
|
|
395
|
+
*/
|
|
396
|
+
valueY?: string;
|
|
397
|
+
eventY?: string;
|
|
398
|
+
minY?: number;
|
|
399
|
+
maxY?: number;
|
|
400
|
+
stepY?: number;
|
|
401
|
+
sensitivityY?: number;
|
|
352
402
|
}
|
|
353
403
|
export interface PanelSceneEvent {
|
|
354
404
|
controlId: string;
|
|
@@ -1748,6 +1798,50 @@ export interface PanelRadioState {
|
|
|
1748
1798
|
transmitting: boolean | null;
|
|
1749
1799
|
meters: Partial<Record<"signalDbm" | "powerOut" | "swr" | "alc" | "supplyVolts", number>>;
|
|
1750
1800
|
canTune: boolean;
|
|
1801
|
+
/** The model's rated output ceiling, where the app knows it (extension API 7). */
|
|
1802
|
+
maxWatts?: number | null;
|
|
1803
|
+
/**
|
|
1804
|
+
* The radio's antenna tuner as it reports it, or null for a radio without one, or whose driver
|
|
1805
|
+
* cannot read it (extension API 7).
|
|
1806
|
+
*/
|
|
1807
|
+
tuner?: "inline" | "bypassed" | "tuning" | "failed" | null;
|
|
1808
|
+
/**
|
|
1809
|
+
* The receive filter's edges in Hz from the tuned frequency, as the radio shows them — `100`…`2800`
|
|
1810
|
+
* in USB, `-2800`…`-100` in LSB, around zero in CW — or null where its driver cannot read it
|
|
1811
|
+
* (extension API 7).
|
|
1812
|
+
*/
|
|
1813
|
+
passband?: {
|
|
1814
|
+
lowHz: number;
|
|
1815
|
+
highHz: number;
|
|
1816
|
+
} | null;
|
|
1817
|
+
/**
|
|
1818
|
+
* The radio's own antenna ports, by the radio's names, and which it uses — or null where its driver
|
|
1819
|
+
* cannot read them. `separate`: it picks receive and transmit antennas independently, as a FlexRadio
|
|
1820
|
+
* does; otherwise one port serves both (extension API 7).
|
|
1821
|
+
*/
|
|
1822
|
+
antennas?: {
|
|
1823
|
+
rxPorts: string[];
|
|
1824
|
+
txPorts: string[];
|
|
1825
|
+
rx: string | null;
|
|
1826
|
+
tx: string | null;
|
|
1827
|
+
separate: boolean;
|
|
1828
|
+
} | null;
|
|
1829
|
+
/** The modes `setRadio` accepts for this radio, in the radio's order (extension API 7). */
|
|
1830
|
+
modes?: string[];
|
|
1831
|
+
/**
|
|
1832
|
+
* What the app keys with, while this is the logging radio, connected, and the app's keying
|
|
1833
|
+
* is on; null otherwise (extension API 7).
|
|
1834
|
+
*/
|
|
1835
|
+
keying?: PanelRadioKeying | null;
|
|
1836
|
+
/**
|
|
1837
|
+
* The last transmission the radio finished since the app started: its average SWR over the readings taken with
|
|
1838
|
+
* RF out (null when there were none), and when it ended in real milliseconds — compare it with
|
|
1839
|
+
* `clock.realNowMillis` (extension API 7).
|
|
1840
|
+
*/
|
|
1841
|
+
lastTransmission?: {
|
|
1842
|
+
swrAverage: number | null;
|
|
1843
|
+
endedAtMillis: number;
|
|
1844
|
+
} | null;
|
|
1751
1845
|
/**
|
|
1752
1846
|
* The radio's scope, on hosts with the panadapter enabled: absent there otherwise, and null
|
|
1753
1847
|
* for a radio without one. The sweeps are never sent — a `spectrum` scene control draws them.
|
|
@@ -1783,21 +1877,80 @@ export interface PanelRadioSpectrumSet {
|
|
|
1783
1877
|
refLevelDb?: number;
|
|
1784
1878
|
speed?: PanelRadioSpectrumSpeed;
|
|
1785
1879
|
}
|
|
1786
|
-
|
|
1880
|
+
/** The logging radio's keying: the designated key, radio1's voice memories and its PTT. */
|
|
1881
|
+
export interface PanelRadioKeying {
|
|
1882
|
+
canKeyCw: boolean;
|
|
1883
|
+
cwSending: boolean;
|
|
1884
|
+
/** The key's own report, clamping included; null before it has said. */
|
|
1885
|
+
keyerSpeedWpm: number | null;
|
|
1886
|
+
keyerSpeedRange: {
|
|
1887
|
+
min: number;
|
|
1888
|
+
max: number;
|
|
1889
|
+
} | null;
|
|
1890
|
+
canPtt: boolean;
|
|
1891
|
+
/** Whether the app's PTT latch is holding the transmitter. */
|
|
1892
|
+
ptt: boolean;
|
|
1893
|
+
/** Empty when the radio plays none. `number` is what `playRadioVoice` takes. */
|
|
1894
|
+
voiceMemories: {
|
|
1895
|
+
number: number;
|
|
1896
|
+
label: string;
|
|
1897
|
+
}[];
|
|
1898
|
+
}
|
|
1899
|
+
/** A radio identity and selection, as every command carries. */
|
|
1900
|
+
export interface PanelRadioTarget {
|
|
1787
1901
|
/** Reject if this identity no longer matches the selected radio. */
|
|
1788
1902
|
id: string;
|
|
1789
1903
|
/** Pin a local radio by ID; omitted means follow the designated radio. */
|
|
1790
1904
|
selection?: string;
|
|
1905
|
+
}
|
|
1906
|
+
export interface PanelRadioSet extends PanelRadioTarget {
|
|
1791
1907
|
frequencyHz?: number;
|
|
1792
|
-
|
|
1908
|
+
/**
|
|
1909
|
+
* The frequency comes from an on-screen dial turning, not a typed or clicked target: on the
|
|
1910
|
+
* logging radio, the app's Smart VFO Snap settles it like the radio's own knob (extension API 7).
|
|
1911
|
+
*/
|
|
1912
|
+
dial?: boolean;
|
|
1913
|
+
/** One of the radio's `modes`. */
|
|
1914
|
+
mode?: string;
|
|
1915
|
+
/** Up to `maxWatts` where the radio states one. On the logging radio this is the power the next QSO logs. */
|
|
1916
|
+
powerWatts?: number;
|
|
1917
|
+
/** The logging radio's keyer; refused as `unsupported` where `keying.canKeyCw` is false. */
|
|
1918
|
+
keyerSpeedWpm?: number;
|
|
1919
|
+
/**
|
|
1920
|
+
* Puts the antenna tuner in line or bypasses it; refused as `unsupported` where `tuner` is null.
|
|
1921
|
+
* On a FlexRadio, in line is a TUNE — a carrier on the air — since its tuner has no other way in.
|
|
1922
|
+
*/
|
|
1923
|
+
tunerInline?: boolean;
|
|
1924
|
+
/**
|
|
1925
|
+
* The receive filter, as `passband` reports it; refused as `unsupported` where that is null. A
|
|
1926
|
+
* radio that sets only a width from a table takes the nearest, keeping its own shift — read the
|
|
1927
|
+
* result back from `passband`.
|
|
1928
|
+
*/
|
|
1929
|
+
passband?: {
|
|
1930
|
+
lowHz: number;
|
|
1931
|
+
highHz: number;
|
|
1932
|
+
};
|
|
1933
|
+
/** Ports from `antennas`; refused as `unsupported` where that is null. Without `separate`, either names the one port. */
|
|
1934
|
+
antenna?: {
|
|
1935
|
+
rx?: string;
|
|
1936
|
+
tx?: string;
|
|
1937
|
+
};
|
|
1793
1938
|
}
|
|
1794
|
-
|
|
1939
|
+
/** @deprecated `setRadio`'s argument through extension API 6. */
|
|
1940
|
+
export type PanelRadioTune = PanelRadioSet;
|
|
1941
|
+
export interface PanelRadioResult {
|
|
1795
1942
|
/** Accepted by the host; confirmation still comes from reported state. */
|
|
1796
1943
|
accepted: boolean;
|
|
1797
|
-
/**
|
|
1798
|
-
|
|
1944
|
+
/**
|
|
1945
|
+
* `stopped`: a scope change while no display has the scope running — nothing was sent.
|
|
1946
|
+
* `unsupported`: this radio, or the app's keying, cannot do it — keying calls on a radio that
|
|
1947
|
+
* is not the logging radio, or with the app's keying off.
|
|
1948
|
+
*/
|
|
1949
|
+
reason?: "changed" | "disconnected" | "transmitting" | "invalid" | "stopped" | "unsupported";
|
|
1799
1950
|
state: PanelRadioState | null;
|
|
1800
1951
|
}
|
|
1952
|
+
/** @deprecated `PanelRadioResult`'s name through extension API 6. */
|
|
1953
|
+
export type PanelRadioTuneResult = PanelRadioResult;
|
|
1801
1954
|
export interface PanelRenderArgs {
|
|
1802
1955
|
/** Present on environment-capable hosts. Changes automatically request a throttled render of a `scene` panel. */
|
|
1803
1956
|
environment?: PanelEnvironment;
|
|
@@ -2232,14 +2385,29 @@ export declare const hooks: {
|
|
|
2232
2385
|
export declare const host: {
|
|
2233
2386
|
listRadios(): Promise<PanelRadioState[]>;
|
|
2234
2387
|
readRadio(id?: string): Promise<PanelRadioState | null>;
|
|
2235
|
-
setRadioConnection(args: {
|
|
2236
|
-
id: string;
|
|
2237
|
-
selection?: string;
|
|
2388
|
+
setRadioConnection(args: PanelRadioTarget & {
|
|
2238
2389
|
connected: boolean;
|
|
2239
|
-
}): Promise<
|
|
2240
|
-
|
|
2390
|
+
}): Promise<PanelRadioResult>;
|
|
2391
|
+
/** Sets frequency, mode, power, keyer speed, tuner, passband and antennas. Needs `requiresRadioWrite` and extension API 7. */
|
|
2392
|
+
setRadio(args: PanelRadioSet): Promise<PanelRadioResult>;
|
|
2393
|
+
/** @deprecated `setRadio`'s name through extension API 6, still answered by newer hosts. */
|
|
2394
|
+
tuneRadio(args: PanelRadioSet): Promise<PanelRadioResult>;
|
|
2241
2395
|
/** Changes the radio's scope. Needs `requiresRadioWrite` and extension API 6; the result's state reports what the radio took. */
|
|
2242
|
-
setRadioSpectrum(args: PanelRadioSpectrumSet): Promise<
|
|
2396
|
+
setRadioSpectrum(args: PanelRadioSpectrumSet): Promise<PanelRadioResult>;
|
|
2397
|
+
/** Keys `text` on the logging radio's key, as the keyer area does. Needs `requiresRadioWrite` and extension API 7. */
|
|
2398
|
+
sendRadioCw(args: PanelRadioTarget & {
|
|
2399
|
+
text: string;
|
|
2400
|
+
}): Promise<PanelRadioResult>;
|
|
2401
|
+
/** Plays one of `keying.voiceMemories` once — never repeating. Needs `requiresRadioWrite` and extension API 7. */
|
|
2402
|
+
playRadioVoice(args: PanelRadioTarget & {
|
|
2403
|
+
memory: number;
|
|
2404
|
+
}): Promise<PanelRadioResult>;
|
|
2405
|
+
/** Keys or unkeys the logging radio's PTT through the app's own latch. Needs `requiresRadioWrite` and extension API 7. */
|
|
2406
|
+
setRadioPtt(args: PanelRadioTarget & {
|
|
2407
|
+
on: boolean;
|
|
2408
|
+
}): Promise<PanelRadioResult>;
|
|
2409
|
+
/** Stops all CW, voice and PTT the app is keying, as Escape does. Needs `requiresRadioWrite` and extension API 7. */
|
|
2410
|
+
stopRadioTransmit(args: PanelRadioTarget): Promise<PanelRadioResult>;
|
|
2243
2411
|
fetch(url: string, options?: FetchOptions): Promise<FetchResponse>;
|
|
2244
2412
|
webSocket(url: string, options?: WebSocketOptions): ExtensionWebSocket;
|
|
2245
2413
|
kvGet(key: string): Promise<JSONValue | null>;
|
package/dist/index.js
CHANGED
|
@@ -169,6 +169,11 @@ const host = {
|
|
|
169
169
|
async setRadioConnection(args) {
|
|
170
170
|
return await hostCallFn()("setRadioConnection", { ...args });
|
|
171
171
|
},
|
|
172
|
+
/** Sets frequency, mode, power, keyer speed, tuner, passband and antennas. Needs `requiresRadioWrite` and extension API 7. */
|
|
173
|
+
async setRadio(args) {
|
|
174
|
+
return await hostCallFn()("setRadio", { ...args });
|
|
175
|
+
},
|
|
176
|
+
/** @deprecated `setRadio`'s name through extension API 6, still answered by newer hosts. */
|
|
172
177
|
async tuneRadio(args) {
|
|
173
178
|
return await hostCallFn()("tuneRadio", { ...args });
|
|
174
179
|
},
|
|
@@ -176,6 +181,22 @@ const host = {
|
|
|
176
181
|
async setRadioSpectrum(args) {
|
|
177
182
|
return await hostCallFn()("setRadioSpectrum", { ...args });
|
|
178
183
|
},
|
|
184
|
+
/** Keys `text` on the logging radio's key, as the keyer area does. Needs `requiresRadioWrite` and extension API 7. */
|
|
185
|
+
async sendRadioCw(args) {
|
|
186
|
+
return await hostCallFn()("sendRadioCw", { ...args });
|
|
187
|
+
},
|
|
188
|
+
/** Plays one of `keying.voiceMemories` once — never repeating. Needs `requiresRadioWrite` and extension API 7. */
|
|
189
|
+
async playRadioVoice(args) {
|
|
190
|
+
return await hostCallFn()("playRadioVoice", { ...args });
|
|
191
|
+
},
|
|
192
|
+
/** Keys or unkeys the logging radio's PTT through the app's own latch. Needs `requiresRadioWrite` and extension API 7. */
|
|
193
|
+
async setRadioPtt(args) {
|
|
194
|
+
return await hostCallFn()("setRadioPtt", { ...args });
|
|
195
|
+
},
|
|
196
|
+
/** Stops all CW, voice and PTT the app is keying, as Escape does. Needs `requiresRadioWrite` and extension API 7. */
|
|
197
|
+
async stopRadioTransmit(args) {
|
|
198
|
+
return await hostCallFn()("stopRadioTransmit", { ...args });
|
|
199
|
+
},
|
|
179
200
|
async fetch(url, options) {
|
|
180
201
|
return await hostCallFn()("fetch", { url, ...options });
|
|
181
202
|
},
|
package/docs/distribution.md
CHANGED
|
@@ -53,7 +53,12 @@ and `onShow`/`onHide`, and from 3 the build rewrites a bare `setTimeout` into
|
|
|
53
53
|
the SDK's (README's "Timers"); 4 lets `host.fetch` reach a plain-`http://` host
|
|
54
54
|
the app fronts with its https proxy (README's `host.fetch`); 5 adds panel
|
|
55
55
|
scenes' `scene` kind, native controls, `strings`, `layout`, `after` and control
|
|
56
|
-
`opacity` (hooks.md's `panel`)
|
|
56
|
+
`opacity` (hooks.md's `panel`); 6 adds the scene's `spectrum` control and
|
|
57
|
+
`host.setRadioSpectrum`; 7 adds `host.setRadio`, the keying calls
|
|
58
|
+
(`sendRadioCw`, `playRadioVoice`, `setRadioPtt`, `stopRadioTransmit`), what
|
|
59
|
+
`readRadio` adds with them, and the scene's host-fed `radio` values, scale
|
|
60
|
+
bindings, text glow, two-axis and menu knobs and the spectrum's `follow`,
|
|
61
|
+
`span` and label `decimals`.
|
|
57
62
|
|
|
58
63
|
Three fields are **refused** in a distributed bundle, and it is worth
|
|
59
64
|
understanding why:
|
|
@@ -262,7 +267,7 @@ It reports every problem at once rather than one per run:
|
|
|
262
267
|
h2kext-pack: ./build is not ready to package:
|
|
263
268
|
manifest.json: missing required field 'name'
|
|
264
269
|
manifest.json: key 'my-clock' must start with your callsign — 'my' is not one (e.g. ki2d-my-clock)
|
|
265
|
-
manifest.json: missing 'api' — declare the extension API this was built against (currently
|
|
270
|
+
manifest.json: missing 'api' — declare the extension API this was built against (currently 7)
|
|
266
271
|
```
|
|
267
272
|
|
|
268
273
|
`--force-name` waives the naming convention below. It exists for Ham2K's own
|
package/docs/hooks.md
CHANGED
|
@@ -1551,7 +1551,11 @@ which the SDK keeps as a deprecated alias along with the `SvgScene*` type names.
|
|
|
1551
1551
|
`strings`, `layout`, `after` and control `opacity`. A bundle using any of them
|
|
1552
1552
|
declares `"api": 5` ([distribution.md](distribution.md)), and an app speaking
|
|
1553
1553
|
less refuses it at install rather than showing the panel as an unknown kind.
|
|
1554
|
-
A plain scene that must reach those apps keeps `svgScene` and its lower API.
|
|
1554
|
+
A plain scene that must reach those apps keeps `svgScene` and its lower API.
|
|
1555
|
+
**Extension API 7** adds the scene's host-fed `radio` values (*Radio values*
|
|
1556
|
+
below), `scaleX`/`scaleY` bindings, text `glow`, a knob's `menu` and second
|
|
1557
|
+
axis, and the spectrum control's `follow`, `span` and label `decimals`; a
|
|
1558
|
+
bundle using any of them declares `"api": 7`. Scene-capable hosts pass
|
|
1555
1559
|
`instanceId` on render/event calls. `onEvent` handles control events and returns
|
|
1556
1560
|
value patches (`values`, and `strings` for string controls); local-only controls and animation frames make no bridge
|
|
1557
1561
|
calls. Controls with `continuous: true` send `change` events during dragging and a
|
|
@@ -1561,7 +1565,10 @@ for local numeric formatting; bindings accept `scale`, `truncate` and `modulo`
|
|
|
1561
1565
|
too, applied to the value before the ramp. KI2D’s Radio Panel uses these for its Modern
|
|
1562
1566
|
frequency groups; its settings offer Modern/LCD styles and radio selection. Local sliders may use `hover` and `discrete` hourly buckets. Text `samples`
|
|
1563
1567
|
may hold numbers or preformatted strings; control `valueLabels` supplies accessible
|
|
1564
|
-
value descriptions.
|
|
1568
|
+
value descriptions. A layer binds `rotation`, `translateX`/`translateY`,
|
|
1569
|
+
`scaleX`/`scaleY` and `opacity`; rotation and scale turn about its `pivot`, so
|
|
1570
|
+
`scaleX` with `pivot: [0, 0.5]` grows a bar's fill from its left end.
|
|
1571
|
+
Transform/opacity bindings may also supply numeric `samples`,
|
|
1565
1572
|
equally spaced over their `input` range, instead of the `output` ramp. The host
|
|
1566
1573
|
interpolates and clamps these locally; chart markers and callouts can follow
|
|
1567
1574
|
individual readings without bridge calls. See [the design notes and open milestones](https://github.com/ham2k/halo/blob/main/docs/design/panel-scenes.md)
|
|
@@ -1599,10 +1606,13 @@ Both `environment` and `clock` are optional in the SDK for older-host detection.
|
|
|
1599
1606
|
Use scene **text layers** for typography; SVG `<text>` is not a portable text path
|
|
1600
1607
|
through the renderer. Text supports either `literal` or bound `value`, plus
|
|
1601
1608
|
`fontFamily`, `fontWeight`, `lineHeight`, and `letterSpacing`. Text participates
|
|
1602
|
-
in native directionality, accessibility scaling and semantics.
|
|
1609
|
+
in native directionality, accessibility scaling and semantics. A text `glow`
|
|
1610
|
+
(`#RRGGBB`) draws a soft halo of that colour around the letters, to keep them
|
|
1611
|
+
legible over busy artwork.
|
|
1603
1612
|
Button controls can supply `menu: [{label, event}]` to open a native dropdown.
|
|
1604
1613
|
Selecting an item emits its `event` as the action with the original control ID;
|
|
1605
|
-
dismissing the menu emits nothing.
|
|
1614
|
+
dismissing the menu emits nothing. A knob takes a `menu` too: a click opens it,
|
|
1615
|
+
while a scroll or a drag still turns the knob.
|
|
1606
1616
|
|
|
1607
1617
|
**Native controls.** Besides the drawn `button`, `slider` and `knob` — invisible
|
|
1608
1618
|
hit areas over the extension's own artwork — a scene can use Material widgets
|
|
@@ -1619,7 +1629,11 @@ on leaving it after typing; a render that lands while the operator is typing
|
|
|
1619
1629
|
does not overwrite the typing. Every control's `label` is its accessible name;
|
|
1620
1630
|
the app has no tooltips. A text field or dropdown in a panel keeps keyboard
|
|
1621
1631
|
focus; clicking any other native control hands focus back to the callsign
|
|
1622
|
-
field, as clicking a button anywhere else in the Operation view does.
|
|
1632
|
+
field, as clicking a button anywhere else in the Operation view does. A drawn
|
|
1633
|
+
`button` never takes focus from a click, so the operator's typing stays where
|
|
1634
|
+
it was. A drawn `slider` or `knob` clicked or dragged holds focus for three
|
|
1635
|
+
seconds after its last use — long enough for the arrow keys to adjust it — then
|
|
1636
|
+
hands it back to wherever it came from. Focus that arrives by Tab stays.
|
|
1623
1637
|
|
|
1624
1638
|
**Layout.** A native control either has an `x`/`y`/`width`/`height` rect over the
|
|
1625
1639
|
artwork, like the drawn kinds — it takes the rect's width and its own height, up
|
|
@@ -1664,6 +1678,7 @@ drawing part of it.
|
|
|
1664
1678
|
| `layers` | Painted in order, each over the ones before: `svg` artwork or `text`. At most 128, and 1 MiB of artwork and text in all. |
|
|
1665
1679
|
| `controls` | At most 64. Drawn or native; see below. |
|
|
1666
1680
|
| `layout` | Optional. Where rect-less native controls and the artwork go; omitted, the artwork fills the pane. |
|
|
1681
|
+
| `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*. |
|
|
1667
1682
|
|
|
1668
1683
|
**Controls.** Every control has an `id` and a `label`, which is its accessible
|
|
1669
1684
|
name. An `event` names the action sent when it is used; without one the control
|
|
@@ -1674,7 +1689,7 @@ the extension. `value` names what it shows and sets.
|
|
|
1674
1689
|
|---|---|---|---|
|
|
1675
1690
|
| `button` | — | `activate` | `menu` |
|
|
1676
1691
|
| `slider` | number | `commit` | `min`, `max`, `step`, `discrete`, `hover`, `valueLabels`, `continuous` |
|
|
1677
|
-
| `knob` | number | `commit` | `min`, `max`, `step`, `sensitivity`, `valueLabels`, `continuous` |
|
|
1692
|
+
| `knob` | number | `commit` | `min`, `max`, `step`, `sensitivity`, `valueLabels`, `continuous`, `menu`, a second axis (`valueY`, `eventY`, `minY`, `maxY`, `stepY`, `sensitivityY`) |
|
|
1678
1693
|
| `nativeButton` | — | `activate` | `variant` (`filled`, `tonal`, `outlined`, `text`), `icon`, `menu`; needs `event` or `menu` |
|
|
1679
1694
|
| `nativeSlider` | number | `commit` | `min`, `max`, `step`, `valueLabels`, `continuous` |
|
|
1680
1695
|
| `nativeTextField` | string | `commit` on Enter, or on leaving it after typing | `placeholder`, `continuous` |
|
|
@@ -1702,6 +1717,15 @@ string is `""` or one of them; a `multi` choice holds its values joined with
|
|
|
1702
1717
|
commas in option order, so no option may contain one. `menu` is
|
|
1703
1718
|
`[{label, event}]`, up to 32: picking an item sends its `event` as the action.
|
|
1704
1719
|
|
|
1720
|
+
A knob with `valueY` sets two numbers from one patch of artwork — a passband's
|
|
1721
|
+
shift across and its width up, say. Horizontal dragging, horizontal scrolling
|
|
1722
|
+
and the left/right keys set `value`; vertical ones and the up/down keys set
|
|
1723
|
+
`valueY`, which must be in `values`, with its own range, step and feel from the
|
|
1724
|
+
`…Y` fields. The second axis sends its events with the action `eventY`, which a
|
|
1725
|
+
knob with an `event` must give, different from `event` — an event carries no
|
|
1726
|
+
other mark of which axis moved, and a scene without one is refused. The host queues the two axes separately, so neither
|
|
1727
|
+
replaces the other's pending value, and a drag commits only the axes it moved.
|
|
1728
|
+
|
|
1705
1729
|
**Layout nodes.** Each is an object with exactly one of these keys:
|
|
1706
1730
|
|
|
1707
1731
|
| Node | |
|
|
@@ -1796,6 +1820,11 @@ export const spotter: PanelHook = {
|
|
|
1796
1820
|
}
|
|
1797
1821
|
```
|
|
1798
1822
|
|
|
1823
|
+
The callsign comes back upper-cased, and the field shows the extension's
|
|
1824
|
+
version once the typing is committed. A bundle carrying this declares
|
|
1825
|
+
`"api": 5`. The `k2hrc-panel-scenes` sample in the SDK has every kind, a
|
|
1826
|
+
control faded in over the artwork, and a click-through layer painted over it.
|
|
1827
|
+
|
|
1799
1828
|
#### Spectrum
|
|
1800
1829
|
|
|
1801
1830
|
A `spectrum` control is a radio's panadapter and waterfall, drawn by the host
|
|
@@ -1820,7 +1849,20 @@ control and takes the fields of `PanelSceneSpectrum` in the SDK:
|
|
|
1820
1849
|
- `overlays`: up to 16 lines (`at`) and areas (`from`, `to`) in Hz from the
|
|
1821
1850
|
tuned frequency, the sweep's middle, or absolute, each styled per part.
|
|
1822
1851
|
- `scale`: the gap between the panadapter and the waterfall, its fill and
|
|
1823
|
-
borders, and frequency `labels` and `ticks`.
|
|
1852
|
+
borders, and frequency `labels` and `ticks`. `labels.decimals` (0–6) fixes
|
|
1853
|
+
the decimals of MHz every label shows; omitted, as many as tell one label
|
|
1854
|
+
from the next.
|
|
1855
|
+
- `span`: Hz shown across the control, from the middle of each sweep — a
|
|
1856
|
+
number or a scene value. A wider sweep is cropped to it, so a radio whose
|
|
1857
|
+
scope comes in a few fixed widths can be shown at any narrower one; set the
|
|
1858
|
+
scope to the narrowest that covers it. Everything is drawn, labelled and
|
|
1859
|
+
clicked on the window shown.
|
|
1860
|
+
- `follow` (0–0.45): keeps the tuned frequency at least this share of the
|
|
1861
|
+
width from either edge, by moving the radio's scope just far enough to put
|
|
1862
|
+
it on that margin — the picture scrolls with the dial rather than jumping
|
|
1863
|
+
to centre it. Only where the radio lets the app place its scope (a
|
|
1864
|
+
FlexRadio, whose panadapter in SmartSDR moves with it) and the panel
|
|
1865
|
+
declares `requiresRadioWrite`; elsewhere it changes nothing.
|
|
1824
1866
|
- `annotations`: up to 64 pieces of text at frequencies, with a pointer to
|
|
1825
1867
|
where they sit. One with an `event` sends `activate` when clicked, its
|
|
1826
1868
|
frequency as `value` and its `id` as `annotation`, and does not tune.
|
|
@@ -1828,10 +1870,35 @@ control and takes the fields of `PanelSceneSpectrum` in the SDK:
|
|
|
1828
1870
|
- `tuneStep`: a click elsewhere sends `commit` with the frequency under it,
|
|
1829
1871
|
rounded to this many Hz. The extension decides whether to tune.
|
|
1830
1872
|
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1873
|
+
Each waterfall row stays at the frequencies it was taken on: when the radio
|
|
1874
|
+
retunes its scope the history slides sideways, and when the span changes it
|
|
1875
|
+
stretches, rather than old signals being redrawn where they never were.
|
|
1876
|
+
|
|
1877
|
+
#### Radio values
|
|
1878
|
+
|
|
1879
|
+
A scene with a document-level `radio` block (extension API 7) is fed that
|
|
1880
|
+
radio's live readings by the host, into reserved values a binding, a text
|
|
1881
|
+
layer or a control's `opacity` can read like any other:
|
|
1882
|
+
|
|
1883
|
+
| Value | |
|
|
1884
|
+
|---|---|
|
|
1885
|
+
| `radio.signalDbm` | Receive signal, dBm. |
|
|
1886
|
+
| `radio.powerOut` | RF out, watts. |
|
|
1887
|
+
| `radio.swr`, `radio.alc`, `radio.supplyVolts` | The transmit meters. |
|
|
1888
|
+
| `radio.transmitting` | 1 or 0. |
|
|
1889
|
+
| `radio.swrAverage` | The SWR averaged over the transmission under way, or else over the last one — only readings taken with RF out count. |
|
|
1890
|
+
| `radio.secondsSinceTransmit` | 0 while transmitting, then counting up; a very large number when there has been no transmission. |
|
|
1891
|
+
|
|
1892
|
+
They change as the radio reports them — several times a second — with no
|
|
1893
|
+
render and no bridge call, so a meter needle moves at the radio's rate while
|
|
1894
|
+
the extension renders once a second or not at all. A reading the radio has
|
|
1895
|
+
not given, or no longer fresh, is missing: a binding takes it as the bottom
|
|
1896
|
+
of its range and a text layer shows `—`. `radio.secondsSinceTransmit` is
|
|
1897
|
+
re-read on its own for fifteen seconds after a transmission, so a hold or a
|
|
1898
|
+
fade bound to it ends on time. The names are reserved — a scene may not
|
|
1899
|
+
declare them in `values` or `strings` — and readable only with the block;
|
|
1900
|
+
nothing may set them. The feed needs `requiresRadioRead`: without it every
|
|
1901
|
+
reading is missing.
|
|
1835
1902
|
|
|
1836
1903
|
The `ki2d-weather-panel`, `ki2d-solar-panel` and `ki2d-radio-panel` reference
|
|
1837
1904
|
panels live in
|
|
@@ -1843,8 +1910,10 @@ repository's dashboard README for building them.
|
|
|
1843
1910
|
The radio host calls are manifest capabilities, refused unless declared — the
|
|
1844
1911
|
same shape as `requiresLocation`, and shown to the operator at install:
|
|
1845
1912
|
`"requiresRadioRead": true` allows `host.listRadios()` and `host.readRadio()`;
|
|
1846
|
-
`"requiresRadioWrite": true` allows `host.
|
|
1847
|
-
`host.setRadioConnection()` and `host.setRadioSpectrum()
|
|
1913
|
+
`"requiresRadioWrite": true` allows `host.setRadio()`,
|
|
1914
|
+
`host.setRadioConnection()` and `host.setRadioSpectrum()` — and transmitting
|
|
1915
|
+
with them: `host.sendRadioCw()`, `host.playRadioVoice()`, `host.setRadioPtt()`
|
|
1916
|
+
and `host.stopRadioTransmit()` — and implies read, since a command answers with the
|
|
1848
1917
|
radio's state. An undeclared call rejects rather than answering null.
|
|
1849
1918
|
A capability belongs to the bundle that declared it, and to the sub-extensions
|
|
1850
1919
|
that bundle defines under its own key. A separately installed bundle never
|
|
@@ -1859,19 +1928,93 @@ the hook); the control shows its pending value until the outcome is known.
|
|
|
1859
1928
|
local transceivers. `host.readRadio(id?)` returns
|
|
1860
1929
|
a **local** radio's reported frequency (Hz), mode, connection state,
|
|
1861
1930
|
power, TX state and fresh telemetry, or null on hosts without this API.
|
|
1862
|
-
`meters
|
|
1931
|
+
`meters` holds `signalDbm`, `powerOut`, `swr`, `alc` and `supplyVolts`, each
|
|
1932
|
+
only while its driver has a fresh reading. `meters.signalDbm` is receive power in whole dBm when supported, refreshed a few
|
|
1863
1933
|
times a second; it expires after two seconds without a reading. The Flex driver receives the selected slice’s LEVEL
|
|
1864
|
-
meter through its native UDP channel. The reference panel draws a segmented
|
|
1934
|
+
meter, and the transmitter's forward power and SWR, through its native UDP channel. The reference panel draws a segmented
|
|
1865
1935
|
receive meter and leaves it unlit when unavailable, stale, or transmitting. It does
|
|
1866
|
-
not substitute the app's manually entered VFO for a radio reading.
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1936
|
+
not substitute the app's manually entered VFO for a radio reading. A scene
|
|
1937
|
+
that only draws meters reads them as *Radio values* instead, at the radio's
|
|
1938
|
+
rate. Extension API 7 adds to the answer:
|
|
1939
|
+
|
|
1940
|
+
- `maxWatts`: the model's rated output, where the app knows it; `modes`: the
|
|
1941
|
+
modes `setRadio` accepts for this radio, in the radio's order.
|
|
1942
|
+
- `tuner`: `inline`, `bypassed`, `tuning` or `failed`; null for a radio
|
|
1943
|
+
without one, or whose driver cannot read it.
|
|
1944
|
+
- `passband`: `{lowHz, highHz}`, the receive filter's edges in Hz from the
|
|
1945
|
+
tuned frequency as the radio shows them — `100`…`2800` in USB,
|
|
1946
|
+
`-2800`…`-100` in LSB, around zero in CW — or null where its driver cannot
|
|
1947
|
+
read it.
|
|
1948
|
+
- `antennas`: `{rxPorts, txPorts, rx, tx, separate}`, the radio's own antenna
|
|
1949
|
+
ports by the radio's names and which it uses, or null where its driver
|
|
1950
|
+
cannot read them. `separate`: it picks receive and transmit independently,
|
|
1951
|
+
as a FlexRadio does; otherwise one port serves both.
|
|
1952
|
+
- `keying`: what the app keys with — `canKeyCw`, `cwSending`,
|
|
1953
|
+
`keyerSpeedWpm` (the key's own report, null before it has given one),
|
|
1954
|
+
`keyerSpeedRange`, `canPtt`, `ptt` (whether the app's PTT latch holds the
|
|
1955
|
+
transmitter) and `voiceMemories` (`[{number, label}]`, empty for a radio
|
|
1956
|
+
that plays none) — while this is the logging radio, connected, on a host
|
|
1957
|
+
with keying enabled; null otherwise.
|
|
1958
|
+
- `lastTransmission`: `{swrAverage, endedAtMillis}` for the last
|
|
1959
|
+
transmission the radio finished since the app started — its SWR averaged over the readings
|
|
1960
|
+
taken with RF out (null when there were none), and when it ended, in real
|
|
1961
|
+
milliseconds to compare with `clock.realNowMillis` — or null.
|
|
1962
|
+
|
|
1963
|
+
`host.setRadio({id, selection?, ...settings})` sets any of these on that same
|
|
1964
|
+
radio, through the existing VFO/CAT service. It and the keying calls below are
|
|
1965
|
+
extension API 7: a bundle using them declares `"api": 7`.
|
|
1966
|
+
|
|
1967
|
+
- `frequencyHz`; `dial: true` says it comes from an on-screen dial turning,
|
|
1968
|
+
not a typed or clicked target, and on the logging radio Smart VFO Snap
|
|
1969
|
+
settles it like the radio's own knob.
|
|
1970
|
+
- `mode`: one of `modes`.
|
|
1971
|
+
- `powerWatts`: up to `maxWatts` where that is stated. On the logging radio
|
|
1972
|
+
it goes through the app's VFO, so it is the power the next QSO logs.
|
|
1973
|
+
- `keyerSpeedWpm`: the logging radio's keyer, which clamps to its own range —
|
|
1974
|
+
`keying.keyerSpeedWpm` says what it took.
|
|
1975
|
+
- `tunerInline`: puts the antenna tuner in line or bypasses it. On a
|
|
1976
|
+
FlexRadio, whose tuner has no other way in, in line is a TUNE — a carrier
|
|
1977
|
+
on the air; other radios switch it without transmitting.
|
|
1978
|
+
- `passband`: `{lowHz, highHz}`, as `passband` reports it. A radio that sets
|
|
1979
|
+
only a width from a table takes the nearest, keeping its own shift — read
|
|
1980
|
+
the result back from `passband`. It applies to the mode the radio is in: a
|
|
1981
|
+
call that also changes `mode` is `invalid`.
|
|
1982
|
+
- `antenna`: `{rx?, tx?}`, ports from `antennas`; without `separate`, either
|
|
1983
|
+
names the one port, and two different ones are `invalid`. An `rx` naming
|
|
1984
|
+
the `tx` asked for in the same call is taken even where `rxPorts` does not
|
|
1985
|
+
list it yet.
|
|
1986
|
+
|
|
1987
|
+
The host rejects a changed designation (`changed`), a disconnected or stale
|
|
1988
|
+
radio (`disconnected`), a transmission in progress (`transmitting`) and a
|
|
1989
|
+
malformed command or a value out of range (`invalid`); a setting this radio
|
|
1990
|
+
cannot take — a tuner, passband or antenna its readout has as null, a keyer
|
|
1991
|
+
speed without `keying.canKeyCw` — answers `unsupported`. Accepted requests are
|
|
1992
|
+
spaced by at least 250 ms, since a dragged control would otherwise flood the
|
|
1993
|
+
CAT link. Omitting a selection follows the designated logging radio; an explicit `selection`
|
|
1994
|
+
pins the command to that local radio without changing the logging designation.
|
|
1873
1995
|
An accepted result means requested, not confirmed; only subsequent
|
|
1874
1996
|
reported state confirms it. Drivers retain their own protocol/echo handling.
|
|
1997
|
+
|
|
1998
|
+
The keying calls transmit, and act only on the logging radio, on a host with
|
|
1999
|
+
keying enabled — a panel pinned to another radio is answered `unsupported`
|
|
2000
|
+
rather than keying the logging radio behind the operator's back:
|
|
2001
|
+
|
|
2002
|
+
- `host.sendRadioCw({id, selection?, text})` keys `text` (up to 1000
|
|
2003
|
+
characters) on the key in use, as the keyer area's messages do.
|
|
2004
|
+
- `host.playRadioVoice({id, selection?, memory})` plays one of
|
|
2005
|
+
`keying.voiceMemories` once. It never arms a repeat: a CQ repeating on a
|
|
2006
|
+
panel's say-so is one the operator did not start where they would stop it.
|
|
2007
|
+
- `host.setRadioPtt({id, selection?, on})` keys or unkeys PTT through the
|
|
2008
|
+
app's own latch, so Escape releases it as it does the operator's own.
|
|
2009
|
+
`on: false` releases whatever key the app holds and is never refused; a
|
|
2010
|
+
radio that does not take the key answers `on: true` with `disconnected`.
|
|
2011
|
+
- `host.stopRadioTransmit({id, selection?})` stops every CW message, voice
|
|
2012
|
+
memory and PTT the app is keying, as Escape does, whatever radio the
|
|
2013
|
+
identity names: a stop is never the call that is refused.
|
|
2014
|
+
|
|
2015
|
+
A radio that cannot do what is asked (no CW key, no voice memories, no PTT)
|
|
2016
|
+
answers `unsupported`; one that is not connected, `disconnected`. These are
|
|
2017
|
+
not paced, and not refused for a transmission in progress.
|
|
1875
2018
|
`host.setRadioConnection({id, selection?, connected})` starts or ends a local CAT
|
|
1876
2019
|
connection using the same selection and identity checks. Acceptance starts the
|
|
1877
2020
|
operation; follow reported connection state for completion. It does not switch
|
package/package.json
CHANGED
package/samples/README.md
CHANGED
|
@@ -11,7 +11,7 @@ building — the header comment in each `src/index.ts` says what it leaves out.
|
|
|
11
11
|
| `k2hrc-cqww` | A contest: an exchange to type, and a score to keep |
|
|
12
12
|
| `k2hrc-radio` | An HTML panel, and why you should probably write markdown instead |
|
|
13
13
|
| `k2hrc-panel-scenes` | Experimental panel scenes: SVG artwork, controls and animations, with simulated radio and weather panels |
|
|
14
|
-
| `k2hrc-panafall` | A radio's panadapter and waterfall in a scene, styled and tuned by click (extension API
|
|
14
|
+
| `k2hrc-panafall` | A radio's panadapter and waterfall in a scene, styled and tuned by click (extension API 7) |
|
|
15
15
|
|
|
16
16
|
## Running one
|
|
17
17
|
|
|
@@ -224,7 +224,7 @@ export function createPanafall(): PanelHook {
|
|
|
224
224
|
if (!radio?.id) return { values: {} }
|
|
225
225
|
if ((action === "tune" || action === "goto") && typeof value === "number") {
|
|
226
226
|
// Already on the step: the host snapped it.
|
|
227
|
-
await host.
|
|
227
|
+
await host.setRadio({ id: radio.id, frequencyHz: Math.round(value) })
|
|
228
228
|
} else if (action === "span" && text) {
|
|
229
229
|
await host.setRadioSpectrum({ id: radio.id, spanHz: Number(text) })
|
|
230
230
|
} else if (action === "ref" && typeof value === "number") {
|