hamlib 0.2.7 → 0.3.1

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/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
- printf("PPT pushed!");
51
+ (void)handle;
52
+ (void)vfo;
53
+ (void)ptt;
54
+ (void)arg;
49
55
  return 0;
50
56
  };
51
- int cb_result = shim_rig_set_ptt_callback(hamlib_instance_->my_rig, ppt_cb, NULL);
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
  }
@@ -280,7 +286,11 @@ public:
280
286
  void Execute() override {
281
287
  CHECK_RIG_VALID();
282
288
 
283
- result_code_ = shim_rig_set_level_f(hamlib_instance_->my_rig, SHIM_RIG_VFO_CURR, level_type_, value_);
289
+ if (shim_rig_level_is_float(level_type_)) {
290
+ result_code_ = shim_rig_set_level_f(hamlib_instance_->my_rig, SHIM_RIG_VFO_CURR, level_type_, value_);
291
+ } else {
292
+ result_code_ = shim_rig_set_level_i(hamlib_instance_->my_rig, SHIM_RIG_VFO_CURR, level_type_, static_cast<int>(value_));
293
+ }
284
294
  if (result_code_ != SHIM_RIG_OK) {
285
295
  error_message_ = shim_rigerror(result_code_);
286
296
  }
@@ -2170,7 +2180,7 @@ private:
2170
2180
 
2171
2181
  // Helper function to parse VFO parameter from JavaScript
2172
2182
  int parseVfoParameter(const Napi::CallbackInfo& info, int index, int defaultVfo = SHIM_RIG_VFO_CURR) {
2173
- if (info.Length() > index && info[index].IsString()) {
2183
+ if (info.Length() > static_cast<size_t>(index) && info[index].IsString()) {
2174
2184
  std::string vfoStr = info[index].As<Napi::String>().Utf8Value();
2175
2185
  if (vfoStr == "VFO-A") {
2176
2186
  return SHIM_RIG_VFO_A;
@@ -2268,6 +2278,8 @@ NodeHamLib::NodeHamLib(const Napi::CallbackInfo & info): ObjectWrap(info) {
2268
2278
 
2269
2279
  // 析构函数 - 确保资源正确清理
2270
2280
  NodeHamLib::~NodeHamLib() {
2281
+ StopSpectrumStreamInternal();
2282
+
2271
2283
  // 如果rig指针存在,执行清理
2272
2284
  if (my_rig) {
2273
2285
  // 如果rig是打开状态,先关闭
@@ -2281,10 +2293,70 @@ NodeHamLib::~NodeHamLib() {
2281
2293
  }
2282
2294
  }
2283
2295
 
2296
+ int NodeHamLib::spectrum_line_cb(void* handle, const shim_spectrum_line_t* line, void* arg) {
2297
+ (void)handle;
2298
+ NodeHamLib* instance = static_cast<NodeHamLib*>(arg);
2299
+ if (!instance || !line) {
2300
+ return 0;
2301
+ }
2302
+ instance->EmitSpectrumLine(*line);
2303
+ return 0;
2304
+ }
2305
+
2306
+ void NodeHamLib::EmitSpectrumLine(const shim_spectrum_line_t& line) {
2307
+ if (!spectrum_tsfn_) {
2308
+ return;
2309
+ }
2310
+
2311
+ auto* line_copy = new shim_spectrum_line_t(line);
2312
+ napi_status status = spectrum_tsfn_.BlockingCall(
2313
+ line_copy,
2314
+ [](Napi::Env env, Napi::Function callback, shim_spectrum_line_t* data) {
2315
+ Napi::Object lineObject = Napi::Object::New(env);
2316
+ lineObject.Set("scopeId", Napi::Number::New(env, data->id));
2317
+ lineObject.Set("dataLevelMin", Napi::Number::New(env, data->data_level_min));
2318
+ lineObject.Set("dataLevelMax", Napi::Number::New(env, data->data_level_max));
2319
+ lineObject.Set("signalStrengthMin", Napi::Number::New(env, data->signal_strength_min));
2320
+ lineObject.Set("signalStrengthMax", Napi::Number::New(env, data->signal_strength_max));
2321
+ lineObject.Set("mode", Napi::Number::New(env, data->spectrum_mode));
2322
+ lineObject.Set("centerFreq", Napi::Number::New(env, data->center_freq));
2323
+ lineObject.Set("spanHz", Napi::Number::New(env, data->span_freq));
2324
+ lineObject.Set("lowEdgeFreq", Napi::Number::New(env, data->low_edge_freq));
2325
+ lineObject.Set("highEdgeFreq", Napi::Number::New(env, data->high_edge_freq));
2326
+ lineObject.Set("dataLength", Napi::Number::New(env, data->data_length));
2327
+ lineObject.Set("timestamp", Napi::Number::New(env, static_cast<double>(std::chrono::duration_cast<std::chrono::milliseconds>(
2328
+ std::chrono::system_clock::now().time_since_epoch()).count())));
2329
+ lineObject.Set("data", Napi::Buffer<unsigned char>::Copy(env, data->data, static_cast<size_t>(data->data_length)));
2330
+ callback.Call({ lineObject });
2331
+ delete data;
2332
+ });
2333
+
2334
+ if (status != napi_ok) {
2335
+ delete line_copy;
2336
+ return;
2337
+ }
2338
+ }
2339
+
2340
+ void NodeHamLib::StopSpectrumStreamInternal() {
2341
+ std::lock_guard<std::mutex> lock(spectrum_mutex_);
2342
+ if (spectrum_stream_running_) {
2343
+ shim_rig_set_spectrum_callback(my_rig, nullptr, nullptr);
2344
+ spectrum_stream_running_ = false;
2345
+ }
2346
+ if (spectrum_tsfn_) {
2347
+ spectrum_tsfn_.Release();
2348
+ spectrum_tsfn_ = Napi::ThreadSafeFunction();
2349
+ }
2350
+ }
2351
+
2284
2352
  int NodeHamLib::freq_change_cb(void *handle, int vfo, double freq, void* arg) {
2353
+ (void)handle;
2354
+ (void)vfo;
2355
+ (void)freq;
2285
2356
  auto instance = static_cast<NodeHamLib*>(arg);
2286
- printf("Rig changed freq to %0.7f Hz\n", freq);
2287
- Napi::Env env = instance->m_currentInfo->Env();
2357
+ if (!instance) {
2358
+ return 0;
2359
+ }
2288
2360
  //Napi::Function emit = instance->m_currentInfo[0].Get("emit").As<Napi::Function>();
2289
2361
  // Napi::Function emit = instance->m_currentInfo[0]->This().As<Napi::Object>().Get("emit").As<Napi::Function>();
2290
2362
  //emit.Call(instance->m_currentInfo->This(), { Napi::String::New(env, "frequency_change"), Napi::Number::New(env, freq) });
@@ -2293,7 +2365,6 @@ int NodeHamLib::freq_change_cb(void *handle, int vfo, double freq, void* arg) {
2293
2365
  //auto fn = global.Get("process").As<Napi::Object>().Get("emit").As<Napi::Function>();
2294
2366
  //fn.Call({Napi::Number::New(env, freq)});
2295
2367
  return 0;
2296
- return 0;
2297
2368
  }
2298
2369
 
2299
2370
  Napi::Value NodeHamLib::Open(const Napi::CallbackInfo & info) {
@@ -2565,6 +2636,8 @@ Napi::Value NodeHamLib::Close(const Napi::CallbackInfo & info) {
2565
2636
  .ThrowAsJavaScriptException();
2566
2637
  return env.Null();
2567
2638
  }
2639
+
2640
+ StopSpectrumStreamInternal();
2568
2641
 
2569
2642
  CloseAsyncWorker* worker = new CloseAsyncWorker(env, this);
2570
2643
  worker->Queue();
@@ -2574,6 +2647,8 @@ Napi::Value NodeHamLib::Close(const Napi::CallbackInfo & info) {
2574
2647
 
2575
2648
  Napi::Value NodeHamLib::Destroy(const Napi::CallbackInfo & info) {
2576
2649
  Napi::Env env = info.Env();
2650
+
2651
+ StopSpectrumStreamInternal();
2577
2652
 
2578
2653
  DestroyAsyncWorker* worker = new DestroyAsyncWorker(env, this);
2579
2654
  worker->Queue();
@@ -2912,16 +2987,54 @@ Napi::Value NodeHamLib::SetLevel(const Napi::CallbackInfo & info) {
2912
2987
  levelType = SHIM_RIG_LEVEL_VOXDELAY;
2913
2988
  } else if (levelTypeStr == "ANTIVOX") {
2914
2989
  levelType = SHIM_RIG_LEVEL_ANTIVOX;
2990
+ } else if (levelTypeStr == "SPECTRUM_MODE") {
2991
+ levelType = SHIM_RIG_LEVEL_SPECTRUM_MODE;
2992
+ } else if (levelTypeStr == "SPECTRUM_SPAN") {
2993
+ levelType = SHIM_RIG_LEVEL_SPECTRUM_SPAN;
2994
+ } else if (levelTypeStr == "SPECTRUM_EDGE_LOW") {
2995
+ levelType = SHIM_RIG_LEVEL_SPECTRUM_EDGE_LOW;
2996
+ } else if (levelTypeStr == "SPECTRUM_EDGE_HIGH") {
2997
+ levelType = SHIM_RIG_LEVEL_SPECTRUM_EDGE_HIGH;
2998
+ } else if (levelTypeStr == "SPECTRUM_SPEED") {
2999
+ levelType = SHIM_RIG_LEVEL_SPECTRUM_SPEED;
3000
+ } else if (levelTypeStr == "SPECTRUM_REF") {
3001
+ levelType = SHIM_RIG_LEVEL_SPECTRUM_REF;
3002
+ } else if (levelTypeStr == "SPECTRUM_AVG") {
3003
+ levelType = SHIM_RIG_LEVEL_SPECTRUM_AVG;
3004
+ } else if (levelTypeStr == "SPECTRUM_ATT") {
3005
+ levelType = SHIM_RIG_LEVEL_SPECTRUM_ATT;
2915
3006
  } else {
2916
3007
  Napi::TypeError::New(env, "Invalid level type").ThrowAsJavaScriptException();
2917
3008
  return env.Null();
2918
3009
  }
2919
3010
 
2920
- float val = static_cast<float>(levelValue);
3011
+ const bool isSpectrumLevel =
3012
+ levelType == SHIM_RIG_LEVEL_SPECTRUM_MODE
3013
+ || levelType == SHIM_RIG_LEVEL_SPECTRUM_SPAN
3014
+ || levelType == SHIM_RIG_LEVEL_SPECTRUM_EDGE_LOW
3015
+ || levelType == SHIM_RIG_LEVEL_SPECTRUM_EDGE_HIGH
3016
+ || levelType == SHIM_RIG_LEVEL_SPECTRUM_SPEED
3017
+ || levelType == SHIM_RIG_LEVEL_SPECTRUM_REF
3018
+ || levelType == SHIM_RIG_LEVEL_SPECTRUM_AVG
3019
+ || levelType == SHIM_RIG_LEVEL_SPECTRUM_ATT;
3020
+
3021
+ if (isSpectrumLevel) {
3022
+ Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(env);
3023
+ int result = shim_rig_level_is_float(levelType)
3024
+ ? shim_rig_set_level_f(my_rig, SHIM_RIG_VFO_CURR, levelType, static_cast<float>(levelValue))
3025
+ : shim_rig_set_level_i(my_rig, SHIM_RIG_VFO_CURR, levelType, static_cast<int>(levelValue));
3026
+
3027
+ if (result != SHIM_RIG_OK) {
3028
+ deferred.Reject(Napi::Error::New(env, shim_rigerror(result)).Value());
3029
+ } else {
3030
+ deferred.Resolve(Napi::Number::New(env, result));
3031
+ }
3032
+ return deferred.Promise();
3033
+ }
2921
3034
 
3035
+ float val = static_cast<float>(levelValue);
2922
3036
  SetLevelAsyncWorker* worker = new SetLevelAsyncWorker(env, this, levelType, val);
2923
3037
  worker->Queue();
2924
-
2925
3038
  return worker->GetPromise();
2926
3039
  }
2927
3040
 
@@ -2972,6 +3085,50 @@ Napi::Value NodeHamLib::GetLevel(const Napi::CallbackInfo & info) {
2972
3085
  levelType = SHIM_RIG_LEVEL_ID_METER;
2973
3086
  } else if (levelTypeStr == "TEMP_METER") {
2974
3087
  levelType = SHIM_RIG_LEVEL_TEMP_METER;
3088
+ } else if (levelTypeStr == "NR") {
3089
+ levelType = SHIM_RIG_LEVEL_NR;
3090
+ } else if (levelTypeStr == "AF") {
3091
+ levelType = SHIM_RIG_LEVEL_AF;
3092
+ } else if (levelTypeStr == "RF") {
3093
+ levelType = SHIM_RIG_LEVEL_RF;
3094
+ } else if (levelTypeStr == "SQL") {
3095
+ levelType = SHIM_RIG_LEVEL_SQL;
3096
+ } else if (levelTypeStr == "RFPOWER") {
3097
+ levelType = SHIM_RIG_LEVEL_RFPOWER;
3098
+ } else if (levelTypeStr == "MICGAIN") {
3099
+ levelType = SHIM_RIG_LEVEL_MICGAIN;
3100
+ } else if (levelTypeStr == "VOXDELAY") {
3101
+ levelType = SHIM_RIG_LEVEL_VOXDELAY;
3102
+ } else if (levelTypeStr == "PBT_IN") {
3103
+ levelType = SHIM_RIG_LEVEL_PBT_IN;
3104
+ } else if (levelTypeStr == "PBT_OUT") {
3105
+ levelType = SHIM_RIG_LEVEL_PBT_OUT;
3106
+ } else if (levelTypeStr == "CWPITCH") {
3107
+ levelType = SHIM_RIG_LEVEL_CWPITCH;
3108
+ } else if (levelTypeStr == "COMP") {
3109
+ levelType = SHIM_RIG_LEVEL_COMP;
3110
+ } else if (levelTypeStr == "AGC") {
3111
+ levelType = SHIM_RIG_LEVEL_AGC;
3112
+ } else if (levelTypeStr == "VOXGAIN") {
3113
+ levelType = SHIM_RIG_LEVEL_VOXGAIN;
3114
+ } else if (levelTypeStr == "ANTIVOX") {
3115
+ levelType = SHIM_RIG_LEVEL_ANTIVOX;
3116
+ } else if (levelTypeStr == "SPECTRUM_MODE") {
3117
+ levelType = SHIM_RIG_LEVEL_SPECTRUM_MODE;
3118
+ } else if (levelTypeStr == "SPECTRUM_SPAN") {
3119
+ levelType = SHIM_RIG_LEVEL_SPECTRUM_SPAN;
3120
+ } else if (levelTypeStr == "SPECTRUM_EDGE_LOW") {
3121
+ levelType = SHIM_RIG_LEVEL_SPECTRUM_EDGE_LOW;
3122
+ } else if (levelTypeStr == "SPECTRUM_EDGE_HIGH") {
3123
+ levelType = SHIM_RIG_LEVEL_SPECTRUM_EDGE_HIGH;
3124
+ } else if (levelTypeStr == "SPECTRUM_SPEED") {
3125
+ levelType = SHIM_RIG_LEVEL_SPECTRUM_SPEED;
3126
+ } else if (levelTypeStr == "SPECTRUM_REF") {
3127
+ levelType = SHIM_RIG_LEVEL_SPECTRUM_REF;
3128
+ } else if (levelTypeStr == "SPECTRUM_AVG") {
3129
+ levelType = SHIM_RIG_LEVEL_SPECTRUM_AVG;
3130
+ } else if (levelTypeStr == "SPECTRUM_ATT") {
3131
+ levelType = SHIM_RIG_LEVEL_SPECTRUM_ATT;
2975
3132
  } else {
2976
3133
  Napi::TypeError::New(env, "Invalid level type").ThrowAsJavaScriptException();
2977
3134
  return env.Null();
@@ -3032,6 +3189,14 @@ Napi::Value NodeHamLib::GetSupportedLevels(const Napi::CallbackInfo & info) {
3032
3189
  if (levels & SHIM_RIG_LEVEL_COMP_METER) levelArray[index++] = Napi::String::New(env, "COMP_METER");
3033
3190
  if (levels & SHIM_RIG_LEVEL_VD_METER) levelArray[index++] = Napi::String::New(env, "VD_METER");
3034
3191
  if (levels & SHIM_RIG_LEVEL_ID_METER) levelArray[index++] = Napi::String::New(env, "ID_METER");
3192
+ if (levels & SHIM_RIG_LEVEL_SPECTRUM_MODE) levelArray[index++] = Napi::String::New(env, "SPECTRUM_MODE");
3193
+ if (levels & SHIM_RIG_LEVEL_SPECTRUM_SPAN) levelArray[index++] = Napi::String::New(env, "SPECTRUM_SPAN");
3194
+ if (levels & SHIM_RIG_LEVEL_SPECTRUM_EDGE_LOW) levelArray[index++] = Napi::String::New(env, "SPECTRUM_EDGE_LOW");
3195
+ if (levels & SHIM_RIG_LEVEL_SPECTRUM_EDGE_HIGH) levelArray[index++] = Napi::String::New(env, "SPECTRUM_EDGE_HIGH");
3196
+ if (levels & SHIM_RIG_LEVEL_SPECTRUM_SPEED) levelArray[index++] = Napi::String::New(env, "SPECTRUM_SPEED");
3197
+ if (levels & SHIM_RIG_LEVEL_SPECTRUM_REF) levelArray[index++] = Napi::String::New(env, "SPECTRUM_REF");
3198
+ if (levels & SHIM_RIG_LEVEL_SPECTRUM_AVG) levelArray[index++] = Napi::String::New(env, "SPECTRUM_AVG");
3199
+ if (levels & SHIM_RIG_LEVEL_SPECTRUM_ATT) levelArray[index++] = Napi::String::New(env, "SPECTRUM_ATT");
3035
3200
  if (levels & SHIM_RIG_LEVEL_TEMP_METER) levelArray[index++] = Napi::String::New(env, "TEMP_METER");
3036
3201
 
3037
3202
  return levelArray;
@@ -3112,6 +3277,12 @@ Napi::Value NodeHamLib::SetFunction(const Napi::CallbackInfo & info) {
3112
3277
  funcType = SHIM_RIG_FUNC_RESUME;
3113
3278
  } else if (funcTypeStr == "TBURST") {
3114
3279
  funcType = SHIM_RIG_FUNC_TBURST;
3280
+ } else if (funcTypeStr == "TRANSCEIVE") {
3281
+ funcType = SHIM_RIG_FUNC_TRANSCEIVE;
3282
+ } else if (funcTypeStr == "SPECTRUM") {
3283
+ funcType = SHIM_RIG_FUNC_SPECTRUM;
3284
+ } else if (funcTypeStr == "SPECTRUM_HOLD") {
3285
+ funcType = SHIM_RIG_FUNC_SPECTRUM_HOLD;
3115
3286
  } else {
3116
3287
  Napi::TypeError::New(env, "Invalid function type").ThrowAsJavaScriptException();
3117
3288
  return env.Null();
@@ -3196,6 +3367,12 @@ Napi::Value NodeHamLib::GetFunction(const Napi::CallbackInfo & info) {
3196
3367
  funcType = SHIM_RIG_FUNC_RESUME;
3197
3368
  } else if (funcTypeStr == "TBURST") {
3198
3369
  funcType = SHIM_RIG_FUNC_TBURST;
3370
+ } else if (funcTypeStr == "TRANSCEIVE") {
3371
+ funcType = SHIM_RIG_FUNC_TRANSCEIVE;
3372
+ } else if (funcTypeStr == "SPECTRUM") {
3373
+ funcType = SHIM_RIG_FUNC_SPECTRUM;
3374
+ } else if (funcTypeStr == "SPECTRUM_HOLD") {
3375
+ funcType = SHIM_RIG_FUNC_SPECTRUM_HOLD;
3199
3376
  } else {
3200
3377
  Napi::TypeError::New(env, "Invalid function type").ThrowAsJavaScriptException();
3201
3378
  return env.Null();
@@ -3244,6 +3421,9 @@ Napi::Value NodeHamLib::GetSupportedFunctions(const Napi::CallbackInfo & info) {
3244
3421
  if (functions & SHIM_RIG_FUNC_SCOPE) funcArray[index++] = Napi::String::New(env, "SCOPE");
3245
3422
  if (functions & SHIM_RIG_FUNC_RESUME) funcArray[index++] = Napi::String::New(env, "RESUME");
3246
3423
  if (functions & SHIM_RIG_FUNC_TBURST) funcArray[index++] = Napi::String::New(env, "TBURST");
3424
+ if (functions & SHIM_RIG_FUNC_TRANSCEIVE) funcArray[index++] = Napi::String::New(env, "TRANSCEIVE");
3425
+ if (functions & SHIM_RIG_FUNC_SPECTRUM) funcArray[index++] = Napi::String::New(env, "SPECTRUM");
3426
+ if (functions & SHIM_RIG_FUNC_SPECTRUM_HOLD) funcArray[index++] = Napi::String::New(env, "SPECTRUM_HOLD");
3247
3427
 
3248
3428
  return funcArray;
3249
3429
  }
@@ -3730,9 +3910,12 @@ Napi::Function NodeHamLib::GetClass(Napi::Env env) {
3730
3910
  // VFO Info (Hamlib >= 4.7.0)
3731
3911
  NodeHamLib::InstanceMethod("getVfoInfo", & NodeHamLib::GetVfoInfo),
3732
3912
 
3733
- // Rig Info / Raw / Conf (async)
3913
+ // Rig Info / Spectrum / Conf (async)
3734
3914
  NodeHamLib::InstanceMethod("getInfo", & NodeHamLib::GetInfo),
3735
3915
  NodeHamLib::InstanceMethod("sendRaw", & NodeHamLib::SendRaw),
3916
+ NodeHamLib::InstanceMethod("getSpectrumCapabilities", & NodeHamLib::GetSpectrumCapabilities),
3917
+ NodeHamLib::InstanceMethod("startSpectrumStream", & NodeHamLib::StartSpectrumStream),
3918
+ NodeHamLib::InstanceMethod("stopSpectrumStream", & NodeHamLib::StopSpectrumStream),
3736
3919
  NodeHamLib::InstanceMethod("setConf", & NodeHamLib::SetConf),
3737
3920
  NodeHamLib::InstanceMethod("getConf", & NodeHamLib::GetConf),
3738
3921
 
@@ -5809,6 +5992,104 @@ Napi::Value NodeHamLib::SendRaw(const Napi::CallbackInfo& info) {
5809
5992
  return asyncWorker->GetPromise();
5810
5993
  }
5811
5994
 
5995
+ Napi::Value NodeHamLib::GetSpectrumCapabilities(const Napi::CallbackInfo& info) {
5996
+ Napi::Env env = info.Env();
5997
+ Napi::Object result = Napi::Object::New(env);
5998
+
5999
+ result.Set("asyncDataSupported", Napi::Boolean::New(env, shim_rig_is_async_data_supported(my_rig) != 0));
6000
+
6001
+ Napi::Array scopes = Napi::Array::New(env);
6002
+ int scopeCount = shim_rig_get_caps_spectrum_scope_count(my_rig);
6003
+ for (int i = 0; i < scopeCount; ++i) {
6004
+ shim_spectrum_scope_t scope{};
6005
+ if (shim_rig_get_caps_spectrum_scope(my_rig, i, &scope) != SHIM_RIG_OK) {
6006
+ continue;
6007
+ }
6008
+ Napi::Object scopeObject = Napi::Object::New(env);
6009
+ scopeObject.Set("id", Napi::Number::New(env, scope.id));
6010
+ scopeObject.Set("name", Napi::String::New(env, scope.name));
6011
+ scopes.Set(i, scopeObject);
6012
+ }
6013
+ result.Set("scopes", scopes);
6014
+
6015
+ Napi::Array modes = Napi::Array::New(env);
6016
+ int modeCount = shim_rig_get_caps_spectrum_mode_count(my_rig);
6017
+ for (int i = 0; i < modeCount; ++i) {
6018
+ int modeId = 0;
6019
+ if (shim_rig_get_caps_spectrum_mode(my_rig, i, &modeId) != SHIM_RIG_OK) {
6020
+ continue;
6021
+ }
6022
+ Napi::Object modeObject = Napi::Object::New(env);
6023
+ modeObject.Set("id", Napi::Number::New(env, modeId));
6024
+ modeObject.Set("name", Napi::String::New(env, shim_rig_str_spectrum_mode(modeId)));
6025
+ modes.Set(i, modeObject);
6026
+ }
6027
+ result.Set("modes", modes);
6028
+
6029
+ Napi::Array spans = Napi::Array::New(env);
6030
+ int spanCount = shim_rig_get_caps_spectrum_span_count(my_rig);
6031
+ for (int i = 0; i < spanCount; ++i) {
6032
+ double spanHz = 0;
6033
+ if (shim_rig_get_caps_spectrum_span(my_rig, i, &spanHz) != SHIM_RIG_OK) {
6034
+ continue;
6035
+ }
6036
+ spans.Set(i, Napi::Number::New(env, spanHz));
6037
+ }
6038
+ result.Set("spans", spans);
6039
+
6040
+ Napi::Array avgModes = Napi::Array::New(env);
6041
+ int avgModeCount = shim_rig_get_caps_spectrum_avg_mode_count(my_rig);
6042
+ for (int i = 0; i < avgModeCount; ++i) {
6043
+ shim_spectrum_avg_mode_t avgMode{};
6044
+ if (shim_rig_get_caps_spectrum_avg_mode(my_rig, i, &avgMode) != SHIM_RIG_OK) {
6045
+ continue;
6046
+ }
6047
+ Napi::Object avgModeObject = Napi::Object::New(env);
6048
+ avgModeObject.Set("id", Napi::Number::New(env, avgMode.id));
6049
+ avgModeObject.Set("name", Napi::String::New(env, avgMode.name));
6050
+ avgModes.Set(i, avgModeObject);
6051
+ }
6052
+ result.Set("avgModes", avgModes);
6053
+
6054
+ return result;
6055
+ }
6056
+
6057
+ Napi::Value NodeHamLib::StartSpectrumStream(const Napi::CallbackInfo& info) {
6058
+ Napi::Env env = info.Env();
6059
+ if (!rig_is_open) {
6060
+ Napi::TypeError::New(env, "Rig is not open!").ThrowAsJavaScriptException();
6061
+ return env.Null();
6062
+ }
6063
+ if (info.Length() < 1 || !info[0].IsFunction()) {
6064
+ Napi::TypeError::New(env, "Expected (callback: Function)").ThrowAsJavaScriptException();
6065
+ return env.Null();
6066
+ }
6067
+
6068
+ std::lock_guard<std::mutex> lock(spectrum_mutex_);
6069
+ if (spectrum_stream_running_) {
6070
+ Napi::Error::New(env, "Spectrum stream is already running").ThrowAsJavaScriptException();
6071
+ return env.Null();
6072
+ }
6073
+
6074
+ Napi::Function callback = info[0].As<Napi::Function>();
6075
+ spectrum_tsfn_ = Napi::ThreadSafeFunction::New(env, callback, "HamLibSpectrumStream", 0, 1);
6076
+ int ret = shim_rig_set_spectrum_callback(my_rig, &NodeHamLib::spectrum_line_cb, this);
6077
+ if (ret != SHIM_RIG_OK) {
6078
+ spectrum_tsfn_.Release();
6079
+ spectrum_tsfn_ = Napi::ThreadSafeFunction();
6080
+ Napi::Error::New(env, shim_rigerror(ret)).ThrowAsJavaScriptException();
6081
+ return env.Null();
6082
+ }
6083
+ spectrum_stream_running_ = true;
6084
+ return Napi::Boolean::New(env, true);
6085
+ }
6086
+
6087
+ Napi::Value NodeHamLib::StopSpectrumStream(const Napi::CallbackInfo& info) {
6088
+ Napi::Env env = info.Env();
6089
+ StopSpectrumStreamInternal();
6090
+ return Napi::Boolean::New(env, true);
6091
+ }
6092
+
5812
6093
  // ===== SetConf (async) =====
5813
6094
 
5814
6095
  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
  };
@@ -333,14 +333,20 @@ SHIM_API int shim_rig_get_strength(hamlib_shim_handle_t h, int vfo, int* strengt
333
333
 
334
334
  /* ===== Level control ===== */
335
335
 
336
+ static void shim_ensure_targetable_level(hamlib_shim_handle_t h);
337
+
336
338
  SHIM_API int shim_rig_set_level_f(hamlib_shim_handle_t h, int vfo, uint64_t level, float value) {
339
+ shim_ensure_targetable_level(h);
337
340
  value_t val;
341
+ memset(&val, 0, sizeof(val));
338
342
  val.f = value;
339
343
  return rig_set_level((RIG*)h, (vfo_t)vfo, (setting_t)level, val);
340
344
  }
341
345
 
342
346
  SHIM_API int shim_rig_set_level_i(hamlib_shim_handle_t h, int vfo, uint64_t level, int value) {
347
+ shim_ensure_targetable_level(h);
343
348
  value_t val;
349
+ memset(&val, 0, sizeof(val));
344
350
  val.i = value;
345
351
  return rig_set_level((RIG*)h, (vfo_t)vfo, (setting_t)level, val);
346
352
  }
@@ -383,6 +389,10 @@ SHIM_API int shim_rig_get_level_i(hamlib_shim_handle_t h, int vfo, uint64_t leve
383
389
  return ret;
384
390
  }
385
391
 
392
+ SHIM_API int shim_rig_level_is_float(uint64_t level) {
393
+ return RIG_LEVEL_IS_FLOAT((setting_t)level) ? 1 : 0;
394
+ }
395
+
386
396
  /*
387
397
  * Auto-detect int/float level type using RIG_LEVEL_IS_FLOAT macro.
388
398
  * Returns the value as double regardless of the underlying type.
@@ -837,9 +847,15 @@ struct shim_ptt_cb_adapter {
837
847
  void* user_arg;
838
848
  };
839
849
 
850
+ struct shim_spectrum_cb_adapter {
851
+ shim_spectrum_cb_t user_cb;
852
+ void* user_arg;
853
+ };
854
+
840
855
  /* We store adapters statically (one per rig handle - simplified) */
841
856
  static struct shim_freq_cb_adapter freq_cb_adapter = {NULL, NULL};
842
857
  static struct shim_ptt_cb_adapter ptt_cb_adapter = {NULL, NULL};
858
+ static struct shim_spectrum_cb_adapter spectrum_cb_adapter = {NULL, NULL};
843
859
 
844
860
  static int shim_freq_cb_thunk(RIG *rig, vfo_t vfo, freq_t freq, rig_ptr_t arg) {
845
861
  struct shim_freq_cb_adapter *adapter = (struct shim_freq_cb_adapter*)arg;
@@ -857,6 +873,33 @@ static int shim_ptt_cb_thunk(RIG *rig, vfo_t vfo, ptt_t ptt, rig_ptr_t arg) {
857
873
  return 0;
858
874
  }
859
875
 
876
+ static int shim_spectrum_cb_thunk(RIG *rig, struct rig_spectrum_line *line, rig_ptr_t arg) {
877
+ struct shim_spectrum_cb_adapter *adapter = (struct shim_spectrum_cb_adapter*)arg;
878
+ shim_spectrum_line_t safe_line;
879
+
880
+ if (!adapter || !adapter->user_cb || !line) {
881
+ return 0;
882
+ }
883
+
884
+ memset(&safe_line, 0, sizeof(safe_line));
885
+ safe_line.id = line->id;
886
+ safe_line.data_level_min = line->data_level_min;
887
+ safe_line.data_level_max = line->data_level_max;
888
+ safe_line.signal_strength_min = line->signal_strength_min;
889
+ safe_line.signal_strength_max = line->signal_strength_max;
890
+ safe_line.spectrum_mode = (int)line->spectrum_mode;
891
+ safe_line.center_freq = (double)line->center_freq;
892
+ safe_line.span_freq = (double)line->span_freq;
893
+ safe_line.low_edge_freq = (double)line->low_edge_freq;
894
+ safe_line.high_edge_freq = (double)line->high_edge_freq;
895
+ safe_line.data_length = (int)((line->spectrum_data_length > sizeof(safe_line.data)) ? sizeof(safe_line.data) : line->spectrum_data_length);
896
+ if (safe_line.data_length > 0 && line->spectrum_data) {
897
+ memcpy(safe_line.data, line->spectrum_data, (size_t)safe_line.data_length);
898
+ }
899
+
900
+ return adapter->user_cb((void*)rig, &safe_line, adapter->user_arg);
901
+ }
902
+
860
903
  SHIM_API int shim_rig_set_freq_callback(hamlib_shim_handle_t h, shim_freq_cb_t cb, void* arg) {
861
904
  freq_cb_adapter.user_cb = cb;
862
905
  freq_cb_adapter.user_arg = arg;
@@ -869,6 +912,12 @@ SHIM_API int shim_rig_set_ptt_callback(hamlib_shim_handle_t h, shim_ptt_cb_t cb,
869
912
  return rig_set_ptt_callback((RIG*)h, shim_ptt_cb_thunk, &ptt_cb_adapter);
870
913
  }
871
914
 
915
+ SHIM_API int shim_rig_set_spectrum_callback(hamlib_shim_handle_t h, shim_spectrum_cb_t cb, void* arg) {
916
+ spectrum_cb_adapter.user_cb = cb;
917
+ spectrum_cb_adapter.user_arg = arg;
918
+ return rig_set_spectrum_callback((RIG*)h, cb ? shim_spectrum_cb_thunk : NULL, cb ? &spectrum_cb_adapter : NULL);
919
+ }
920
+
872
921
  /* rig_set_trn: deprecated in Hamlib 4.7.0, may be removed in future versions. */
873
922
  #ifdef __GNUC__
874
923
  #pragma GCC diagnostic push
@@ -908,6 +957,92 @@ SHIM_API uint64_t shim_rig_get_caps_has_set_func(hamlib_shim_handle_t h) {
908
957
  return (uint64_t)(rig->caps->has_set_func);
909
958
  }
910
959
 
960
+ SHIM_API int shim_rig_is_async_data_supported(hamlib_shim_handle_t h) {
961
+ RIG* rig = (RIG*)h;
962
+ if (!rig || !rig->caps) return 0;
963
+ return rig->caps->async_data_supported ? 1 : 0;
964
+ }
965
+
966
+ SHIM_API int shim_rig_get_caps_spectrum_scope_count(hamlib_shim_handle_t h) {
967
+ int count = 0;
968
+ RIG* rig = (RIG*)h;
969
+ if (!rig || !rig->caps) return 0;
970
+ while (count < HAMLIB_MAX_SPECTRUM_SCOPES && rig->caps->spectrum_scopes[count].name) {
971
+ count++;
972
+ }
973
+ return count;
974
+ }
975
+
976
+ SHIM_API int shim_rig_get_caps_spectrum_scope(hamlib_shim_handle_t h, int index, shim_spectrum_scope_t* out) {
977
+ RIG* rig = (RIG*)h;
978
+ if (!rig || !rig->caps || !out || index < 0 || index >= HAMLIB_MAX_SPECTRUM_SCOPES) return -RIG_EINVAL;
979
+ if (!rig->caps->spectrum_scopes[index].name) return -RIG_ENAVAIL;
980
+ memset(out, 0, sizeof(*out));
981
+ out->id = rig->caps->spectrum_scopes[index].id;
982
+ strncpy(out->name, rig->caps->spectrum_scopes[index].name, sizeof(out->name) - 1);
983
+ return RIG_OK;
984
+ }
985
+
986
+ SHIM_API int shim_rig_get_caps_spectrum_mode_count(hamlib_shim_handle_t h) {
987
+ int count = 0;
988
+ RIG* rig = (RIG*)h;
989
+ if (!rig || !rig->caps) return 0;
990
+ while (count < HAMLIB_MAX_SPECTRUM_MODES && rig->caps->spectrum_modes[count] != RIG_SPECTRUM_MODE_NONE) {
991
+ count++;
992
+ }
993
+ return count;
994
+ }
995
+
996
+ SHIM_API int shim_rig_get_caps_spectrum_mode(hamlib_shim_handle_t h, int index, int* out) {
997
+ RIG* rig = (RIG*)h;
998
+ if (!rig || !rig->caps || !out || index < 0 || index >= HAMLIB_MAX_SPECTRUM_MODES) return -RIG_EINVAL;
999
+ if (rig->caps->spectrum_modes[index] == RIG_SPECTRUM_MODE_NONE) return -RIG_ENAVAIL;
1000
+ *out = (int)rig->caps->spectrum_modes[index];
1001
+ return RIG_OK;
1002
+ }
1003
+
1004
+ SHIM_API int shim_rig_get_caps_spectrum_span_count(hamlib_shim_handle_t h) {
1005
+ int count = 0;
1006
+ RIG* rig = (RIG*)h;
1007
+ if (!rig || !rig->caps) return 0;
1008
+ while (count < HAMLIB_MAX_SPECTRUM_SPANS && rig->caps->spectrum_spans[count] != 0) {
1009
+ count++;
1010
+ }
1011
+ return count;
1012
+ }
1013
+
1014
+ SHIM_API int shim_rig_get_caps_spectrum_span(hamlib_shim_handle_t h, int index, double* out) {
1015
+ RIG* rig = (RIG*)h;
1016
+ if (!rig || !rig->caps || !out || index < 0 || index >= HAMLIB_MAX_SPECTRUM_SPANS) return -RIG_EINVAL;
1017
+ if (rig->caps->spectrum_spans[index] == 0) return -RIG_ENAVAIL;
1018
+ *out = (double)rig->caps->spectrum_spans[index];
1019
+ return RIG_OK;
1020
+ }
1021
+
1022
+ SHIM_API int shim_rig_get_caps_spectrum_avg_mode_count(hamlib_shim_handle_t h) {
1023
+ int count = 0;
1024
+ RIG* rig = (RIG*)h;
1025
+ if (!rig || !rig->caps) return 0;
1026
+ while (count < HAMLIB_MAX_SPECTRUM_AVG_MODES && rig->caps->spectrum_avg_modes[count].name) {
1027
+ count++;
1028
+ }
1029
+ return count;
1030
+ }
1031
+
1032
+ SHIM_API int shim_rig_get_caps_spectrum_avg_mode(hamlib_shim_handle_t h, int index, shim_spectrum_avg_mode_t* out) {
1033
+ RIG* rig = (RIG*)h;
1034
+ if (!rig || !rig->caps || !out || index < 0 || index >= HAMLIB_MAX_SPECTRUM_AVG_MODES) return -RIG_EINVAL;
1035
+ if (!rig->caps->spectrum_avg_modes[index].name) return -RIG_ENAVAIL;
1036
+ memset(out, 0, sizeof(*out));
1037
+ out->id = rig->caps->spectrum_avg_modes[index].id;
1038
+ strncpy(out->name, rig->caps->spectrum_avg_modes[index].name, sizeof(out->name) - 1);
1039
+ return RIG_OK;
1040
+ }
1041
+
1042
+ SHIM_API const char* shim_rig_str_spectrum_mode(int mode) {
1043
+ return rig_strspectrummode((enum rig_spectrum_mode_e)mode);
1044
+ }
1045
+
911
1046
  SHIM_API int shim_rig_sprintf_mode(uint64_t modes, char* buf, int buflen) {
912
1047
  if (!buf || buflen <= 0) return 0;
913
1048
  buf[0] = '\0';