Skip to content

Commit 66c8c48

Browse files
committed
feat: add spectrum fixed display controls
1 parent 6895233 commit 66c8c48

6 files changed

Lines changed: 208 additions & 6 deletions

File tree

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,11 +204,29 @@ Spectrum API summary:
204204
- `getSpectrumCapabilities()` returns conservative backend metadata exposed by the native addon.
205205
- `getSpectrumSupportSummary()` returns a product-oriented summary of whether official spectrum streaming is usable on the current rig/backend.
206206
- `configureSpectrum()` applies supported `SPECTRUM_*` levels and optional `SPECTRUM_HOLD`.
207+
- `getSpectrumDisplayState()` returns a normalized display state with `mode/span/fixed edges/edge slot`.
208+
- `configureSpectrumDisplay()` applies a normalized display config and reads back the resulting state.
209+
- `getSpectrumEdgeSlot()` / `setSpectrumEdgeSlot()` expose backend edge-slot control when available.
210+
- `getSpectrumFixedEdges()` / `setSpectrumFixedEdges()` expose direct fixed-range control using `SPECTRUM_EDGE_LOW/HIGH`.
207211
- `startSpectrumStream(callback?)` registers the official Hamlib spectrum callback only.
208212
- `stopSpectrumStream()` unregisters the official spectrum callback.
209213
- `startManagedSpectrum(config?)` runs the validated startup sequence for Icom/Hamlib async spectrum.
210214
- `stopManagedSpectrum()` runs the symmetric shutdown sequence and unregisters the callback.
211215

216+
Fixed-range example:
217+
218+
```javascript
219+
await rig.configureSpectrumDisplay({
220+
mode: 'fixed',
221+
edgeSlot: 1,
222+
edgeLowHz: 14074000,
223+
edgeHighHz: 14077000,
224+
});
225+
226+
const displayState = await rig.getSpectrumDisplayState();
227+
console.log(displayState);
228+
```
229+
212230
Emitted events:
213231

214232
- `spectrumLine` carries a single `SpectrumLine` object with frequency edges, mode, and raw bin payload.

index.d.ts

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,17 +200,40 @@ interface SpectrumSupportSummary extends SpectrumCapabilities {
200200
hasSpectrumHoldFunction: boolean;
201201
hasTransceiveFunction: boolean;
202202
configurableLevels: string[];
203+
supportsFixedEdges: boolean;
204+
supportsEdgeSlotSelection: boolean;
205+
supportedEdgeSlots: number[];
203206
}
204207

208+
type SpectrumDisplayMode = 'center' | 'fixed' | 'scroll-center' | 'scroll-fixed';
209+
205210
interface SpectrumConfig {
206211
hold?: boolean;
207-
mode?: number;
212+
mode?: number | SpectrumDisplayMode;
208213
spanHz?: number;
214+
edgeSlot?: number;
215+
edgeLowHz?: number;
216+
edgeHighHz?: number;
209217
speed?: number;
210218
referenceLevel?: number;
211219
averageMode?: number;
212220
}
213221

