hamlib 0.3.3 → 0.4.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
@@ -7,6 +7,7 @@
7
7
  #include <chrono>
8
8
  #include <cstdio>
9
9
  #include <exception>
10
+ #include <limits>
10
11
 
11
12
  // 安全宏 - 检查RIG指针有效性,防止空指针解引用和已销毁对象访问
12
13
  #define CHECK_RIG_VALID() \
@@ -18,6 +19,21 @@
18
19
  } \
19
20
  } while(0)
20
21
 
22
+ #define RETURN_NULL_IF_INVALID_VFO(vfo) \
23
+ do { \
24
+ if ((vfo) == kInvalidVfoParameter) { \
25
+ return env.Null(); \
26
+ } \
27
+ } while(0)
28
+
29
+ #define RETURN_NULL_IF_RIG_HANDLE_INVALID() \
30
+ do { \
31
+ if (!my_rig) { \
32
+ Napi::Error::New(env, "RIG is not initialized or has been destroyed").ThrowAsJavaScriptException(); \
33
+ return env.Null(); \
34
+ } \
35
+ } while(0)
36
+
21
37
  // Structure to hold rig information for the callback
22
38
  struct RigListData {
23
39
  std::vector<Napi::Object> rigList;
@@ -29,6 +45,25 @@ using namespace Napi;
29
45
  Napi::FunctionReference NodeHamLib::constructor;
30
46
  Napi::ThreadSafeFunction tsfn;
31
47
 
48
+ constexpr int kInvalidVfoParameter = std::numeric_limits<int>::min();
49
+
50
+ static std::string publicVfoToken(int vfo) {
51
+ const char* rawToken = shim_rig_strvfo(vfo);
52
+ if (!rawToken || !*rawToken) {
53
+ return "UNKNOWN";
54
+ }
55
+ return rawToken;
56
+ }
57
+
58
+ static int parseVfoString(Napi::Env env, const std::string& vfoToken) {
59
+ int vfo = shim_rig_parse_vfo(vfoToken.c_str());
60
+ if (vfo == SHIM_RIG_VFO_NONE) {
61
+ Napi::TypeError::New(env, "Invalid Hamlib VFO token: " + vfoToken).ThrowAsJavaScriptException();
62
+ return kInvalidVfoParameter;
63
+ }
64
+ return vfo;
65
+ }
66
+
32
67
  // Base AsyncWorker implementation with Promise support
33
68
  HamLibAsyncWorker::HamLibAsyncWorker(Napi::Env env, NodeHamLib* hamlib_instance)
34
69
  : AsyncWorker(env), hamlib_instance_(hamlib_instance), result_code_(0), error_message_(""), deferred_(Napi::Promise::Deferred::New(env)) {}
@@ -280,16 +315,16 @@ private:
280
315
 
281
316
  class SetLevelAsyncWorker : public HamLibAsyncWorker {
282
317
  public:
283
- SetLevelAsyncWorker(Napi::Env env, NodeHamLib* hamlib_instance, uint64_t level_type, float value)
284
- : HamLibAsyncWorker(env, hamlib_instance), level_type_(level_type), value_(value) {}
318
+ SetLevelAsyncWorker(Napi::Env env, NodeHamLib* hamlib_instance, uint64_t level_type, float value, int vfo)
319
+ : HamLibAsyncWorker(env, hamlib_instance), level_type_(level_type), value_(value), vfo_(vfo) {}
285
320
 
286
321
  void Execute() override {
287
322
  CHECK_RIG_VALID();
288
323
 
289
324
  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_);
325
+ result_code_ = shim_rig_set_level_f(hamlib_instance_->my_rig, vfo_, level_type_, value_);
291
326
  } else {
292
- result_code_ = shim_rig_set_level_i(hamlib_instance_->my_rig, SHIM_RIG_VFO_CURR, level_type_, static_cast<int>(value_));
327
+ result_code_ = shim_rig_set_level_i(hamlib_instance_->my_rig, vfo_, level_type_, static_cast<int>(value_));
293
328
  }
294
329
  if (result_code_ != SHIM_RIG_OK) {
295
330
  error_message_ = shim_rigerror(result_code_);
@@ -313,6 +348,7 @@ public:
313
348
  private:
314
349
  uint64_t level_type_;
315
350
  float value_;
351
+ int vfo_;
316
352
  };
317
353
 
318
354
  class GetLevelAsyncWorker : public HamLibAsyncWorker {
@@ -807,7 +843,7 @@ private:
807
843
  class GetSplitAsyncWorker : public HamLibAsyncWorker {
808
844
  public:
809
845
  GetSplitAsyncWorker(Napi::Env env, NodeHamLib* hamlib_instance, int vfo = SHIM_RIG_VFO_CURR)
810
- : HamLibAsyncWorker(env, hamlib_instance), split_(SHIM_RIG_SPLIT_OFF), tx_vfo_(SHIM_RIG_VFO_B), vfo_(vfo) {}
846
+ : HamLibAsyncWorker(env, hamlib_instance), split_(SHIM_RIG_SPLIT_OFF), tx_vfo_(SHIM_RIG_VFO_CURR), vfo_(vfo) {}
811
847
 
812
848
  void Execute() override {
813
849
  CHECK_RIG_VALID();
@@ -825,12 +861,7 @@ public:
825
861
  } else {
826
862
  Napi::Object obj = Napi::Object::New(env);
827
863
  obj.Set(Napi::String::New(env, "enabled"), Napi::Boolean::New(env, split_ == SHIM_RIG_SPLIT_ON));
828
-
829
- const char* vfo_str = "VFO-B";
830
- if (tx_vfo_ == SHIM_RIG_VFO_A) {
831
- vfo_str = "VFO-A";
832
- }
833
- obj.Set(Napi::String::New(env, "txVfo"), Napi::String::New(env, vfo_str));
864
+ obj.Set(Napi::String::New(env, "txVfo"), Napi::String::New(env, publicVfoToken(tx_vfo_)));
834
865
 
835
866
  deferred_.Resolve(obj);
836
867
  }
@@ -919,17 +950,7 @@ public:
919
950
  return;
920
951
  }
921
952
 
922
- const char* vfo_str = "VFO-CURR"; // 默认值
923
- if (vfo_ == SHIM_RIG_VFO_A) {
924
- vfo_str = "VFO-A";
925
- } else if (vfo_ == SHIM_RIG_VFO_B) {
926
- vfo_str = "VFO-B";
927
- } else if (vfo_ == SHIM_RIG_VFO_CURR) {
928
- vfo_str = "VFO-CURR";
929
- } else if (vfo_ == SHIM_RIG_VFO_MEM) {
930
- vfo_str = "VFO-MEM";
931
- }
932
- deferred_.Resolve(Napi::String::New(env, vfo_str));
953
+ deferred_.Resolve(Napi::String::New(env, publicVfoToken(vfo_)));
933
954
  }
934
955
 
935
956
  void OnError(const Napi::Error& e) override {
@@ -2180,15 +2201,16 @@ private:
2180
2201
 
2181
2202
  // Helper function to parse VFO parameter from JavaScript
2182
2203
  int parseVfoParameter(const Napi::CallbackInfo& info, int index, int defaultVfo = SHIM_RIG_VFO_CURR) {
2183
- if (info.Length() > static_cast<size_t>(index) && info[index].IsString()) {
2184
- std::string vfoStr = info[index].As<Napi::String>().Utf8Value();
2185
- if (vfoStr == "VFO-A") {
2186
- return SHIM_RIG_VFO_A;
2187
- } else if (vfoStr == "VFO-B") {
2188
- return SHIM_RIG_VFO_B;
2189
- }
2204
+ if (info.Length() <= static_cast<size_t>(index) || info[index].IsUndefined() || info[index].IsNull()) {
2205
+ return defaultVfo;
2206
+ }
2207
+ if (!info[index].IsString()) {
2208
+ Napi::TypeError::New(info.Env(), "VFO must be specified as a Hamlib VFO token string")
2209
+ .ThrowAsJavaScriptException();
2210
+ return kInvalidVfoParameter;
2190
2211
  }
2191
- return defaultVfo;
2212
+ std::string vfoStr = info[index].As<Napi::String>().Utf8Value();
2213
+ return parseVfoString(info.Env(), vfoStr);
2192
2214
  }
2193
2215
 
2194
2216
  NodeHamLib::NodeHamLib(const Napi::CallbackInfo & info): ObjectWrap(info) {
@@ -2339,10 +2361,10 @@ void NodeHamLib::EmitSpectrumLine(const shim_spectrum_line_t& line) {
2339
2361
 
2340
2362
  void NodeHamLib::StopSpectrumStreamInternal() {
2341
2363
  std::lock_guard<std::mutex> lock(spectrum_mutex_);
2342
- if (spectrum_stream_running_) {
2364
+ if (spectrum_stream_running_ && my_rig) {
2343
2365
  shim_rig_set_spectrum_callback(my_rig, nullptr, nullptr);
2344
- spectrum_stream_running_ = false;
2345
2366
  }
2367
+ spectrum_stream_running_ = false;
2346
2368
  if (spectrum_tsfn_) {
2347
2369
  spectrum_tsfn_.Release();
2348
2370
  spectrum_tsfn_ = Napi::ThreadSafeFunction();
@@ -2392,23 +2414,13 @@ Napi::Value NodeHamLib::SetVFO(const Napi::CallbackInfo & info) {
2392
2414
  }
2393
2415
 
2394
2416
  if (!info[0].IsString()) {
2395
- Napi::TypeError::New(env, "Must specify VFO-A or VFO-B as a string")
2417
+ Napi::TypeError::New(env, "Must specify a Hamlib VFO token as a string")
2396
2418
  .ThrowAsJavaScriptException();
2397
2419
  return env.Null();
2398
2420
  }
2399
2421
 
2400
- std::string name = info[0].As<Napi::String>().Utf8Value();
2401
- int vfo;
2402
-
2403
- if (name == "VFO-A") {
2404
- vfo = SHIM_RIG_VFO_A;
2405
- } else if (name == "VFO-B") {
2406
- vfo = SHIM_RIG_VFO_B;
2407
- } else {
2408
- Napi::TypeError::New(env, "Invalid VFO name")
2409
- .ThrowAsJavaScriptException();
2410
- return env.Null();
2411
- }
2422
+ int vfo = parseVfoParameter(info, 0, SHIM_RIG_VFO_CURR);
2423
+ RETURN_NULL_IF_INVALID_VFO(vfo);
2412
2424
 
2413
2425
  SetVfoAsyncWorker* worker = new SetVfoAsyncWorker(env, this, vfo);
2414
2426
  worker->Queue();
@@ -2448,13 +2460,9 @@ Napi::Value NodeHamLib::SetFrequency(const Napi::CallbackInfo & info) {
2448
2460
  // Support optional VFO parameter
2449
2461
  int vfo = SHIM_RIG_VFO_CURR;
2450
2462
 
2451
- if (info.Length() >= 2 && info[1].IsString()) {
2452
- std::string vfostr = info[1].As<Napi::String>().Utf8Value();
2453
- if (vfostr == "VFO-A") {
2454
- vfo = SHIM_RIG_VFO_A;
2455
- } else if (vfostr == "VFO-B") {
2456
- vfo = SHIM_RIG_VFO_B;
2457
- }
2463
+ if (info.Length() >= 2) {
2464
+ vfo = parseVfoParameter(info, 1, SHIM_RIG_VFO_CURR);
2465
+ RETURN_NULL_IF_INVALID_VFO(vfo);
2458
2466
  }
2459
2467
 
2460
2468
  SetFrequencyAsyncWorker* worker = new SetFrequencyAsyncWorker(env, this, freq, vfo);
@@ -2505,12 +2513,14 @@ Napi::Value NodeHamLib::SetMode(const Napi::CallbackInfo & info) {
2505
2513
  } else {
2506
2514
  // If second parameter is not "narrow" or "wide", might be VFO
2507
2515
  vfo = parseVfoParameter(info, 1, SHIM_RIG_VFO_CURR);
2516
+ RETURN_NULL_IF_INVALID_VFO(vfo);
2508
2517
  }
2509
2518
  }
2510
2519
 
2511
2520
  // Check for third parameter (VFO) if bandwidth was specified
2512
2521
  if (info.Length() >= 3) {
2513
2522
  vfo = parseVfoParameter(info, 2, SHIM_RIG_VFO_CURR);
2523
+ RETURN_NULL_IF_INVALID_VFO(vfo);
2514
2524
  }
2515
2525
 
2516
2526
  SetModeAsyncWorker* worker = new SetModeAsyncWorker(env, this, mode, bandwidth, vfo);
@@ -2576,13 +2586,9 @@ Napi::Value NodeHamLib::GetFrequency(const Napi::CallbackInfo & info) {
2576
2586
  // Support optional VFO parameter
2577
2587
  int vfo = SHIM_RIG_VFO_CURR;
2578
2588
 
2579
- if (info.Length() >= 1 && info[0].IsString()) {
2580
- std::string vfostr = info[0].As<Napi::String>().Utf8Value();
2581
- if (vfostr == "VFO-A") {
2582
- vfo = SHIM_RIG_VFO_A;
2583
- } else if (vfostr == "VFO-B") {
2584
- vfo = SHIM_RIG_VFO_B;
2585
- }
2589
+ if (info.Length() >= 1) {
2590
+ vfo = parseVfoParameter(info, 0, SHIM_RIG_VFO_CURR);
2591
+ RETURN_NULL_IF_INVALID_VFO(vfo);
2586
2592
  }
2587
2593
 
2588
2594
  GetFrequencyAsyncWorker* worker = new GetFrequencyAsyncWorker(env, this, vfo);
@@ -2618,8 +2624,9 @@ Napi::Value NodeHamLib::GetStrength(const Napi::CallbackInfo & info) {
2618
2624
  // Support optional VFO parameter: getStrength() or getStrength(vfo)
2619
2625
  int vfo = SHIM_RIG_VFO_CURR;
2620
2626
 
2621
- if (info.Length() >= 1 && info[0].IsString()) {
2627
+ if (info.Length() >= 1) {
2622
2628
  vfo = parseVfoParameter(info, 0, SHIM_RIG_VFO_CURR);
2629
+ RETURN_NULL_IF_INVALID_VFO(vfo);
2623
2630
  }
2624
2631
 
2625
2632
  GetStrengthAsyncWorker* worker = new GetStrengthAsyncWorker(env, this, vfo);
@@ -2748,18 +2755,13 @@ Napi::Value NodeHamLib::GetMemoryChannel(const Napi::CallbackInfo & info) {
2748
2755
  return env.Null();
2749
2756
  }
2750
2757
 
2751
- if (info.Length() < 1 || !info[0].IsNumber()) {
2752
- Napi::TypeError::New(env, "Expected (channelNumber: number) or (channelNumber: number, readOnly: boolean)").ThrowAsJavaScriptException();
2758
+ if (info.Length() < 2 || !info[0].IsNumber() || !info[1].IsBoolean()) {
2759
+ Napi::TypeError::New(env, "Expected (channelNumber: number, readOnly: boolean)").ThrowAsJavaScriptException();
2753
2760
  return env.Null();
2754
2761
  }
2755
2762
 
2756
2763
  int channel_num = info[0].As<Napi::Number>().Int32Value();
2757
- bool read_only = true;
2758
-
2759
- if (info.Length() >= 2 && info[1].IsBoolean()) {
2760
- // (channelNumber, readOnly)
2761
- read_only = info[1].As<Napi::Boolean>().Value();
2762
- }
2764
+ bool read_only = info[1].As<Napi::Boolean>().Value();
2763
2765
 
2764
2766
  GetMemoryChannelAsyncWorker* worker = new GetMemoryChannelAsyncWorker(env, this, channel_num, read_only);
2765
2767
  worker->Queue();
@@ -2882,8 +2884,8 @@ Napi::Value NodeHamLib::StartScan(const Napi::CallbackInfo & info) {
2882
2884
  return env.Null();
2883
2885
  }
2884
2886
 
2885
- if (info.Length() < 1 || !info[0].IsString()) {
2886
- Napi::TypeError::New(env, "Expected scan type (VFO, MEM, PROG, etc.)").ThrowAsJavaScriptException();
2887
+ if (info.Length() < 2 || !info[0].IsString() || !info[1].IsNumber()) {
2888
+ Napi::TypeError::New(env, "Expected (scanType: string, channel: number)").ThrowAsJavaScriptException();
2887
2889
  return env.Null();
2888
2890
  }
2889
2891
 
@@ -2905,10 +2907,7 @@ Napi::Value NodeHamLib::StartScan(const Napi::CallbackInfo & info) {
2905
2907
  return env.Null();
2906
2908
  }
2907
2909
 
2908
- int channel = 0;
2909
- if (info.Length() > 1 && info[1].IsNumber()) {
2910
- channel = info[1].As<Napi::Number>().Int32Value();
2911
- }
2910
+ int channel = info[1].As<Napi::Number>().Int32Value();
2912
2911
 
2913
2912
  StartScanAsyncWorker* asyncWorker = new StartScanAsyncWorker(env, this, scanType, channel);
2914
2913
  asyncWorker->Queue();
@@ -2945,13 +2944,9 @@ Napi::Value NodeHamLib::SetLevel(const Napi::CallbackInfo & info) {
2945
2944
  std::string levelTypeStr = info[0].As<Napi::String>().Utf8Value();
2946
2945
  double levelValue = info[1].As<Napi::Number>().DoubleValue();
2947
2946
  int vfo = SHIM_RIG_VFO_CURR;
2948
- if (info.Length() >= 3 && info[2].IsString()) {
2949
- std::string vfoStr = info[2].As<Napi::String>().Utf8Value();
2950
- if (vfoStr == "VFO-A") {
2951
- vfo = SHIM_RIG_VFO_A;
2952
- } else if (vfoStr == "VFO-B") {
2953
- vfo = SHIM_RIG_VFO_B;
2954
- }
2947
+ if (info.Length() >= 3) {
2948
+ vfo = parseVfoParameter(info, 2, SHIM_RIG_VFO_CURR);
2949
+ RETURN_NULL_IF_INVALID_VFO(vfo);
2955
2950
  }
2956
2951
 
2957
2952
  // Map level type strings to hamlib constants
@@ -3042,7 +3037,7 @@ Napi::Value NodeHamLib::SetLevel(const Napi::CallbackInfo & info) {
3042
3037
  }
3043
3038
 
3044
3039
  float val = static_cast<float>(levelValue);
3045
- SetLevelAsyncWorker* worker = new SetLevelAsyncWorker(env, this, levelType, val);
3040
+ SetLevelAsyncWorker* worker = new SetLevelAsyncWorker(env, this, levelType, val, vfo);
3046
3041
  worker->Queue();
3047
3042
  return worker->GetPromise();
3048
3043
  }
@@ -3143,15 +3138,11 @@ Napi::Value NodeHamLib::GetLevel(const Napi::CallbackInfo & info) {
3143
3138
  return env.Null();
3144
3139
  }
3145
3140
 
3146
- // Optional second parameter: VFO string ('VFO-A', 'VFO-B', 'currVFO')
3141
+ // Optional second parameter: Hamlib VFO token string ('VFOA', 'VFOB', 'currVFO', ...)
3147
3142
  int vfo = SHIM_RIG_VFO_CURR;
3148
- if (info.Length() >= 2 && info[1].IsString()) {
3149
- std::string vfoStr = info[1].As<Napi::String>().Utf8Value();
3150
- if (vfoStr == "VFO-A") {
3151
- vfo = SHIM_RIG_VFO_A;
3152
- } else if (vfoStr == "VFO-B") {
3153
- vfo = SHIM_RIG_VFO_B;
3154
- }
3143
+ if (info.Length() >= 2) {
3144
+ vfo = parseVfoParameter(info, 1, SHIM_RIG_VFO_CURR);
3145
+ RETURN_NULL_IF_INVALID_VFO(vfo);
3155
3146
  }
3156
3147
 
3157
3148
  GetLevelAsyncWorker* worker = new GetLevelAsyncWorker(env, this, levelType, vfo);
@@ -3162,6 +3153,7 @@ Napi::Value NodeHamLib::GetLevel(const Napi::CallbackInfo & info) {
3162
3153
 
3163
3154
  Napi::Value NodeHamLib::GetSupportedLevels(const Napi::CallbackInfo & info) {
3164
3155
  Napi::Env env = info.Env();
3156
+ RETURN_NULL_IF_RIG_HANDLE_INVALID();
3165
3157
 
3166
3158
  // Get capabilities from rig caps instead of state (doesn't require rig to be open)
3167
3159
  uint64_t levels = shim_rig_get_caps_has_get_level(my_rig) | shim_rig_get_caps_has_set_level(my_rig);
@@ -3395,6 +3387,7 @@ Napi::Value NodeHamLib::GetFunction(const Napi::CallbackInfo & info) {
3395
3387
 
3396
3388
  Napi::Value NodeHamLib::GetSupportedFunctions(const Napi::CallbackInfo & info) {
3397
3389
  Napi::Env env = info.Env();
3390
+ RETURN_NULL_IF_RIG_HANDLE_INVALID();
3398
3391
 
3399
3392
  // Get capabilities from rig caps instead of state (doesn't require rig to be open)
3400
3393
  uint64_t functions = shim_rig_get_caps_has_get_func(my_rig) | shim_rig_get_caps_has_set_func(my_rig);
@@ -3440,6 +3433,7 @@ Napi::Value NodeHamLib::GetSupportedFunctions(const Napi::CallbackInfo & info) {
3440
3433
  // Mode Query
3441
3434
  Napi::Value NodeHamLib::GetSupportedModes(const Napi::CallbackInfo & info) {
3442
3435
  Napi::Env env = info.Env();
3436
+ RETURN_NULL_IF_RIG_HANDLE_INVALID();
3443
3437
 
3444
3438
  // Get mode list from rig state (populated during rig_open from rx/tx range lists)
3445
3439
  uint64_t modes = shim_rig_get_mode_list(my_rig);
@@ -3490,6 +3484,7 @@ Napi::Value NodeHamLib::SetSplitFreq(const Napi::CallbackInfo & info) {
3490
3484
  if (info.Length() >= 2 && info[1].IsString()) {
3491
3485
  // setSplitFreq(freq, vfo)
3492
3486
  vfo = parseVfoParameter(info, 1, SHIM_RIG_VFO_CURR);
3487
+ RETURN_NULL_IF_INVALID_VFO(vfo);
3493
3488
  }
3494
3489
 
3495
3490
  SetSplitFreqAsyncWorker* asyncWorker = new SetSplitFreqAsyncWorker(env, this, tx_freq, vfo);
@@ -3510,6 +3505,7 @@ Napi::Value NodeHamLib::GetSplitFreq(const Napi::CallbackInfo & info) {
3510
3505
  if (info.Length() >= 1 && info[0].IsString()) {
3511
3506
  // getSplitFreq(vfo)
3512
3507
  vfo = parseVfoParameter(info, 0, SHIM_RIG_VFO_CURR);
3508
+ RETURN_NULL_IF_INVALID_VFO(vfo);
3513
3509
  }
3514
3510
  // Otherwise use default SHIM_RIG_VFO_CURR for getSplitFreq()
3515
3511
 
@@ -3547,6 +3543,7 @@ Napi::Value NodeHamLib::SetSplitMode(const Napi::CallbackInfo & info) {
3547
3543
  if (info[1].IsString()) {
3548
3544
  // setSplitMode(mode, vfo)
3549
3545
  vfo = parseVfoParameter(info, 1, SHIM_RIG_VFO_CURR);
3546
+ RETURN_NULL_IF_INVALID_VFO(vfo);
3550
3547
  } else if (info[1].IsNumber()) {
3551
3548
  // setSplitMode(mode, width)
3552
3549
  tx_width = info[1].As<Napi::Number>().Int32Value();
@@ -3555,6 +3552,7 @@ Napi::Value NodeHamLib::SetSplitMode(const Napi::CallbackInfo & info) {
3555
3552
  // setSplitMode(mode, width, vfo)
3556
3553
  tx_width = info[1].As<Napi::Number>().Int32Value();
3557
3554
  vfo = parseVfoParameter(info, 2, SHIM_RIG_VFO_CURR);
3555
+ RETURN_NULL_IF_INVALID_VFO(vfo);
3558
3556
  }
3559
3557
 
3560
3558
  SetSplitModeAsyncWorker* asyncWorker = new SetSplitModeAsyncWorker(env, this, tx_mode, tx_width, vfo);
@@ -3575,6 +3573,7 @@ Napi::Value NodeHamLib::GetSplitMode(const Napi::CallbackInfo & info) {
3575
3573
  if (info.Length() >= 1 && info[0].IsString()) {
3576
3574
  // getSplitMode(vfo)
3577
3575
  vfo = parseVfoParameter(info, 0, SHIM_RIG_VFO_CURR);
3576
+ RETURN_NULL_IF_INVALID_VFO(vfo);
3578
3577
  }
3579
3578
  // Otherwise use default SHIM_RIG_VFO_CURR for getSplitMode()
3580
3579
 
@@ -3601,7 +3600,7 @@ Napi::Value NodeHamLib::SetSplit(const Napi::CallbackInfo & info) {
3601
3600
 
3602
3601
  // Default values
3603
3602
  int rx_vfo = SHIM_RIG_VFO_CURR;
3604
- int tx_vfo = SHIM_RIG_VFO_B; // Default TX VFO
3603
+ int tx_vfo = SHIM_RIG_VFO_CURR;
3605
3604
 
3606
3605
  // ⚠️ CRITICAL HISTORICAL ISSUE WARNING ⚠️
3607
3606
  // This Split API had a severe parameter order bug that caused AI to repeatedly
@@ -3620,31 +3619,17 @@ Napi::Value NodeHamLib::SetSplit(const Napi::CallbackInfo & info) {
3620
3619
 
3621
3620
  if (info.Length() == 2 && info[1].IsString()) {
3622
3621
  // setSplit(enable, rxVfo) - treating vfo as RX VFO for current VFO operation
3623
- std::string vfoStr = info[1].As<Napi::String>().Utf8Value();
3624
- if (vfoStr == "VFO-A") {
3625
- rx_vfo = SHIM_RIG_VFO_A;
3626
- } else if (vfoStr == "VFO-B") {
3627
- rx_vfo = SHIM_RIG_VFO_B;
3628
- }
3622
+ rx_vfo = parseVfoParameter(info, 1, SHIM_RIG_VFO_CURR);
3623
+ RETURN_NULL_IF_INVALID_VFO(rx_vfo);
3629
3624
  } else if (info.Length() == 3 && info[1].IsString() && info[2].IsString()) {
3630
3625
  // setSplit(enable, rxVfo, txVfo)
3631
3626
  // ⚠️ CRITICAL: Parameter assignment was WRONG before 2024-08-10 fix!
3632
3627
  // ⚠️ Previous bug: info[1] -> txVfoStr, info[2] -> rxVfoStr (WRONG!)
3633
3628
  // ⚠️ Correct now: info[1] -> rxVfoStr, info[2] -> txVfoStr (RIGHT!)
3634
- std::string rxVfoStr = info[1].As<Napi::String>().Utf8Value(); // ✅ CORRECT: info[1] is rxVfo
3635
- std::string txVfoStr = info[2].As<Napi::String>().Utf8Value(); // ✅ CORRECT: info[2] is txVfo
3636
-
3637
- if (rxVfoStr == "VFO-A") {
3638
- rx_vfo = SHIM_RIG_VFO_A;
3639
- } else if (rxVfoStr == "VFO-B") {
3640
- rx_vfo = SHIM_RIG_VFO_B;
3641
- }
3642
-
3643
- if (txVfoStr == "VFO-A") {
3644
- tx_vfo = SHIM_RIG_VFO_A;
3645
- } else if (txVfoStr == "VFO-B") {
3646
- tx_vfo = SHIM_RIG_VFO_B;
3647
- }
3629
+ rx_vfo = parseVfoParameter(info, 1, SHIM_RIG_VFO_CURR);
3630
+ RETURN_NULL_IF_INVALID_VFO(rx_vfo);
3631
+ tx_vfo = parseVfoParameter(info, 2, SHIM_RIG_VFO_CURR);
3632
+ RETURN_NULL_IF_INVALID_VFO(tx_vfo);
3648
3633
  }
3649
3634
 
3650
3635
  SetSplitAsyncWorker* asyncWorker = new SetSplitAsyncWorker(env, this, rx_vfo, split, tx_vfo);
@@ -3665,6 +3650,7 @@ Napi::Value NodeHamLib::GetSplit(const Napi::CallbackInfo & info) {
3665
3650
  if (info.Length() >= 1 && info[0].IsString()) {
3666
3651
  // getSplit(vfo)
3667
3652
  vfo = parseVfoParameter(info, 0, SHIM_RIG_VFO_CURR);
3653
+ RETURN_NULL_IF_INVALID_VFO(vfo);
3668
3654
  }
3669
3655
  // Otherwise use default SHIM_RIG_VFO_CURR for getSplit()
3670
3656
 
@@ -3746,6 +3732,7 @@ Napi::Value NodeHamLib::SetAntenna(const Napi::CallbackInfo & info) {
3746
3732
  int vfo = SHIM_RIG_VFO_CURR;
3747
3733
  if (info.Length() >= 2 && info[1].IsString()) {
3748
3734
  vfo = parseVfoParameter(info, 1, SHIM_RIG_VFO_CURR);
3735
+ RETURN_NULL_IF_INVALID_VFO(vfo);
3749
3736
  }
3750
3737
 
3751
3738
  // Default option value (can be extended later if needed)
@@ -3768,6 +3755,7 @@ Napi::Value NodeHamLib::GetAntenna(const Napi::CallbackInfo & info) {
3768
3755
  int vfo = SHIM_RIG_VFO_CURR;
3769
3756
  if (info.Length() >= 1 && info[0].IsString()) {
3770
3757
  vfo = parseVfoParameter(info, 0, SHIM_RIG_VFO_CURR);
3758
+ RETURN_NULL_IF_INVALID_VFO(vfo);
3771
3759
  }
3772
3760
 
3773
3761
  // Default antenna query (SHIM_RIG_ANT_CURR gets all antenna info)
@@ -4280,6 +4268,7 @@ Napi::Value NodeHamLib::GetPtt(const Napi::CallbackInfo& info) {
4280
4268
  Napi::Env env = info.Env();
4281
4269
 
4282
4270
  int vfo = parseVfoParameter(info, 0, SHIM_RIG_VFO_CURR);
4271
+ RETURN_NULL_IF_INVALID_VFO(vfo);
4283
4272
 
4284
4273
  GetPttAsyncWorker* asyncWorker = new GetPttAsyncWorker(env, this, vfo);
4285
4274
  asyncWorker->Queue();
@@ -4291,6 +4280,7 @@ Napi::Value NodeHamLib::GetDcd(const Napi::CallbackInfo& info) {
4291
4280
  Napi::Env env = info.Env();
4292
4281
 
4293
4282
  int vfo = parseVfoParameter(info, 0, SHIM_RIG_VFO_CURR);
4283
+ RETURN_NULL_IF_INVALID_VFO(vfo);
4294
4284
 
4295
4285
  GetDcdAsyncWorker* asyncWorker = new GetDcdAsyncWorker(env, this, vfo);
4296
4286
  asyncWorker->Queue();
@@ -4308,6 +4298,7 @@ Napi::Value NodeHamLib::SetTuningStep(const Napi::CallbackInfo& info) {
4308
4298
 
4309
4299
  int ts = info[0].As<Napi::Number>().Int32Value();
4310
4300
  int vfo = parseVfoParameter(info, 1, SHIM_RIG_VFO_CURR);
4301
+ RETURN_NULL_IF_INVALID_VFO(vfo);
4311
4302
 
4312
4303
  SetTuningStepAsyncWorker* asyncWorker = new SetTuningStepAsyncWorker(env, this, vfo, ts);
4313
4304
  asyncWorker->Queue();
@@ -4318,6 +4309,7 @@ Napi::Value NodeHamLib::GetTuningStep(const Napi::CallbackInfo& info) {
4318
4309
  Napi::Env env = info.Env();
4319
4310
 
4320
4311
  int vfo = parseVfoParameter(info, 0, SHIM_RIG_VFO_CURR);
4312
+ RETURN_NULL_IF_INVALID_VFO(vfo);
4321
4313
 
4322
4314
  GetTuningStepAsyncWorker* asyncWorker = new GetTuningStepAsyncWorker(env, this, vfo);
4323
4315
  asyncWorker->Queue();
@@ -4348,6 +4340,7 @@ Napi::Value NodeHamLib::SetRepeaterShift(const Napi::CallbackInfo& info) {
4348
4340
  }
4349
4341
 
4350
4342
  int vfo = parseVfoParameter(info, 1, SHIM_RIG_VFO_CURR);
4343
+ RETURN_NULL_IF_INVALID_VFO(vfo);
4351
4344
 
4352
4345
  SetRepeaterShiftAsyncWorker* asyncWorker = new SetRepeaterShiftAsyncWorker(env, this, vfo, shift);
4353
4346
  asyncWorker->Queue();
@@ -4358,6 +4351,7 @@ Napi::Value NodeHamLib::GetRepeaterShift(const Napi::CallbackInfo& info) {
4358
4351
  Napi::Env env = info.Env();
4359
4352
 
4360
4353
  int vfo = parseVfoParameter(info, 0, SHIM_RIG_VFO_CURR);
4354
+ RETURN_NULL_IF_INVALID_VFO(vfo);
4361
4355
 
4362
4356
  GetRepeaterShiftAsyncWorker* asyncWorker = new GetRepeaterShiftAsyncWorker(env, this, vfo);
4363
4357
  asyncWorker->Queue();
@@ -4374,6 +4368,7 @@ Napi::Value NodeHamLib::SetRepeaterOffset(const Napi::CallbackInfo& info) {
4374
4368
 
4375
4369
  int offset = info[0].As<Napi::Number>().Int32Value();
4376
4370
  int vfo = parseVfoParameter(info, 1, SHIM_RIG_VFO_CURR);
4371
+ RETURN_NULL_IF_INVALID_VFO(vfo);
4377
4372
 
4378
4373
  SetRepeaterOffsetAsyncWorker* asyncWorker = new SetRepeaterOffsetAsyncWorker(env, this, vfo, offset);
4379
4374
  asyncWorker->Queue();
@@ -4384,6 +4379,7 @@ Napi::Value NodeHamLib::GetRepeaterOffset(const Napi::CallbackInfo& info) {
4384
4379
  Napi::Env env = info.Env();
4385
4380
 
4386
4381
  int vfo = parseVfoParameter(info, 0, SHIM_RIG_VFO_CURR);
4382
+ RETURN_NULL_IF_INVALID_VFO(vfo);
4387
4383
 
4388
4384
  GetRepeaterOffsetAsyncWorker* asyncWorker = new GetRepeaterOffsetAsyncWorker(env, this, vfo);
4389
4385
  asyncWorker->Queue();
@@ -5185,6 +5181,7 @@ Napi::Value NodeHamLib::SetCtcssTone(const Napi::CallbackInfo& info) {
5185
5181
 
5186
5182
  unsigned int tone = static_cast<unsigned int>(info[0].As<Napi::Number>().Uint32Value());
5187
5183
  int vfo = parseVfoParameter(info, 1, SHIM_RIG_VFO_CURR);
5184
+ RETURN_NULL_IF_INVALID_VFO(vfo);
5188
5185
 
5189
5186
  SetCtcssToneAsyncWorker* asyncWorker = new SetCtcssToneAsyncWorker(env, this, vfo, tone);
5190
5187
  asyncWorker->Queue();
@@ -5195,6 +5192,7 @@ Napi::Value NodeHamLib::GetCtcssTone(const Napi::CallbackInfo& info) {
5195
5192
  Napi::Env env = info.Env();
5196
5193
 
5197
5194
  int vfo = parseVfoParameter(info, 0, SHIM_RIG_VFO_CURR);
5195
+ RETURN_NULL_IF_INVALID_VFO(vfo);
5198
5196
 
5199
5197
  GetCtcssToneAsyncWorker* asyncWorker = new GetCtcssToneAsyncWorker(env, this, vfo);
5200
5198
  asyncWorker->Queue();
@@ -5211,6 +5209,7 @@ Napi::Value NodeHamLib::SetDcsCode(const Napi::CallbackInfo& info) {
5211
5209
 
5212
5210
  unsigned int code = static_cast<unsigned int>(info[0].As<Napi::Number>().Uint32Value());
5213
5211
  int vfo = parseVfoParameter(info, 1, SHIM_RIG_VFO_CURR);
5212
+ RETURN_NULL_IF_INVALID_VFO(vfo);
5214
5213
 
5215
5214
  SetDcsCodeAsyncWorker* asyncWorker = new SetDcsCodeAsyncWorker(env, this, vfo, code);
5216
5215
  asyncWorker->Queue();
@@ -5221,6 +5220,7 @@ Napi::Value NodeHamLib::GetDcsCode(const Napi::CallbackInfo& info) {
5221
5220
  Napi::Env env = info.Env();
5222
5221
 
5223
5222
  int vfo = parseVfoParameter(info, 0, SHIM_RIG_VFO_CURR);
5223
+ RETURN_NULL_IF_INVALID_VFO(vfo);
5224
5224
 
5225
5225
  GetDcsCodeAsyncWorker* asyncWorker = new GetDcsCodeAsyncWorker(env, this, vfo);
5226
5226
  asyncWorker->Queue();
@@ -5237,6 +5237,7 @@ Napi::Value NodeHamLib::SetCtcssSql(const Napi::CallbackInfo& info) {
5237
5237
 
5238
5238
  unsigned int tone = static_cast<unsigned int>(info[0].As<Napi::Number>().Uint32Value());
5239
5239
  int vfo = parseVfoParameter(info, 1, SHIM_RIG_VFO_CURR);
5240
+ RETURN_NULL_IF_INVALID_VFO(vfo);
5240
5241
 
5241
5242
  SetCtcssSqlAsyncWorker* asyncWorker = new SetCtcssSqlAsyncWorker(env, this, vfo, tone);
5242
5243
  asyncWorker->Queue();
@@ -5247,6 +5248,7 @@ Napi::Value NodeHamLib::GetCtcssSql(const Napi::CallbackInfo& info) {
5247
5248
  Napi::Env env = info.Env();
5248
5249
 
5249
5250
  int vfo = parseVfoParameter(info, 0, SHIM_RIG_VFO_CURR);
5251
+ RETURN_NULL_IF_INVALID_VFO(vfo);
5250
5252
 
5251
5253
  GetCtcssSqlAsyncWorker* asyncWorker = new GetCtcssSqlAsyncWorker(env, this, vfo);
5252
5254
  asyncWorker->Queue();
@@ -5263,6 +5265,7 @@ Napi::Value NodeHamLib::SetDcsSql(const Napi::CallbackInfo& info) {
5263
5265
 
5264
5266
  unsigned int code = static_cast<unsigned int>(info[0].As<Napi::Number>().Uint32Value());
5265
5267
  int vfo = parseVfoParameter(info, 1, SHIM_RIG_VFO_CURR);
5268
+ RETURN_NULL_IF_INVALID_VFO(vfo);
5266
5269
 
5267
5270
  SetDcsSqlAsyncWorker* asyncWorker = new SetDcsSqlAsyncWorker(env, this, vfo, code);
5268
5271
  asyncWorker->Queue();
@@ -5273,6 +5276,7 @@ Napi::Value NodeHamLib::GetDcsSql(const Napi::CallbackInfo& info) {
5273
5276
  Napi::Env env = info.Env();
5274
5277
 
5275
5278
  int vfo = parseVfoParameter(info, 0, SHIM_RIG_VFO_CURR);
5279
+ RETURN_NULL_IF_INVALID_VFO(vfo);
5276
5280
 
5277
5281
  GetDcsSqlAsyncWorker* asyncWorker = new GetDcsSqlAsyncWorker(env, this, vfo);
5278
5282
  asyncWorker->Queue();
@@ -5335,6 +5339,7 @@ Napi::Value NodeHamLib::SendDtmf(const Napi::CallbackInfo& info) {
5335
5339
 
5336
5340
  std::string digits = info[0].As<Napi::String>().Utf8Value();
5337
5341
  int vfo = parseVfoParameter(info, 1, SHIM_RIG_VFO_CURR);
5342
+ RETURN_NULL_IF_INVALID_VFO(vfo);
5338
5343
 
5339
5344
  SendDtmfAsyncWorker* asyncWorker = new SendDtmfAsyncWorker(env, this, vfo, digits);
5340
5345
  asyncWorker->Queue();
@@ -5344,12 +5349,14 @@ Napi::Value NodeHamLib::SendDtmf(const Napi::CallbackInfo& info) {
5344
5349
  Napi::Value NodeHamLib::RecvDtmf(const Napi::CallbackInfo& info) {
5345
5350
  Napi::Env env = info.Env();
5346
5351
 
5347
- int maxLength = 32; // Default max length
5348
- if (info.Length() > 0 && info[0].IsNumber()) {
5349
- maxLength = info[0].As<Napi::Number>().Int32Value();
5352
+ if (info.Length() < 1 || !info[0].IsNumber()) {
5353
+ Napi::TypeError::New(env, "Expected (maxLength: number, vfo?: string)").ThrowAsJavaScriptException();
5354
+ return env.Null();
5350
5355
  }
5356
+ int maxLength = info[0].As<Napi::Number>().Int32Value();
5351
5357
 
5352
5358
  int vfo = parseVfoParameter(info, 1, SHIM_RIG_VFO_CURR);
5359
+ RETURN_NULL_IF_INVALID_VFO(vfo);
5353
5360
 
5354
5361
  RecvDtmfAsyncWorker* asyncWorker = new RecvDtmfAsyncWorker(env, this, vfo, maxLength);
5355
5362
  asyncWorker->Queue();
@@ -5361,6 +5368,7 @@ Napi::Value NodeHamLib::GetMem(const Napi::CallbackInfo& info) {
5361
5368
  Napi::Env env = info.Env();
5362
5369
 
5363
5370
  int vfo = parseVfoParameter(info, 0, SHIM_RIG_VFO_CURR);
5371
+ RETURN_NULL_IF_INVALID_VFO(vfo);
5364
5372
 
5365
5373
  GetMemAsyncWorker* asyncWorker = new GetMemAsyncWorker(env, this, vfo);
5366
5374
  asyncWorker->Queue();
@@ -5377,6 +5385,7 @@ Napi::Value NodeHamLib::SetBank(const Napi::CallbackInfo& info) {
5377
5385
 
5378
5386
  int bank = info[0].As<Napi::Number>().Int32Value();
5379
5387
  int vfo = parseVfoParameter(info, 1, SHIM_RIG_VFO_CURR);
5388
+ RETURN_NULL_IF_INVALID_VFO(vfo);
5380
5389
 
5381
5390
  SetBankAsyncWorker* asyncWorker = new SetBankAsyncWorker(env, this, vfo, bank);
5382
5391
  asyncWorker->Queue();
@@ -5402,6 +5411,7 @@ Napi::Value NodeHamLib::SendMorse(const Napi::CallbackInfo& info) {
5402
5411
 
5403
5412
  std::string msg = info[0].As<Napi::String>().Utf8Value();
5404
5413
  int vfo = parseVfoParameter(info, 1, SHIM_RIG_VFO_CURR);
5414
+ RETURN_NULL_IF_INVALID_VFO(vfo);
5405
5415
 
5406
5416
  SendMorseAsyncWorker* asyncWorker = new SendMorseAsyncWorker(env, this, vfo, msg);
5407
5417
  asyncWorker->Queue();
@@ -5412,6 +5422,7 @@ Napi::Value NodeHamLib::StopMorse(const Napi::CallbackInfo& info) {
5412
5422
  Napi::Env env = info.Env();
5413
5423
 
5414
5424
  int vfo = parseVfoParameter(info, 0, SHIM_RIG_VFO_CURR);
5425
+ RETURN_NULL_IF_INVALID_VFO(vfo);
5415
5426
 
5416
5427
  StopMorseAsyncWorker* asyncWorker = new StopMorseAsyncWorker(env, this, vfo);
5417
5428
  asyncWorker->Queue();
@@ -5422,6 +5433,7 @@ Napi::Value NodeHamLib::WaitMorse(const Napi::CallbackInfo& info) {
5422
5433
  Napi::Env env = info.Env();
5423
5434
 
5424
5435
  int vfo = parseVfoParameter(info, 0, SHIM_RIG_VFO_CURR);
5436
+ RETURN_NULL_IF_INVALID_VFO(vfo);
5425
5437
 
5426
5438
  WaitMorseAsyncWorker* asyncWorker = new WaitMorseAsyncWorker(env, this, vfo);
5427
5439
  asyncWorker->Queue();
@@ -5439,6 +5451,7 @@ Napi::Value NodeHamLib::SendVoiceMem(const Napi::CallbackInfo& info) {
5439
5451
 
5440
5452
  int ch = info[0].As<Napi::Number>().Int32Value();
5441
5453
  int vfo = parseVfoParameter(info, 1, SHIM_RIG_VFO_CURR);
5454
+ RETURN_NULL_IF_INVALID_VFO(vfo);
5442
5455
 
5443
5456
  SendVoiceMemAsyncWorker* asyncWorker = new SendVoiceMemAsyncWorker(env, this, vfo, ch);
5444
5457
  asyncWorker->Queue();
@@ -5449,6 +5462,7 @@ Napi::Value NodeHamLib::StopVoiceMem(const Napi::CallbackInfo& info) {
5449
5462
  Napi::Env env = info.Env();
5450
5463
 
5451
5464
  int vfo = parseVfoParameter(info, 0, SHIM_RIG_VFO_CURR);
5465
+ RETURN_NULL_IF_INVALID_VFO(vfo);
5452
5466
 
5453
5467
  StopVoiceMemAsyncWorker* asyncWorker = new StopVoiceMemAsyncWorker(env, this, vfo);
5454
5468
  asyncWorker->Queue();
@@ -5475,6 +5489,7 @@ Napi::Value NodeHamLib::SetSplitFreqMode(const Napi::CallbackInfo& info) {
5475
5489
  std::string mode_str = info[1].As<Napi::String>().Utf8Value();
5476
5490
  int tx_width = static_cast<int>(info[2].As<Napi::Number>().DoubleValue());
5477
5491
  int vfo = parseVfoParameter(info, 3, SHIM_RIG_VFO_CURR);
5492
+ RETURN_NULL_IF_INVALID_VFO(vfo);
5478
5493
 
5479
5494
  int tx_mode = shim_rig_parse_mode(mode_str.c_str());
5480
5495
  if (tx_mode == SHIM_RIG_MODE_NONE) {
@@ -5491,6 +5506,7 @@ Napi::Value NodeHamLib::GetSplitFreqMode(const Napi::CallbackInfo& info) {
5491
5506
  Napi::Env env = info.Env();
5492
5507
 
5493
5508
  int vfo = parseVfoParameter(info, 0, SHIM_RIG_VFO_CURR);
5509
+ RETURN_NULL_IF_INVALID_VFO(vfo);
5494
5510
 
5495
5511
  GetSplitFreqModeAsyncWorker* asyncWorker = new GetSplitFreqModeAsyncWorker(env, this, vfo);
5496
5512
  asyncWorker->Queue();
@@ -5585,25 +5601,27 @@ Napi::Value NodeHamLib::MW2Power(const Napi::CallbackInfo& info) {
5585
5601
  Napi::Value NodeHamLib::Reset(const Napi::CallbackInfo& info) {
5586
5602
  Napi::Env env = info.Env();
5587
5603
 
5588
- int reset = SHIM_RIG_RESET_SOFT; // Default to soft reset
5589
-
5590
- if (info.Length() > 0 && info[0].IsString()) {
5591
- std::string reset_str = info[0].As<Napi::String>().Utf8Value();
5592
- if (reset_str == "NONE") {
5593
- reset = SHIM_RIG_RESET_NONE;
5594
- } else if (reset_str == "SOFT") {
5595
- reset = SHIM_RIG_RESET_SOFT;
5596
- } else if (reset_str == "MCALL") {
5597
- reset = SHIM_RIG_RESET_MCALL;
5598
- } else if (reset_str == "MASTER") {
5599
- reset = SHIM_RIG_RESET_MASTER;
5600
- } else if (reset_str == "VFO") {
5601
- reset = SHIM_RIG_RESET_VFO;
5602
- } else {
5603
- Napi::Error::New(env, "Invalid reset type: " + reset_str +
5604
- " (valid: NONE, SOFT, VFO, MCALL, MASTER)").ThrowAsJavaScriptException();
5605
- return env.Null();
5606
- }
5604
+ if (info.Length() < 1 || !info[0].IsString()) {
5605
+ Napi::TypeError::New(env, "Expected (resetType: string)").ThrowAsJavaScriptException();
5606
+ return env.Null();
5607
+ }
5608
+
5609
+ int reset = SHIM_RIG_RESET_SOFT;
5610
+ std::string reset_str = info[0].As<Napi::String>().Utf8Value();
5611
+ if (reset_str == "NONE") {
5612
+ reset = SHIM_RIG_RESET_NONE;
5613
+ } else if (reset_str == "SOFT") {
5614
+ reset = SHIM_RIG_RESET_SOFT;
5615
+ } else if (reset_str == "MCALL") {
5616
+ reset = SHIM_RIG_RESET_MCALL;
5617
+ } else if (reset_str == "MASTER") {
5618
+ reset = SHIM_RIG_RESET_MASTER;
5619
+ } else if (reset_str == "VFO") {
5620
+ reset = SHIM_RIG_RESET_VFO;
5621
+ } else {
5622
+ Napi::Error::New(env, "Invalid reset type: " + reset_str +
5623
+ " (valid: NONE, SOFT, VFO, MCALL, MASTER)").ThrowAsJavaScriptException();
5624
+ return env.Null();
5607
5625
  }
5608
5626
 
5609
5627
  ResetAsyncWorker* asyncWorker = new ResetAsyncWorker(env, this, reset);
@@ -5876,6 +5894,7 @@ Napi::Value NodeHamLib::GetVfoInfo(const Napi::CallbackInfo& info) {
5876
5894
  Napi::Env env = info.Env();
5877
5895
 
5878
5896
  int vfo = parseVfoParameter(info, 0, SHIM_RIG_VFO_CURR);
5897
+ RETURN_NULL_IF_INVALID_VFO(vfo);
5879
5898
 
5880
5899
  GetVfoInfoAsyncWorker* asyncWorker = new GetVfoInfoAsyncWorker(env, this, vfo);
5881
5900
  asyncWorker->Queue();
@@ -6003,6 +6022,7 @@ Napi::Value NodeHamLib::SendRaw(const Napi::CallbackInfo& info) {
6003
6022
 
6004
6023
  Napi::Value NodeHamLib::GetSpectrumCapabilities(const Napi::CallbackInfo& info) {
6005
6024
  Napi::Env env = info.Env();
6025
+ RETURN_NULL_IF_RIG_HANDLE_INVALID();
6006
6026
  Napi::Object result = Napi::Object::New(env);
6007
6027
 
6008
6028
  result.Set("asyncDataSupported", Napi::Boolean::New(env, shim_rig_is_async_data_supported(my_rig) != 0));
@@ -6214,6 +6234,7 @@ Napi::Value NodeHamLib::GetConf(const Napi::CallbackInfo& info) {
6214
6234
 
6215
6235
  Napi::Value NodeHamLib::GetPassbandNormal(const Napi::CallbackInfo& info) {
6216
6236
  Napi::Env env = info.Env();
6237
+ RETURN_NULL_IF_RIG_HANDLE_INVALID();
6217
6238
 
6218
6239
  if (info.Length() < 1 || !info[0].IsString()) {
6219
6240
  Napi::TypeError::New(env, "Expected (mode: string)").ThrowAsJavaScriptException();
@@ -6228,6 +6249,7 @@ Napi::Value NodeHamLib::GetPassbandNormal(const Napi::CallbackInfo& info) {
6228
6249
 
6229
6250
  Napi::Value NodeHamLib::GetPassbandNarrow(const Napi::CallbackInfo& info) {
6230
6251
  Napi::Env env = info.Env();
6252
+ RETURN_NULL_IF_RIG_HANDLE_INVALID();
6231
6253
 
6232
6254
  if (info.Length() < 1 || !info[0].IsString()) {
6233
6255
  Napi::TypeError::New(env, "Expected (mode: string)").ThrowAsJavaScriptException();
@@ -6242,6 +6264,7 @@ Napi::Value NodeHamLib::GetPassbandNarrow(const Napi::CallbackInfo& info) {
6242
6264
 
6243
6265
  Napi::Value NodeHamLib::GetPassbandWide(const Napi::CallbackInfo& info) {
6244
6266
  Napi::Env env = info.Env();
6267
+ RETURN_NULL_IF_RIG_HANDLE_INVALID();
6245
6268
 
6246
6269
  if (info.Length() < 1 || !info[0].IsString()) {
6247
6270
  Napi::TypeError::New(env, "Expected (mode: string)").ThrowAsJavaScriptException();
@@ -6258,6 +6281,7 @@ Napi::Value NodeHamLib::GetPassbandWide(const Napi::CallbackInfo& info) {
6258
6281
 
6259
6282
  Napi::Value NodeHamLib::GetResolution(const Napi::CallbackInfo& info) {
6260
6283
  Napi::Env env = info.Env();
6284
+ RETURN_NULL_IF_RIG_HANDLE_INVALID();
6261
6285
 
6262
6286
  if (info.Length() < 1 || !info[0].IsString()) {
6263
6287
  Napi::TypeError::New(env, "Expected (mode: string)").ThrowAsJavaScriptException();
@@ -6274,6 +6298,7 @@ Napi::Value NodeHamLib::GetResolution(const Napi::CallbackInfo& info) {
6274
6298
 
6275
6299
  Napi::Value NodeHamLib::GetSupportedParms(const Napi::CallbackInfo& info) {
6276
6300
  Napi::Env env = info.Env();
6301
+ RETURN_NULL_IF_RIG_HANDLE_INVALID();
6277
6302
 
6278
6303
  uint64_t parms = shim_rig_get_caps_has_get_parm(my_rig) | shim_rig_get_caps_has_set_parm(my_rig);
6279
6304
  Napi::Array parmArray = Napi::Array::New(env);
@@ -6295,6 +6320,7 @@ Napi::Value NodeHamLib::GetSupportedParms(const Napi::CallbackInfo& info) {
6295
6320
 
6296
6321
  Napi::Value NodeHamLib::GetSupportedVfoOps(const Napi::CallbackInfo& info) {
6297
6322
  Napi::Env env = info.Env();
6323
+ RETURN_NULL_IF_RIG_HANDLE_INVALID();
6298
6324
 
6299
6325
  int ops = shim_rig_get_caps_vfo_ops(my_rig);
6300
6326
  Napi::Array opsArray = Napi::Array::New(env);
@@ -6321,6 +6347,7 @@ Napi::Value NodeHamLib::GetSupportedVfoOps(const Napi::CallbackInfo& info) {
6321
6347
 
6322
6348
  Napi::Value NodeHamLib::GetSupportedScanTypes(const Napi::CallbackInfo& info) {
6323
6349
  Napi::Env env = info.Env();
6350
+ RETURN_NULL_IF_RIG_HANDLE_INVALID();
6324
6351
 
6325
6352
  int scan = shim_rig_get_caps_has_scan(my_rig);
6326
6353
  Napi::Array scanArray = Napi::Array::New(env);
@@ -6355,6 +6382,7 @@ static Napi::Array ModeBitmaskToArray(Napi::Env env, uint64_t modes) {
6355
6382
 
6356
6383
  Napi::Value NodeHamLib::GetPreampValues(const Napi::CallbackInfo& info) {
6357
6384
  Napi::Env env = info.Env();
6385
+ RETURN_NULL_IF_RIG_HANDLE_INVALID();
6358
6386
  int buf[SHIM_HAMLIB_MAX_MODES];
6359
6387
  int count = shim_rig_get_caps_preamp(my_rig, buf, SHIM_HAMLIB_MAX_MODES);
6360
6388
  Napi::Array arr = Napi::Array::New(env, count);
@@ -6366,6 +6394,7 @@ Napi::Value NodeHamLib::GetPreampValues(const Napi::CallbackInfo& info) {
6366
6394
 
6367
6395
  Napi::Value NodeHamLib::GetAttenuatorValues(const Napi::CallbackInfo& info) {
6368
6396
  Napi::Env env = info.Env();
6397
+ RETURN_NULL_IF_RIG_HANDLE_INVALID();
6369
6398
  int buf[SHIM_HAMLIB_MAX_MODES];
6370
6399
  int count = shim_rig_get_caps_attenuator(my_rig, buf, SHIM_HAMLIB_MAX_MODES);
6371
6400
  Napi::Array arr = Napi::Array::New(env, count);
@@ -6376,19 +6405,26 @@ Napi::Value NodeHamLib::GetAttenuatorValues(const Napi::CallbackInfo& info) {
6376
6405
  }
6377
6406
 
6378
6407
  Napi::Value NodeHamLib::GetMaxRit(const Napi::CallbackInfo& info) {
6408
+ Napi::Env env = info.Env();
6409
+ RETURN_NULL_IF_RIG_HANDLE_INVALID();
6379
6410
  return Napi::Number::New(info.Env(), (double)shim_rig_get_caps_max_rit(my_rig));
6380
6411
  }
6381
6412
 
6382
6413
  Napi::Value NodeHamLib::GetMaxXit(const Napi::CallbackInfo& info) {
6414
+ Napi::Env env = info.Env();
6415
+ RETURN_NULL_IF_RIG_HANDLE_INVALID();
6383
6416
  return Napi::Number::New(info.Env(), (double)shim_rig_get_caps_max_xit(my_rig));
6384
6417
  }
6385
6418
 
6386
6419
  Napi::Value NodeHamLib::GetMaxIfShift(const Napi::CallbackInfo& info) {
6420
+ Napi::Env env = info.Env();
6421
+ RETURN_NULL_IF_RIG_HANDLE_INVALID();
6387
6422
  return Napi::Number::New(info.Env(), (double)shim_rig_get_caps_max_ifshift(my_rig));
6388
6423
  }
6389
6424
 
6390
6425
  Napi::Value NodeHamLib::GetAvailableCtcssTones(const Napi::CallbackInfo& info) {
6391
6426
  Napi::Env env = info.Env();
6427
+ RETURN_NULL_IF_RIG_HANDLE_INVALID();
6392
6428
  unsigned int buf[256];
6393
6429
  int count = shim_rig_get_caps_ctcss_list(my_rig, buf, 256);
6394
6430
  Napi::Array arr = Napi::Array::New(env, count);
@@ -6401,6 +6437,7 @@ Napi::Value NodeHamLib::GetAvailableCtcssTones(const Napi::CallbackInfo& info) {
6401
6437
 
6402
6438
  Napi::Value NodeHamLib::GetAvailableDcsCodes(const Napi::CallbackInfo& info) {
6403
6439
  Napi::Env env = info.Env();
6440
+ RETURN_NULL_IF_RIG_HANDLE_INVALID();
6404
6441
  unsigned int buf[256];
6405
6442
  int count = shim_rig_get_caps_dcs_list(my_rig, buf, 256);
6406
6443
  Napi::Array arr = Napi::Array::New(env, count);
@@ -6412,6 +6449,7 @@ Napi::Value NodeHamLib::GetAvailableDcsCodes(const Napi::CallbackInfo& info) {
6412
6449
 
6413
6450
  Napi::Value NodeHamLib::GetFrequencyRanges(const Napi::CallbackInfo& info) {
6414
6451
  Napi::Env env = info.Env();
6452
+ RETURN_NULL_IF_RIG_HANDLE_INVALID();
6415
6453
 
6416
6454
  shim_freq_range_t rx_buf[30];
6417
6455
  shim_freq_range_t tx_buf[30];
@@ -6442,6 +6480,7 @@ Napi::Value NodeHamLib::GetFrequencyRanges(const Napi::CallbackInfo& info) {
6442
6480
 
6443
6481
  Napi::Value NodeHamLib::GetTuningSteps(const Napi::CallbackInfo& info) {
6444
6482
  Napi::Env env = info.Env();
6483
+ RETURN_NULL_IF_RIG_HANDLE_INVALID();
6445
6484
  shim_mode_value_t buf[20];
6446
6485
  int count = shim_rig_get_caps_tuning_steps(my_rig, buf, 20);
6447
6486
  Napi::Array arr = Napi::Array::New(env, count);
@@ -6456,6 +6495,7 @@ Napi::Value NodeHamLib::GetTuningSteps(const Napi::CallbackInfo& info) {
6456
6495
 
6457
6496
  Napi::Value NodeHamLib::GetFilterList(const Napi::CallbackInfo& info) {
6458
6497
  Napi::Env env = info.Env();
6498
+ RETURN_NULL_IF_RIG_HANDLE_INVALID();
6459
6499
  shim_mode_value_t buf[60];
6460
6500
  int count = shim_rig_get_caps_filters(my_rig, buf, 60);
6461
6501
  Napi::Array arr = Napi::Array::New(env, count);