hamlib 0.2.6 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -6
- package/binding.gyp +8 -2
- package/docs/build-hamlib-from-source.md +640 -0
- package/docs/prebuilt-bundling.md +347 -0
- package/index.d.ts +127 -7
- package/lib/index.js +162 -1
- package/package.json +5 -2
- package/prebuilds/darwin-arm64/libhamlib.4.dylib +0 -0
- package/prebuilds/darwin-arm64/node.napi.node +0 -0
- package/prebuilds/darwin-x64/libhamlib.4.dylib +0 -0
- package/prebuilds/darwin-x64/node.napi.node +0 -0
- package/prebuilds/linux-arm64/libhamlib.so +0 -0
- package/prebuilds/linux-arm64/libhamlib.so.4 +0 -0
- package/prebuilds/linux-arm64/libhamlib.so.4.0.7 +0 -0
- package/prebuilds/linux-arm64/node.napi.node +0 -0
- package/prebuilds/linux-x64/libhamlib.so +0 -0
- package/prebuilds/linux-x64/libhamlib.so.4 +0 -0
- package/prebuilds/linux-x64/libhamlib.so.4.0.7 +0 -0
- package/prebuilds/linux-x64/node.napi.node +0 -0
- package/prebuilds/win32-x64/hamlib_shim.dll +0 -0
- package/prebuilds/win32-x64/node.napi.node +0 -0
- package/src/hamlib.cpp +216 -7
- package/src/hamlib.h +14 -0
- package/src/shim/hamlib_shim.c +125 -0
- package/src/shim/hamlib_shim.h +53 -0
package/lib/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
|
+
const { EventEmitter } = require('events');
|
|
2
3
|
const nodeGypBuild = require('node-gyp-build');
|
|
3
4
|
// Ensure loader resolves from package root (contains prebuilds/ and build/)
|
|
4
5
|
const nativeModule = nodeGypBuild(path.join(__dirname, '..'));
|
|
@@ -9,7 +10,7 @@ const nativeModule = nodeGypBuild(path.join(__dirname, '..'));
|
|
|
9
10
|
* This is a wrapper around the native hamlib module that provides
|
|
10
11
|
* a consistent interface for controlling amateur radio devices.
|
|
11
12
|
*/
|
|
12
|
-
class HamLib {
|
|
13
|
+
class HamLib extends EventEmitter {
|
|
13
14
|
/**
|
|
14
15
|
* Create a new HamLib instance
|
|
15
16
|
* @param {number} model - Radio model number (run `rigctl -l` to find your device model)
|
|
@@ -18,7 +19,9 @@ class HamLib {
|
|
|
18
19
|
* - Network: 'localhost:4532' or '192.168.1.100:4532' (auto-switches to NETRIGCTL mode)
|
|
19
20
|
*/
|
|
20
21
|
constructor(model, port) {
|
|
22
|
+
super();
|
|
21
23
|
this._nativeInstance = new nativeModule.HamLib(model, port);
|
|
24
|
+
this._managedSpectrumRunning = false;
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
/**
|
|
@@ -187,6 +190,9 @@ class HamLib {
|
|
|
187
190
|
* Connection can be re-established by calling open()
|
|
188
191
|
*/
|
|
189
192
|
async close() {
|
|
193
|
+
if (this._managedSpectrumRunning) {
|
|
194
|
+
await this.stopManagedSpectrum();
|
|
195
|
+
}
|
|
190
196
|
return this._nativeInstance.close();
|
|
191
197
|
}
|
|
192
198
|
|
|
@@ -195,6 +201,9 @@ class HamLib {
|
|
|
195
201
|
* Object reference should be deleted after calling this
|
|
196
202
|
*/
|
|
197
203
|
async destroy() {
|
|
204
|
+
if (this._managedSpectrumRunning) {
|
|
205
|
+
await this.stopManagedSpectrum();
|
|
206
|
+
}
|
|
198
207
|
return this._nativeInstance.destroy();
|
|
199
208
|
}
|
|
200
209
|
|
|
@@ -1151,6 +1160,158 @@ class HamLib {
|
|
|
1151
1160
|
return this._nativeInstance.sendRaw(data, replyMaxLen);
|
|
1152
1161
|
}
|
|
1153
1162
|
|
|
1163
|
+
/**
|
|
1164
|
+
* Get official Hamlib spectrum capability metadata from the backend.
|
|
1165
|
+
* @returns {Promise<Object>} Spectrum capability object.
|
|
1166
|
+
*/
|
|
1167
|
+
async getSpectrumCapabilities() {
|
|
1168
|
+
return this._nativeInstance.getSpectrumCapabilities();
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
/**
|
|
1172
|
+
* Summarize whether official Hamlib spectrum streaming is usable for this rig.
|
|
1173
|
+
* @returns {Promise<Object>} Product-oriented support summary.
|
|
1174
|
+
*/
|
|
1175
|
+
async getSpectrumSupportSummary() {
|
|
1176
|
+
const capabilities = await this.getSpectrumCapabilities();
|
|
1177
|
+
const supportedFunctions = this.getSupportedFunctions();
|
|
1178
|
+
const supportedLevels = this.getSupportedLevels();
|
|
1179
|
+
const hasSpectrumFunction = supportedFunctions.includes('SPECTRUM');
|
|
1180
|
+
const hasSpectrumHoldFunction = supportedFunctions.includes('SPECTRUM_HOLD');
|
|
1181
|
+
const hasTransceiveFunction = supportedFunctions.includes('TRANSCEIVE');
|
|
1182
|
+
const asyncDataSupported = capabilities.asyncDataSupported ?? hasSpectrumFunction;
|
|
1183
|
+
const configurableLevels = ['SPECTRUM_MODE', 'SPECTRUM_SPAN', 'SPECTRUM_SPEED', 'SPECTRUM_REF', 'SPECTRUM_AVG']
|
|
1184
|
+
.filter((name) => supportedLevels.includes(name));
|
|
1185
|
+
|
|
1186
|
+
return {
|
|
1187
|
+
supported: Boolean(hasSpectrumFunction),
|
|
1188
|
+
asyncDataSupported: Boolean(asyncDataSupported),
|
|
1189
|
+
hasSpectrumFunction,
|
|
1190
|
+
hasSpectrumHoldFunction,
|
|
1191
|
+
hasTransceiveFunction,
|
|
1192
|
+
configurableLevels,
|
|
1193
|
+
scopes: capabilities.scopes ?? [],
|
|
1194
|
+
modes: capabilities.modes ?? [],
|
|
1195
|
+
spans: capabilities.spans ?? [],
|
|
1196
|
+
avgModes: capabilities.avgModes ?? [],
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
/**
|
|
1201
|
+
* Apply official Hamlib spectrum settings using high-level level/function APIs.
|
|
1202
|
+
* Unsupported settings are skipped silently.
|
|
1203
|
+
* @param {Object} [config]
|
|
1204
|
+
*/
|
|
1205
|
+
async configureSpectrum(config = {}) {
|
|
1206
|
+
const summary = await this.getSpectrumSupportSummary();
|
|
1207
|
+
const applyLevel = async (name, value) => {
|
|
1208
|
+
if (value === undefined || !summary.configurableLevels.includes(name)) return;
|
|
1209
|
+
await this.setLevel(name, value);
|
|
1210
|
+
};
|
|
1211
|
+
|
|
1212
|
+
if (summary.hasSpectrumHoldFunction && config.hold !== undefined) {
|
|
1213
|
+
await this.setFunction('SPECTRUM_HOLD', Boolean(config.hold));
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
await applyLevel('SPECTRUM_MODE', config.mode);
|
|
1217
|
+
await applyLevel('SPECTRUM_SPAN', config.spanHz);
|
|
1218
|
+
await applyLevel('SPECTRUM_SPEED', config.speed);
|
|
1219
|
+
await applyLevel('SPECTRUM_REF', config.referenceLevel);
|
|
1220
|
+
await applyLevel('SPECTRUM_AVG', config.averageMode);
|
|
1221
|
+
|
|
1222
|
+
return summary;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
/**
|
|
1226
|
+
* Start the official Hamlib spectrum callback stream.
|
|
1227
|
+
* @param {(line: Object) => void} [callback]
|
|
1228
|
+
* @returns {Promise<boolean>}
|
|
1229
|
+
*/
|
|
1230
|
+
async startSpectrumStream(callback) {
|
|
1231
|
+
const listener = typeof callback === 'function'
|
|
1232
|
+
? callback
|
|
1233
|
+
: (line) => this.emit('spectrumLine', line);
|
|
1234
|
+
return this._nativeInstance.startSpectrumStream(listener);
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
/**
|
|
1238
|
+
* Stop the official Hamlib spectrum callback stream.
|
|
1239
|
+
* @returns {Promise<boolean>}
|
|
1240
|
+
*/
|
|
1241
|
+
async stopSpectrumStream() {
|
|
1242
|
+
return this._nativeInstance.stopSpectrumStream();
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
/**
|
|
1246
|
+
* High-level managed spectrum startup for application use.
|
|
1247
|
+
* @param {Object} [config]
|
|
1248
|
+
*/
|
|
1249
|
+
async startManagedSpectrum(config = {}) {
|
|
1250
|
+
const summary = await this.getSpectrumSupportSummary();
|
|
1251
|
+
if (!summary.supported) {
|
|
1252
|
+
throw new Error('Official Hamlib spectrum streaming is not supported by this rig/backend');
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
if (this._managedSpectrumRunning) {
|
|
1256
|
+
return true;
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
await this.startSpectrumStream((line) => this.emit('spectrumLine', line));
|
|
1260
|
+
|
|
1261
|
+
try {
|
|
1262
|
+
try {
|
|
1263
|
+
await this.setConf('async', '1');
|
|
1264
|
+
} catch (_) {
|
|
1265
|
+
try {
|
|
1266
|
+
await this.setConf('async', 'True');
|
|
1267
|
+
} catch (_) {
|
|
1268
|
+
// Not every backend accepts the config write even if async is supported.
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
await this.configureSpectrum({ hold: false, ...config });
|
|
1273
|
+
await this.setFunction('SPECTRUM', true);
|
|
1274
|
+
if (summary.hasTransceiveFunction) {
|
|
1275
|
+
await this.setFunction('TRANSCEIVE', true);
|
|
1276
|
+
}
|
|
1277
|
+
} catch (error) {
|
|
1278
|
+
try {
|
|
1279
|
+
await this.stopManagedSpectrum();
|
|
1280
|
+
} catch (_) {
|
|
1281
|
+
// Ignore cleanup failures and surface the original startup error.
|
|
1282
|
+
}
|
|
1283
|
+
throw error;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
this._managedSpectrumRunning = true;
|
|
1287
|
+
this.emit('spectrumStateChanged', { active: true });
|
|
1288
|
+
return true;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
/**
|
|
1292
|
+
* High-level managed spectrum shutdown for application use.
|
|
1293
|
+
*/
|
|
1294
|
+
async stopManagedSpectrum() {
|
|
1295
|
+
try {
|
|
1296
|
+
const supportedFunctions = this.getSupportedFunctions();
|
|
1297
|
+
if (supportedFunctions.includes('TRANSCEIVE')) {
|
|
1298
|
+
await this.setFunction('TRANSCEIVE', false);
|
|
1299
|
+
}
|
|
1300
|
+
if (supportedFunctions.includes('SPECTRUM_HOLD')) {
|
|
1301
|
+
await this.setFunction('SPECTRUM_HOLD', false);
|
|
1302
|
+
}
|
|
1303
|
+
if (supportedFunctions.includes('SPECTRUM')) {
|
|
1304
|
+
await this.setFunction('SPECTRUM', false);
|
|
1305
|
+
}
|
|
1306
|
+
} finally {
|
|
1307
|
+
await this.stopSpectrumStream();
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
this._managedSpectrumRunning = false;
|
|
1311
|
+
this.emit('spectrumStateChanged', { active: false });
|
|
1312
|
+
return true;
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1154
1315
|
/**
|
|
1155
1316
|
* Set a backend configuration parameter
|
|
1156
1317
|
* @param {string} name - Configuration token name
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hamlib",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Node.js
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Node.js bindings for Hamlib rig control with official spectrum streaming support",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"module": "lib/index.mjs",
|
|
7
7
|
"types": "index.d.ts",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"lib/",
|
|
31
31
|
"prebuilds/",
|
|
32
32
|
"src/",
|
|
33
|
+
"docs/",
|
|
33
34
|
"index.js",
|
|
34
35
|
"index.d.ts",
|
|
35
36
|
"binding.gyp",
|
|
@@ -43,9 +44,11 @@
|
|
|
43
44
|
"rebuild": "node-gyp rebuild",
|
|
44
45
|
"clean": "node-gyp clean",
|
|
45
46
|
"test": "node test/test_loader.js",
|
|
47
|
+
"test:version": "node test/test_version.js",
|
|
46
48
|
"test:network": "node test/test_network.js",
|
|
47
49
|
"test:dummy": "node test/test_dummy_complete.js",
|
|
48
50
|
"test:serial": "node test/test_serial_config.js",
|
|
51
|
+
"test:spectrum": "node test/test_spectrum_stream.js",
|
|
49
52
|
"prebuild": "prebuildify --napi --strip",
|
|
50
53
|
"prepare": "",
|
|
51
54
|
"bundle": "node scripts/bundle-deps.js",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/src/hamlib.cpp
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
#include <vector>
|
|
5
5
|
#include <memory>
|
|
6
6
|
#include <algorithm>
|
|
7
|
+
#include <chrono>
|
|
8
|
+
#include <cstdio>
|
|
9
|
+
#include <exception>
|
|
7
10
|
|
|
8
11
|
// 安全宏 - 检查RIG指针有效性,防止空指针解引用和已销毁对象访问
|
|
9
12
|
#define CHECK_RIG_VALID() \
|
|
@@ -45,10 +48,13 @@ public:
|
|
|
45
48
|
} else {
|
|
46
49
|
shim_rig_set_freq_callback(hamlib_instance_->my_rig, NodeHamLib::freq_change_cb, hamlib_instance_);
|
|
47
50
|
auto ppt_cb = +[](void* handle, int vfo, int ptt, void* arg) -> int {
|
|
48
|
-
|
|
51
|
+
(void)handle;
|
|
52
|
+
(void)vfo;
|
|
53
|
+
(void)ptt;
|
|
54
|
+
(void)arg;
|
|
49
55
|
return 0;
|
|
50
56
|
};
|
|
51
|
-
|
|
57
|
+
shim_rig_set_ptt_callback(hamlib_instance_->my_rig, ppt_cb, NULL);
|
|
52
58
|
shim_rig_set_trn(hamlib_instance_->my_rig, SHIM_RIG_TRN_POLL);
|
|
53
59
|
hamlib_instance_->rig_is_open = true;
|
|
54
60
|
}
|
|
@@ -2170,7 +2176,7 @@ private:
|
|
|
2170
2176
|
|
|
2171
2177
|
// Helper function to parse VFO parameter from JavaScript
|
|
2172
2178
|
int parseVfoParameter(const Napi::CallbackInfo& info, int index, int defaultVfo = SHIM_RIG_VFO_CURR) {
|
|
2173
|
-
if (info.Length() > index && info[index].IsString()) {
|
|
2179
|
+
if (info.Length() > static_cast<size_t>(index) && info[index].IsString()) {
|
|
2174
2180
|
std::string vfoStr = info[index].As<Napi::String>().Utf8Value();
|
|
2175
2181
|
if (vfoStr == "VFO-A") {
|
|
2176
2182
|
return SHIM_RIG_VFO_A;
|
|
@@ -2268,6 +2274,8 @@ NodeHamLib::NodeHamLib(const Napi::CallbackInfo & info): ObjectWrap(info) {
|
|
|
2268
2274
|
|
|
2269
2275
|
// 析构函数 - 确保资源正确清理
|
|
2270
2276
|
NodeHamLib::~NodeHamLib() {
|
|
2277
|
+
StopSpectrumStreamInternal();
|
|
2278
|
+
|
|
2271
2279
|
// 如果rig指针存在,执行清理
|
|
2272
2280
|
if (my_rig) {
|
|
2273
2281
|
// 如果rig是打开状态,先关闭
|
|
@@ -2281,10 +2289,70 @@ NodeHamLib::~NodeHamLib() {
|
|
|
2281
2289
|
}
|
|
2282
2290
|
}
|
|
2283
2291
|
|
|
2292
|
+
int NodeHamLib::spectrum_line_cb(void* handle, const shim_spectrum_line_t* line, void* arg) {
|
|
2293
|
+
(void)handle;
|
|
2294
|
+
NodeHamLib* instance = static_cast<NodeHamLib*>(arg);
|
|
2295
|
+
if (!instance || !line) {
|
|
2296
|
+
return 0;
|
|
2297
|
+
}
|
|
2298
|
+
instance->EmitSpectrumLine(*line);
|
|
2299
|
+
return 0;
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
void NodeHamLib::EmitSpectrumLine(const shim_spectrum_line_t& line) {
|
|
2303
|
+
if (!spectrum_tsfn_) {
|
|
2304
|
+
return;
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2307
|
+
auto* line_copy = new shim_spectrum_line_t(line);
|
|
2308
|
+
napi_status status = spectrum_tsfn_.BlockingCall(
|
|
2309
|
+
line_copy,
|
|
2310
|
+
[](Napi::Env env, Napi::Function callback, shim_spectrum_line_t* data) {
|
|
2311
|
+
Napi::Object lineObject = Napi::Object::New(env);
|
|
2312
|
+
lineObject.Set("scopeId", Napi::Number::New(env, data->id));
|
|
2313
|
+
lineObject.Set("dataLevelMin", Napi::Number::New(env, data->data_level_min));
|
|
2314
|
+
lineObject.Set("dataLevelMax", Napi::Number::New(env, data->data_level_max));
|
|
2315
|
+
lineObject.Set("signalStrengthMin", Napi::Number::New(env, data->signal_strength_min));
|
|
2316
|
+
lineObject.Set("signalStrengthMax", Napi::Number::New(env, data->signal_strength_max));
|
|
2317
|
+
lineObject.Set("mode", Napi::Number::New(env, data->spectrum_mode));
|
|
2318
|
+
lineObject.Set("centerFreq", Napi::Number::New(env, data->center_freq));
|
|
2319
|
+
lineObject.Set("spanHz", Napi::Number::New(env, data->span_freq));
|
|
2320
|
+
lineObject.Set("lowEdgeFreq", Napi::Number::New(env, data->low_edge_freq));
|
|
2321
|
+
lineObject.Set("highEdgeFreq", Napi::Number::New(env, data->high_edge_freq));
|
|
2322
|
+
lineObject.Set("dataLength", Napi::Number::New(env, data->data_length));
|
|
2323
|
+
lineObject.Set("timestamp", Napi::Number::New(env, static_cast<double>(std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
2324
|
+
std::chrono::system_clock::now().time_since_epoch()).count())));
|
|
2325
|
+
lineObject.Set("data", Napi::Buffer<unsigned char>::Copy(env, data->data, static_cast<size_t>(data->data_length)));
|
|
2326
|
+
callback.Call({ lineObject });
|
|
2327
|
+
delete data;
|
|
2328
|
+
});
|
|
2329
|
+
|
|
2330
|
+
if (status != napi_ok) {
|
|
2331
|
+
delete line_copy;
|
|
2332
|
+
return;
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
void NodeHamLib::StopSpectrumStreamInternal() {
|
|
2337
|
+
std::lock_guard<std::mutex> lock(spectrum_mutex_);
|
|
2338
|
+
if (spectrum_stream_running_) {
|
|
2339
|
+
shim_rig_set_spectrum_callback(my_rig, nullptr, nullptr);
|
|
2340
|
+
spectrum_stream_running_ = false;
|
|
2341
|
+
}
|
|
2342
|
+
if (spectrum_tsfn_) {
|
|
2343
|
+
spectrum_tsfn_.Release();
|
|
2344
|
+
spectrum_tsfn_ = Napi::ThreadSafeFunction();
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2284
2348
|
int NodeHamLib::freq_change_cb(void *handle, int vfo, double freq, void* arg) {
|
|
2349
|
+
(void)handle;
|
|
2350
|
+
(void)vfo;
|
|
2351
|
+
(void)freq;
|
|
2285
2352
|
auto instance = static_cast<NodeHamLib*>(arg);
|
|
2286
|
-
|
|
2287
|
-
|
|
2353
|
+
if (!instance) {
|
|
2354
|
+
return 0;
|
|
2355
|
+
}
|
|
2288
2356
|
//Napi::Function emit = instance->m_currentInfo[0].Get("emit").As<Napi::Function>();
|
|
2289
2357
|
// Napi::Function emit = instance->m_currentInfo[0]->This().As<Napi::Object>().Get("emit").As<Napi::Function>();
|
|
2290
2358
|
//emit.Call(instance->m_currentInfo->This(), { Napi::String::New(env, "frequency_change"), Napi::Number::New(env, freq) });
|
|
@@ -2293,7 +2361,6 @@ int NodeHamLib::freq_change_cb(void *handle, int vfo, double freq, void* arg) {
|
|
|
2293
2361
|
//auto fn = global.Get("process").As<Napi::Object>().Get("emit").As<Napi::Function>();
|
|
2294
2362
|
//fn.Call({Napi::Number::New(env, freq)});
|
|
2295
2363
|
return 0;
|
|
2296
|
-
return 0;
|
|
2297
2364
|
}
|
|
2298
2365
|
|
|
2299
2366
|
Napi::Value NodeHamLib::Open(const Napi::CallbackInfo & info) {
|
|
@@ -2565,6 +2632,8 @@ Napi::Value NodeHamLib::Close(const Napi::CallbackInfo & info) {
|
|
|
2565
2632
|
.ThrowAsJavaScriptException();
|
|
2566
2633
|
return env.Null();
|
|
2567
2634
|
}
|
|
2635
|
+
|
|
2636
|
+
StopSpectrumStreamInternal();
|
|
2568
2637
|
|
|
2569
2638
|
CloseAsyncWorker* worker = new CloseAsyncWorker(env, this);
|
|
2570
2639
|
worker->Queue();
|
|
@@ -2574,6 +2643,8 @@ Napi::Value NodeHamLib::Close(const Napi::CallbackInfo & info) {
|
|
|
2574
2643
|
|
|
2575
2644
|
Napi::Value NodeHamLib::Destroy(const Napi::CallbackInfo & info) {
|
|
2576
2645
|
Napi::Env env = info.Env();
|
|
2646
|
+
|
|
2647
|
+
StopSpectrumStreamInternal();
|
|
2577
2648
|
|
|
2578
2649
|
DestroyAsyncWorker* worker = new DestroyAsyncWorker(env, this);
|
|
2579
2650
|
worker->Queue();
|
|
@@ -2912,6 +2983,22 @@ Napi::Value NodeHamLib::SetLevel(const Napi::CallbackInfo & info) {
|
|
|
2912
2983
|
levelType = SHIM_RIG_LEVEL_VOXDELAY;
|
|
2913
2984
|
} else if (levelTypeStr == "ANTIVOX") {
|
|
2914
2985
|
levelType = SHIM_RIG_LEVEL_ANTIVOX;
|
|
2986
|
+
} else if (levelTypeStr == "SPECTRUM_MODE") {
|
|
2987
|
+
levelType = SHIM_RIG_LEVEL_SPECTRUM_MODE;
|
|
2988
|
+
} else if (levelTypeStr == "SPECTRUM_SPAN") {
|
|
2989
|
+
levelType = SHIM_RIG_LEVEL_SPECTRUM_SPAN;
|
|
2990
|
+
} else if (levelTypeStr == "SPECTRUM_EDGE_LOW") {
|
|
2991
|
+
levelType = SHIM_RIG_LEVEL_SPECTRUM_EDGE_LOW;
|
|
2992
|
+
} else if (levelTypeStr == "SPECTRUM_EDGE_HIGH") {
|
|
2993
|
+
levelType = SHIM_RIG_LEVEL_SPECTRUM_EDGE_HIGH;
|
|
2994
|
+
} else if (levelTypeStr == "SPECTRUM_SPEED") {
|
|
2995
|
+
levelType = SHIM_RIG_LEVEL_SPECTRUM_SPEED;
|
|
2996
|
+
} else if (levelTypeStr == "SPECTRUM_REF") {
|
|
2997
|
+
levelType = SHIM_RIG_LEVEL_SPECTRUM_REF;
|
|
2998
|
+
} else if (levelTypeStr == "SPECTRUM_AVG") {
|
|
2999
|
+
levelType = SHIM_RIG_LEVEL_SPECTRUM_AVG;
|
|
3000
|
+
} else if (levelTypeStr == "SPECTRUM_ATT") {
|
|
3001
|
+
levelType = SHIM_RIG_LEVEL_SPECTRUM_ATT;
|
|
2915
3002
|
} else {
|
|
2916
3003
|
Napi::TypeError::New(env, "Invalid level type").ThrowAsJavaScriptException();
|
|
2917
3004
|
return env.Null();
|
|
@@ -2962,6 +3049,8 @@ Napi::Value NodeHamLib::GetLevel(const Napi::CallbackInfo & info) {
|
|
|
2962
3049
|
levelType = SHIM_RIG_LEVEL_RAWSTR;
|
|
2963
3050
|
} else if (levelTypeStr == "RFPOWER_METER") {
|
|
2964
3051
|
levelType = SHIM_RIG_LEVEL_RFPOWER_METER;
|
|
3052
|
+
} else if (levelTypeStr == "RFPOWER_METER_WATTS") {
|
|
3053
|
+
levelType = SHIM_RIG_LEVEL_RFPOWER_METER_WATTS;
|
|
2965
3054
|
} else if (levelTypeStr == "COMP_METER") {
|
|
2966
3055
|
levelType = SHIM_RIG_LEVEL_COMP_METER;
|
|
2967
3056
|
} else if (levelTypeStr == "VD_METER") {
|
|
@@ -2970,6 +3059,50 @@ Napi::Value NodeHamLib::GetLevel(const Napi::CallbackInfo & info) {
|
|
|
2970
3059
|
levelType = SHIM_RIG_LEVEL_ID_METER;
|
|
2971
3060
|
} else if (levelTypeStr == "TEMP_METER") {
|
|
2972
3061
|
levelType = SHIM_RIG_LEVEL_TEMP_METER;
|
|
3062
|
+
} else if (levelTypeStr == "NR") {
|
|
3063
|
+
levelType = SHIM_RIG_LEVEL_NR;
|
|
3064
|
+
} else if (levelTypeStr == "AF") {
|
|
3065
|
+
levelType = SHIM_RIG_LEVEL_AF;
|
|
3066
|
+
} else if (levelTypeStr == "RF") {
|
|
3067
|
+
levelType = SHIM_RIG_LEVEL_RF;
|
|
3068
|
+
} else if (levelTypeStr == "SQL") {
|
|
3069
|
+
levelType = SHIM_RIG_LEVEL_SQL;
|
|
3070
|
+
} else if (levelTypeStr == "RFPOWER") {
|
|
3071
|
+
levelType = SHIM_RIG_LEVEL_RFPOWER;
|
|
3072
|
+
} else if (levelTypeStr == "MICGAIN") {
|
|
3073
|
+
levelType = SHIM_RIG_LEVEL_MICGAIN;
|
|
3074
|
+
} else if (levelTypeStr == "VOXDELAY") {
|
|
3075
|
+
levelType = SHIM_RIG_LEVEL_VOXDELAY;
|
|
3076
|
+
} else if (levelTypeStr == "PBT_IN") {
|
|
3077
|
+
levelType = SHIM_RIG_LEVEL_PBT_IN;
|
|
3078
|
+
} else if (levelTypeStr == "PBT_OUT") {
|
|
3079
|
+
levelType = SHIM_RIG_LEVEL_PBT_OUT;
|
|
3080
|
+
} else if (levelTypeStr == "CWPITCH") {
|
|
3081
|
+
levelType = SHIM_RIG_LEVEL_CWPITCH;
|
|
3082
|
+
} else if (levelTypeStr == "COMP") {
|
|
3083
|
+
levelType = SHIM_RIG_LEVEL_COMP;
|
|
3084
|
+
} else if (levelTypeStr == "AGC") {
|
|
3085
|
+
levelType = SHIM_RIG_LEVEL_AGC;
|
|
3086
|
+
} else if (levelTypeStr == "VOXGAIN") {
|
|
3087
|
+
levelType = SHIM_RIG_LEVEL_VOXGAIN;
|
|
3088
|
+
} else if (levelTypeStr == "ANTIVOX") {
|
|
3089
|
+
levelType = SHIM_RIG_LEVEL_ANTIVOX;
|
|
3090
|
+
} else if (levelTypeStr == "SPECTRUM_MODE") {
|
|
3091
|
+
levelType = SHIM_RIG_LEVEL_SPECTRUM_MODE;
|
|
3092
|
+
} else if (levelTypeStr == "SPECTRUM_SPAN") {
|
|
3093
|
+
levelType = SHIM_RIG_LEVEL_SPECTRUM_SPAN;
|
|
3094
|
+
} else if (levelTypeStr == "SPECTRUM_EDGE_LOW") {
|
|
3095
|
+
levelType = SHIM_RIG_LEVEL_SPECTRUM_EDGE_LOW;
|
|
3096
|
+
} else if (levelTypeStr == "SPECTRUM_EDGE_HIGH") {
|
|
3097
|
+
levelType = SHIM_RIG_LEVEL_SPECTRUM_EDGE_HIGH;
|
|
3098
|
+
} else if (levelTypeStr == "SPECTRUM_SPEED") {
|
|
3099
|
+
levelType = SHIM_RIG_LEVEL_SPECTRUM_SPEED;
|
|
3100
|
+
} else if (levelTypeStr == "SPECTRUM_REF") {
|
|
3101
|
+
levelType = SHIM_RIG_LEVEL_SPECTRUM_REF;
|
|
3102
|
+
} else if (levelTypeStr == "SPECTRUM_AVG") {
|
|
3103
|
+
levelType = SHIM_RIG_LEVEL_SPECTRUM_AVG;
|
|
3104
|
+
} else if (levelTypeStr == "SPECTRUM_ATT") {
|
|
3105
|
+
levelType = SHIM_RIG_LEVEL_SPECTRUM_ATT;
|
|
2973
3106
|
} else {
|
|
2974
3107
|
Napi::TypeError::New(env, "Invalid level type").ThrowAsJavaScriptException();
|
|
2975
3108
|
return env.Null();
|
|
@@ -3026,9 +3159,18 @@ Napi::Value NodeHamLib::GetSupportedLevels(const Napi::CallbackInfo & info) {
|
|
|
3026
3159
|
if (levels & SHIM_RIG_LEVEL_SWR) levelArray[index++] = Napi::String::New(env, "SWR");
|
|
3027
3160
|
if (levels & SHIM_RIG_LEVEL_ALC) levelArray[index++] = Napi::String::New(env, "ALC");
|
|
3028
3161
|
if (levels & SHIM_RIG_LEVEL_RFPOWER_METER) levelArray[index++] = Napi::String::New(env, "RFPOWER_METER");
|
|
3162
|
+
if (levels & SHIM_RIG_LEVEL_RFPOWER_METER_WATTS) levelArray[index++] = Napi::String::New(env, "RFPOWER_METER_WATTS");
|
|
3029
3163
|
if (levels & SHIM_RIG_LEVEL_COMP_METER) levelArray[index++] = Napi::String::New(env, "COMP_METER");
|
|
3030
3164
|
if (levels & SHIM_RIG_LEVEL_VD_METER) levelArray[index++] = Napi::String::New(env, "VD_METER");
|
|
3031
3165
|
if (levels & SHIM_RIG_LEVEL_ID_METER) levelArray[index++] = Napi::String::New(env, "ID_METER");
|
|
3166
|
+
if (levels & SHIM_RIG_LEVEL_SPECTRUM_MODE) levelArray[index++] = Napi::String::New(env, "SPECTRUM_MODE");
|
|
3167
|
+
if (levels & SHIM_RIG_LEVEL_SPECTRUM_SPAN) levelArray[index++] = Napi::String::New(env, "SPECTRUM_SPAN");
|
|
3168
|
+
if (levels & SHIM_RIG_LEVEL_SPECTRUM_EDGE_LOW) levelArray[index++] = Napi::String::New(env, "SPECTRUM_EDGE_LOW");
|
|
3169
|
+
if (levels & SHIM_RIG_LEVEL_SPECTRUM_EDGE_HIGH) levelArray[index++] = Napi::String::New(env, "SPECTRUM_EDGE_HIGH");
|
|
3170
|
+
if (levels & SHIM_RIG_LEVEL_SPECTRUM_SPEED) levelArray[index++] = Napi::String::New(env, "SPECTRUM_SPEED");
|
|
3171
|
+
if (levels & SHIM_RIG_LEVEL_SPECTRUM_REF) levelArray[index++] = Napi::String::New(env, "SPECTRUM_REF");
|
|
3172
|
+
if (levels & SHIM_RIG_LEVEL_SPECTRUM_AVG) levelArray[index++] = Napi::String::New(env, "SPECTRUM_AVG");
|
|
3173
|
+
if (levels & SHIM_RIG_LEVEL_SPECTRUM_ATT) levelArray[index++] = Napi::String::New(env, "SPECTRUM_ATT");
|
|
3032
3174
|
if (levels & SHIM_RIG_LEVEL_TEMP_METER) levelArray[index++] = Napi::String::New(env, "TEMP_METER");
|
|
3033
3175
|
|
|
3034
3176
|
return levelArray;
|
|
@@ -3109,6 +3251,12 @@ Napi::Value NodeHamLib::SetFunction(const Napi::CallbackInfo & info) {
|
|
|
3109
3251
|
funcType = SHIM_RIG_FUNC_RESUME;
|
|
3110
3252
|
} else if (funcTypeStr == "TBURST") {
|
|
3111
3253
|
funcType = SHIM_RIG_FUNC_TBURST;
|
|
3254
|
+
} else if (funcTypeStr == "TRANSCEIVE") {
|
|
3255
|
+
funcType = SHIM_RIG_FUNC_TRANSCEIVE;
|
|
3256
|
+
} else if (funcTypeStr == "SPECTRUM") {
|
|
3257
|
+
funcType = SHIM_RIG_FUNC_SPECTRUM;
|
|
3258
|
+
} else if (funcTypeStr == "SPECTRUM_HOLD") {
|
|
3259
|
+
funcType = SHIM_RIG_FUNC_SPECTRUM_HOLD;
|
|
3112
3260
|
} else {
|
|
3113
3261
|
Napi::TypeError::New(env, "Invalid function type").ThrowAsJavaScriptException();
|
|
3114
3262
|
return env.Null();
|
|
@@ -3193,6 +3341,12 @@ Napi::Value NodeHamLib::GetFunction(const Napi::CallbackInfo & info) {
|
|
|
3193
3341
|
funcType = SHIM_RIG_FUNC_RESUME;
|
|
3194
3342
|
} else if (funcTypeStr == "TBURST") {
|
|
3195
3343
|
funcType = SHIM_RIG_FUNC_TBURST;
|
|
3344
|
+
} else if (funcTypeStr == "TRANSCEIVE") {
|
|
3345
|
+
funcType = SHIM_RIG_FUNC_TRANSCEIVE;
|
|
3346
|
+
} else if (funcTypeStr == "SPECTRUM") {
|
|
3347
|
+
funcType = SHIM_RIG_FUNC_SPECTRUM;
|
|
3348
|
+
} else if (funcTypeStr == "SPECTRUM_HOLD") {
|
|
3349
|
+
funcType = SHIM_RIG_FUNC_SPECTRUM_HOLD;
|
|
3196
3350
|
} else {
|
|
3197
3351
|
Napi::TypeError::New(env, "Invalid function type").ThrowAsJavaScriptException();
|
|
3198
3352
|
return env.Null();
|
|
@@ -3241,6 +3395,9 @@ Napi::Value NodeHamLib::GetSupportedFunctions(const Napi::CallbackInfo & info) {
|
|
|
3241
3395
|
if (functions & SHIM_RIG_FUNC_SCOPE) funcArray[index++] = Napi::String::New(env, "SCOPE");
|
|
3242
3396
|
if (functions & SHIM_RIG_FUNC_RESUME) funcArray[index++] = Napi::String::New(env, "RESUME");
|
|
3243
3397
|
if (functions & SHIM_RIG_FUNC_TBURST) funcArray[index++] = Napi::String::New(env, "TBURST");
|
|
3398
|
+
if (functions & SHIM_RIG_FUNC_TRANSCEIVE) funcArray[index++] = Napi::String::New(env, "TRANSCEIVE");
|
|
3399
|
+
if (functions & SHIM_RIG_FUNC_SPECTRUM) funcArray[index++] = Napi::String::New(env, "SPECTRUM");
|
|
3400
|
+
if (functions & SHIM_RIG_FUNC_SPECTRUM_HOLD) funcArray[index++] = Napi::String::New(env, "SPECTRUM_HOLD");
|
|
3244
3401
|
|
|
3245
3402
|
return funcArray;
|
|
3246
3403
|
}
|
|
@@ -3727,9 +3884,12 @@ Napi::Function NodeHamLib::GetClass(Napi::Env env) {
|
|
|
3727
3884
|
// VFO Info (Hamlib >= 4.7.0)
|
|
3728
3885
|
NodeHamLib::InstanceMethod("getVfoInfo", & NodeHamLib::GetVfoInfo),
|
|
3729
3886
|
|
|
3730
|
-
// Rig Info /
|
|
3887
|
+
// Rig Info / Spectrum / Conf (async)
|
|
3731
3888
|
NodeHamLib::InstanceMethod("getInfo", & NodeHamLib::GetInfo),
|
|
3732
3889
|
NodeHamLib::InstanceMethod("sendRaw", & NodeHamLib::SendRaw),
|
|
3890
|
+
NodeHamLib::InstanceMethod("getSpectrumCapabilities", & NodeHamLib::GetSpectrumCapabilities),
|
|
3891
|
+
NodeHamLib::InstanceMethod("startSpectrumStream", & NodeHamLib::StartSpectrumStream),
|
|
3892
|
+
NodeHamLib::InstanceMethod("stopSpectrumStream", & NodeHamLib::StopSpectrumStream),
|
|
3733
3893
|
NodeHamLib::InstanceMethod("setConf", & NodeHamLib::SetConf),
|
|
3734
3894
|
NodeHamLib::InstanceMethod("getConf", & NodeHamLib::GetConf),
|
|
3735
3895
|
|
|
@@ -5806,6 +5966,55 @@ Napi::Value NodeHamLib::SendRaw(const Napi::CallbackInfo& info) {
|
|
|
5806
5966
|
return asyncWorker->GetPromise();
|
|
5807
5967
|
}
|
|
5808
5968
|
|
|
5969
|
+
Napi::Value NodeHamLib::GetSpectrumCapabilities(const Napi::CallbackInfo& info) {
|
|
5970
|
+
Napi::Env env = info.Env();
|
|
5971
|
+
Napi::Object result = Napi::Object::New(env);
|
|
5972
|
+
|
|
5973
|
+
result.Set("asyncDataSupported", env.Undefined());
|
|
5974
|
+
result.Set("scopes", Napi::Array::New(env));
|
|
5975
|
+
result.Set("modes", Napi::Array::New(env));
|
|
5976
|
+
result.Set("spans", Napi::Array::New(env));
|
|
5977
|
+
result.Set("avgModes", Napi::Array::New(env));
|
|
5978
|
+
|
|
5979
|
+
return result;
|
|
5980
|
+
}
|
|
5981
|
+
|
|
5982
|
+
Napi::Value NodeHamLib::StartSpectrumStream(const Napi::CallbackInfo& info) {
|
|
5983
|
+
Napi::Env env = info.Env();
|
|
5984
|
+
if (!rig_is_open) {
|
|
5985
|
+
Napi::TypeError::New(env, "Rig is not open!").ThrowAsJavaScriptException();
|
|
5986
|
+
return env.Null();
|
|
5987
|
+
}
|
|
5988
|
+
if (info.Length() < 1 || !info[0].IsFunction()) {
|
|
5989
|
+
Napi::TypeError::New(env, "Expected (callback: Function)").ThrowAsJavaScriptException();
|
|
5990
|
+
return env.Null();
|
|
5991
|
+
}
|
|
5992
|
+
|
|
5993
|
+
std::lock_guard<std::mutex> lock(spectrum_mutex_);
|
|
5994
|
+
if (spectrum_stream_running_) {
|
|
5995
|
+
Napi::Error::New(env, "Spectrum stream is already running").ThrowAsJavaScriptException();
|
|
5996
|
+
return env.Null();
|
|
5997
|
+
}
|
|
5998
|
+
|
|
5999
|
+
Napi::Function callback = info[0].As<Napi::Function>();
|
|
6000
|
+
spectrum_tsfn_ = Napi::ThreadSafeFunction::New(env, callback, "HamLibSpectrumStream", 0, 1);
|
|
6001
|
+
int ret = shim_rig_set_spectrum_callback(my_rig, &NodeHamLib::spectrum_line_cb, this);
|
|
6002
|
+
if (ret != SHIM_RIG_OK) {
|
|
6003
|
+
spectrum_tsfn_.Release();
|
|
6004
|
+
spectrum_tsfn_ = Napi::ThreadSafeFunction();
|
|
6005
|
+
Napi::Error::New(env, shim_rigerror(ret)).ThrowAsJavaScriptException();
|
|
6006
|
+
return env.Null();
|
|
6007
|
+
}
|
|
6008
|
+
spectrum_stream_running_ = true;
|
|
6009
|
+
return Napi::Boolean::New(env, true);
|
|
6010
|
+
}
|
|
6011
|
+
|
|
6012
|
+
Napi::Value NodeHamLib::StopSpectrumStream(const Napi::CallbackInfo& info) {
|
|
6013
|
+
Napi::Env env = info.Env();
|
|
6014
|
+
StopSpectrumStreamInternal();
|
|
6015
|
+
return Napi::Boolean::New(env, true);
|
|
6016
|
+
}
|
|
6017
|
+
|
|
5809
6018
|
// ===== SetConf (async) =====
|
|
5810
6019
|
|
|
5811
6020
|
class SetConfAsyncWorker : public HamLibAsyncWorker {
|
package/src/hamlib.h
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
#include "shim/hamlib_shim.h"
|
|
5
5
|
#include <memory>
|
|
6
6
|
#include <string>
|
|
7
|
+
#include <atomic>
|
|
8
|
+
#include <mutex>
|
|
9
|
+
#include <vector>
|
|
10
|
+
#include <cstdint>
|
|
7
11
|
|
|
8
12
|
// Forward declaration
|
|
9
13
|
class NodeHamLib;
|
|
@@ -173,6 +177,9 @@ class NodeHamLib : public Napi::ObjectWrap<NodeHamLib> {
|
|
|
173
177
|
// Rig Info / Raw / Conf (async)
|
|
174
178
|
Napi::Value GetInfo(const Napi::CallbackInfo&);
|
|
175
179
|
Napi::Value SendRaw(const Napi::CallbackInfo&);
|
|
180
|
+
Napi::Value GetSpectrumCapabilities(const Napi::CallbackInfo&);
|
|
181
|
+
Napi::Value StartSpectrumStream(const Napi::CallbackInfo&);
|
|
182
|
+
Napi::Value StopSpectrumStream(const Napi::CallbackInfo&);
|
|
176
183
|
Napi::Value SetConf(const Napi::CallbackInfo&);
|
|
177
184
|
Napi::Value GetConf(const Napi::CallbackInfo&);
|
|
178
185
|
|
|
@@ -217,6 +224,7 @@ class NodeHamLib : public Napi::ObjectWrap<NodeHamLib> {
|
|
|
217
224
|
|
|
218
225
|
// Frequency change callback (uses basic C types via shim)
|
|
219
226
|
static int freq_change_cb(void* handle, int vfo, double freq, void* arg);
|
|
227
|
+
static int spectrum_line_cb(void* handle, const shim_spectrum_line_t* line, void* arg);
|
|
220
228
|
|
|
221
229
|
public:
|
|
222
230
|
hamlib_shim_handle_t my_rig; // Opaque handle instead of RIG*
|
|
@@ -234,4 +242,10 @@ class NodeHamLib : public Napi::ObjectWrap<NodeHamLib> {
|
|
|
234
242
|
|
|
235
243
|
// Static callback helper for shim_rig_list_foreach
|
|
236
244
|
static int rig_list_callback(const shim_rig_info_t* info, void* data);
|
|
245
|
+
|
|
246
|
+
void EmitSpectrumLine(const shim_spectrum_line_t& line);
|
|
247
|
+
void StopSpectrumStreamInternal();
|
|
248
|
+
std::atomic<bool> spectrum_stream_running_{false};
|
|
249
|
+
Napi::ThreadSafeFunction spectrum_tsfn_;
|
|
250
|
+
std::mutex spectrum_mutex_;
|
|
237
251
|
};
|