222+
interface SpectrumDisplayState {
223+
mode: SpectrumDisplayMode | null;
224+
modeId: number | null;
225+
modeName: string | null;
226+
spanHz: number | null;
227+
edgeSlot: number | null;
228+
edgeLowHz: number | null;
229+
edgeHighHz: number | null;
230+
supportedModes: SpectrumModeInfo[];
231+
supportedSpans: number[];
232+
supportedEdgeSlots: number[];
233+
supportsFixedEdges: boolean;
234+
supportsEdgeSlotSelection: boolean;
235+
}
236+
214237
/**
215238
* Split mode info interface
216239
*/
@@ -1287,6 +1310,41 @@ declare class HamLib extends EventEmitter {
12871310
*/
12881311
configureSpectrum(config?: SpectrumConfig): Promise<SpectrumSupportSummary>;
12891312

1313+
/**
1314+
* Get the current spectrum edge slot if exposed by the backend.
1315+
*/
1316+
getSpectrumEdgeSlot(): Promise<number>;
1317+
1318+
/**
1319+
* Set the current spectrum edge slot if exposed by the backend.
1320+
*/
1321+
setSpectrumEdgeSlot(slot: number): Promise<number>;
1322+
1323+
/**
1324+
* Get supported spectrum edge slots.
1325+
*/
1326+
getSpectrumSupportedEdgeSlots(): Promise<number[]>;
1327+
1328+
/**
1329+
* Read current fixed spectrum edges.
1330+
*/
1331+
getSpectrumFixedEdges(): Promise<{lowHz: number, highHz: number}>;
1332+
1333+
/**
1334+
* Set current fixed spectrum edges.
1335+
*/
1336+
setSpectrumFixedEdges(range: {lowHz: number, highHz: number}): Promise<{lowHz: number, highHz: number}>;
1337+
1338+
/**
1339+
* Get a normalized spectrum display state for application use.
1340+
*/
1341+
getSpectrumDisplayState(): Promise<SpectrumDisplayState>;
1342+
1343+
/**
1344+
* Configure spectrum display state using a normalized application-facing shape.
1345+
*/
1346+
configureSpectrumDisplay(config?: SpectrumConfig): Promise<SpectrumDisplayState>;
1347+
12901348
/**
12911349
* Start receiving official Hamlib spectrum line events.
12921350
*/

lib/index.js

Lines changed: 126 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@ const nodeGypBuild = require('node-gyp-build');
44
// Ensure loader resolves from package root (contains prebuilds/ and build/)
55
const nativeModule = nodeGypBuild(path.join(__dirname, '..'));
66

7+
const DEFAULT_SPECTRUM_EDGE_SLOTS = [1, 2, 3, 4];
8+
9+
function normalizeSpectrumModeName(name) {
10+
const normalized = String(name || '').trim().toLowerCase();
11+
if (normalized === 'center') return 'center';
12+
if (normalized === 'fixed') return 'fixed';
13+
if (normalized === 'center scroll' || normalized === 'center-scroll' || normalized === 'scroll-center') return 'scroll-center';
14+
if (normalized === 'fixed scroll' || normalized === 'fixed-scroll' || normalized === 'scroll-fixed') return 'scroll-fixed';
15+
return null;
16+
}
17+
718
/**
819
* HamLib class for controlling amateur radio devices
920
*
@@ -1180,8 +1191,16 @@ class HamLib extends EventEmitter {
11801191
const hasSpectrumHoldFunction = supportedFunctions.includes('SPECTRUM_HOLD');
11811192
const hasTransceiveFunction = supportedFunctions.includes('TRANSCEIVE');
11821193
const asyncDataSupported = capabilities.asyncDataSupported ?? hasSpectrumFunction;
1183-
const configurableLevels = ['SPECTRUM_MODE', 'SPECTRUM_SPAN', 'SPECTRUM_SPEED', 'SPECTRUM_REF', 'SPECTRUM_AVG']
1194+
const configurableLevels = ['SPECTRUM_MODE', 'SPECTRUM_SPAN', 'SPECTRUM_EDGE_LOW', 'SPECTRUM_EDGE_HIGH', 'SPECTRUM_SPEED', 'SPECTRUM_REF', 'SPECTRUM_AVG']
11841195
.filter((name) => supportedLevels.includes(name));
1196+
let supportsEdgeSlotSelection = false;
1197+
1198+
try {
1199+
const edgeSlot = await this.getConf('SPECTRUM_EDGE');
1200+
supportsEdgeSlotSelection = Number.isFinite(Number.parseInt(String(edgeSlot), 10));
1201+
} catch (_) {
1202+
supportsEdgeSlotSelection = false;
1203+
}
11851204

11861205
return {
11871206
supported: Boolean(hasSpectrumFunction),
@@ -1190,6 +1209,9 @@ class HamLib extends EventEmitter {
11901209
hasSpectrumHoldFunction,
11911210
hasTransceiveFunction,
11921211
configurableLevels,
1212+
supportsFixedEdges: configurableLevels.includes('SPECTRUM_EDGE_LOW') && configurableLevels.includes('SPECTRUM_EDGE_HIGH'),
1213+
supportsEdgeSlotSelection,
1214+
supportedEdgeSlots: supportsEdgeSlotSelection ? [...DEFAULT_SPECTRUM_EDGE_SLOTS] : [],
11931215
scopes: capabilities.scopes ?? [],
11941216
modes: capabilities.modes ?? [],
11951217
spans: capabilities.spans ?? [],
@@ -1208,20 +1230,122 @@ class HamLib extends EventEmitter {
12081230
if (value === undefined || !summary.configurableLevels.includes(name)) return;
12091231
await this.setLevel(name, value);
12101232
};
1233+
const resolveModeId = async (mode) => {
1234+
if (mode === undefined || mode === null) return undefined;
1235+
if (Number.isFinite(mode)) return mode;
1236+
const requested = normalizeSpectrumModeName(mode);
1237+
if (!requested) {
1238+
throw new Error(`Unsupported spectrum mode: ${mode}`);
1239+
}
1240+
const matched = (summary.modes ?? []).find((entry) => normalizeSpectrumModeName(entry?.name) === requested);
1241+
if (!matched) {
1242+
throw new Error(`Spectrum mode not supported by this backend: ${mode}`);
1243+
}
1244+
return matched.id;
1245+
};
12111246

12121247
if (summary.hasSpectrumHoldFunction && config.hold !== undefined) {
12131248
await this.setFunction('SPECTRUM_HOLD', Boolean(config.hold));
12141249
}
12151250

1216-
await applyLevel('SPECTRUM_MODE', config.mode);
1251+
if (config.edgeSlot !== undefined && summary.supportsEdgeSlotSelection) {
1252+
await this.setSpectrumEdgeSlot(config.edgeSlot);
1253+
}
1254+
1255+
await applyLevel('SPECTRUM_MODE', await resolveModeId(config.mode));
12171256
await applyLevel('SPECTRUM_SPAN', config.spanHz);
1257+
await applyLevel('SPECTRUM_EDGE_LOW', config.edgeLowHz);
1258+
await applyLevel('SPECTRUM_EDGE_HIGH', config.edgeHighHz);
12181259
await applyLevel('SPECTRUM_SPEED', config.speed);
12191260
await applyLevel('SPECTRUM_REF', config.referenceLevel);
12201261
await applyLevel('SPECTRUM_AVG', config.averageMode);
12211262

12221263
return summary;
12231264
}
12241265

1266+
async getSpectrumEdgeSlot() {
1267+
const raw = await this.getConf('SPECTRUM_EDGE');
1268+
const parsed = Number.parseInt(String(raw), 10);
1269+
if (!Number.isFinite(parsed)) {
1270+
throw new Error('Spectrum edge slot is not available');
1271+
}
1272+
return parsed;
1273+
}
1274+
1275+
async setSpectrumEdgeSlot(slot) {
1276+
const parsed = Number.parseInt(String(slot), 10);
1277+
if (!Number.isFinite(parsed) || parsed < 1) {
1278+
throw new Error(`Invalid spectrum edge slot: ${slot}`);
1279+
}
1280+
await this.setConf('SPECTRUM_EDGE', String(parsed));
1281+
return parsed;
1282+
}
1283+
1284+
async getSpectrumSupportedEdgeSlots() {
1285+
const summary = await this.getSpectrumSupportSummary();
1286+
return summary.supportedEdgeSlots ?? [];
1287+
}
1288+
1289+
async getSpectrumFixedEdges() {
1290+
const [lowHz, highHz] = await Promise.all([
1291+
this.getLevel('SPECTRUM_EDGE_LOW'),
1292+
this.getLevel('SPECTRUM_EDGE_HIGH'),
1293+
]);
1294+
return { lowHz, highHz };
1295+
}
1296+
1297+
async setSpectrumFixedEdges({ lowHz, highHz }) {
1298+
if (!Number.isFinite(lowHz) || !Number.isFinite(highHz) || lowHz >= highHz) {
1299+
throw new Error('Spectrum fixed edge range must satisfy lowHz < highHz');
1300+
}
1301+
await this.setLevel('SPECTRUM_EDGE_LOW', lowHz);
1302+
await this.setLevel('SPECTRUM_EDGE_HIGH', highHz);
1303+
return { lowHz, highHz };
1304+
}
1305+
1306+
async getSpectrumDisplayState() {
1307+
const summary = await this.getSpectrumSupportSummary();
1308+
const [modeId, spanHz, fixedEdges, edgeSlot] = await Promise.all([
1309+
summary.configurableLevels.includes('SPECTRUM_MODE') ? this.getLevel('SPECTRUM_MODE') : Promise.resolve(null),
1310+
summary.configurableLevels.includes('SPECTRUM_SPAN') ? this.getLevel('SPECTRUM_SPAN') : Promise.resolve(null),
1311+
summary.supportsFixedEdges ? this.getSpectrumFixedEdges() : Promise.resolve(null),
1312+
summary.supportsEdgeSlotSelection ? this.getSpectrumEdgeSlot().catch(() => null) : Promise.resolve(null),
1313+
]);
1314+
const modeInfo = (summary.modes ?? []).find((entry) => entry.id === modeId) ?? null;
1315+
const mode = normalizeSpectrumModeName(modeInfo?.name);
1316+
const edgeLowHz = fixedEdges?.lowHz ?? null;
1317+
const edgeHighHz = fixedEdges?.highHz ?? null;
1318+
const derivedSpanHz = (edgeLowHz !== null && edgeHighHz !== null) ? (edgeHighHz - edgeLowHz) : null;
1319+
1320+
return {
1321+
mode,
1322+
modeId,
1323+
modeName: modeInfo?.name ?? null,
1324+
spanHz: spanHz ?? derivedSpanHz,
1325+
edgeSlot,
1326+
edgeLowHz,
1327+
edgeHighHz,
1328+
supportedModes: summary.modes ?? [],
1329+
supportedSpans: summary.spans ?? [],
1330+
supportedEdgeSlots: summary.supportedEdgeSlots ?? [],
1331+
supportsFixedEdges: Boolean(summary.supportsFixedEdges),
1332+
supportsEdgeSlotSelection: Boolean(summary.supportsEdgeSlotSelection),
1333+
};
1334+
}
1335+
1336+
async configureSpectrumDisplay(config = {}) {
1337+
const normalizedConfig = { ...config };
1338+
if ((normalizedConfig.mode === 'fixed' || normalizedConfig.mode === 'scroll-fixed')
1339+
&& normalizedConfig.edgeLowHz !== undefined
1340+
&& normalizedConfig.edgeHighHz !== undefined
1341+
&& normalizedConfig.edgeLowHz >= normalizedConfig.edgeHighHz) {
1342+
throw new Error('Spectrum fixed edge range must satisfy edgeLowHz < edgeHighHz');
1343+
}
1344+
1345+
await this.configureSpectrum(normalizedConfig);
1346+
return this.getSpectrumDisplayState();
1347+
}
1348+
12251349
/**
12261350
* Start the official Hamlib spectrum callback stream.
12271351
* @param {(line: Object) => void} [callback]

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "hamlib",
3-
"version": "0.3.1",
3+
"version": "0.3.2",
44
"description": "Node.js bindings for Hamlib rig control with official spectrum streaming support",
55
"main": "index.js",
66
"module": "lib/index.mjs",

test/test_loader.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ try {
128128
console.log('\n🆕 补齐 API 方法存在性测试:');
129129
const newApiMethods = [
130130
'getInfo', 'sendRaw', 'getSpectrumCapabilities', 'getSpectrumSupportSummary', 'configureSpectrum',
131+
'getSpectrumDisplayState', 'configureSpectrumDisplay', 'getSpectrumEdgeSlot', 'setSpectrumEdgeSlot',
132+
'getSpectrumSupportedEdgeSlots', 'getSpectrumFixedEdges', 'setSpectrumFixedEdges',
131133
'startSpectrumStream', 'stopSpectrumStream', 'startManagedSpectrum', 'stopManagedSpectrum', 'setConf', 'getConf',
132134
'getPassbandNormal', 'getPassbandNarrow', 'getPassbandWide',
133135
'getResolution',

0 commit comments

Comments
 (0)