com.xrlab.labframe_brainbit 1.1.0 → 1.1.2
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 +602 -237
- package/Scripts/BrainBitConfig.cs +12 -5
- package/Scripts/BrainBitManager.cs +314 -142
- package/package.json +1 -1
|
@@ -60,8 +60,15 @@ public class BrainBitConfig
|
|
|
60
60
|
/// </summary>
|
|
61
61
|
public bool EmotionsMentalEstimation = false;
|
|
62
62
|
|
|
63
|
-
/// <summary>
|
|
64
|
-
/// 情緒分析優先腦側:NONE / LEFT / RIGHT,預設 NONE(雙側平均)。
|
|
65
|
-
/// </summary>
|
|
66
|
-
public SideType EmotionsPrioritySide = SideType.NONE;
|
|
67
|
-
|
|
63
|
+
/// <summary>
|
|
64
|
+
/// 情緒分析優先腦側:NONE / LEFT / RIGHT,預設 NONE(雙側平均)。
|
|
65
|
+
/// </summary>
|
|
66
|
+
public SideType EmotionsPrioritySide = SideType.NONE;
|
|
67
|
+
|
|
68
|
+
/// <summary>
|
|
69
|
+
/// 指定要連線的 BrainBit 裝置 MAC(例如 "E0:39:7A:68:75:58")。
|
|
70
|
+
/// 留空 = 維持原行為(依掃描結果與 AutoSelectBestSignal 選擇)。
|
|
71
|
+
/// 多台同時在場時設定此值,可避免連錯台。
|
|
72
|
+
/// </summary>
|
|
73
|
+
public string TargetDeviceAddress = "";
|
|
74
|
+
}
|
|
@@ -124,13 +124,21 @@ public class BrainBitManager : LabSingleton<BrainBitManager>, IManager
|
|
|
124
124
|
private bool _autoWriteEEGData = false;
|
|
125
125
|
private bool _autoWriteImpedanceData = false;
|
|
126
126
|
|
|
127
|
-
private Coroutine _connectionMonitorCoroutine;
|
|
128
|
-
private Coroutine _scanTimeoutCoroutine;
|
|
129
|
-
|
|
130
|
-
private int _reconnectAttempts = 0;
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
private
|
|
127
|
+
private Coroutine _connectionMonitorCoroutine;
|
|
128
|
+
private Coroutine _scanTimeoutCoroutine;
|
|
129
|
+
|
|
130
|
+
private int _reconnectAttempts = 0;
|
|
131
|
+
private bool _isInitialized = false;
|
|
132
|
+
private bool _autoConnectScheduled = false;
|
|
133
|
+
private const int TargetDeviceFallbackAttempts = 3;
|
|
134
|
+
private const double TargetDeviceAttemptIntervalSeconds = 2.0;
|
|
135
|
+
private int _targetDeviceMissAttempts = 0;
|
|
136
|
+
private DateTime _nextTargetDeviceAttemptUtc = DateTime.MinValue;
|
|
137
|
+
private bool _connectionSelectionQueued = false;
|
|
138
|
+
private readonly object _sensorSelectionLock = new object();
|
|
139
|
+
|
|
140
|
+
// 用於記錄目前 EEG 寫入資料的標籤 (對應不同的儲存檔案)
|
|
141
|
+
private string _currentEEGTag = "eeg";
|
|
134
142
|
// 用於記錄目前阻抗寫入資料的標籤
|
|
135
143
|
private string _currentImpedanceTag = "impedance";
|
|
136
144
|
|
|
@@ -139,10 +147,10 @@ public class BrainBitManager : LabSingleton<BrainBitManager>, IManager
|
|
|
139
147
|
|
|
140
148
|
// === Emotions ===
|
|
141
149
|
private EmotionsController _emotionsController;
|
|
142
|
-
private bool
|
|
143
|
-
private string _currentMindTag
|
|
144
|
-
private string _currentSpectralTag
|
|
145
|
-
private BrainBit_MindData
|
|
150
|
+
private bool _autoWriteEmotionData = false;
|
|
151
|
+
private string _currentMindTag = "mind";
|
|
152
|
+
private string _currentSpectralTag = "spectral";
|
|
153
|
+
private BrainBit_MindData _lastMindData;
|
|
146
154
|
private BrainBit_SpectralData _lastSpectralData;
|
|
147
155
|
// 若為 true,代表 EEG 串流是被情緒處理自動啟動的 — StopEmotionsProcessing 時要一起停
|
|
148
156
|
private bool _emotionsStartedEEG = false;
|
|
@@ -154,20 +162,21 @@ public class BrainBitManager : LabSingleton<BrainBitManager>, IManager
|
|
|
154
162
|
LabTools.Log("[BrainBit] Initializing BrainBit Manager...");
|
|
155
163
|
|
|
156
164
|
// 載入配置
|
|
157
|
-
|
|
165
|
+
if (!EnsureInitialized(nameof(ManagerInit)))
|
|
166
|
+
{
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
158
169
|
|
|
159
170
|
// 初始化數據對象
|
|
160
|
-
_lastEEGData = new BrainBit_EEGData();
|
|
161
|
-
_lastImpedanceData = new BrainBit_ImpedanceData();
|
|
162
171
|
|
|
163
172
|
// 開始連接監控
|
|
164
|
-
_connectionMonitorCoroutine = StartCoroutine(MonitorConnection());
|
|
165
173
|
|
|
166
174
|
// 自動連接
|
|
167
|
-
if (_config.AutoConnectOnInit)
|
|
168
|
-
{
|
|
169
|
-
|
|
170
|
-
|
|
175
|
+
if (_config.AutoConnectOnInit && !_autoConnectScheduled && !IsConnected && !IsScanning)
|
|
176
|
+
{
|
|
177
|
+
_autoConnectScheduled = true;
|
|
178
|
+
StartCoroutine(DelayedAutoConnect());
|
|
179
|
+
}
|
|
171
180
|
|
|
172
181
|
LabTools.Log("[BrainBit] Manager initialized successfully");
|
|
173
182
|
LabTools.Log($"[BrainBit] Config - AutoConnect: {_config.AutoConnectOnInit}, ScanTimeout: {_config.ScanTimeoutSeconds}s");
|
|
@@ -232,21 +241,70 @@ public class BrainBitManager : LabSingleton<BrainBitManager>, IManager
|
|
|
232
241
|
}
|
|
233
242
|
#endregion
|
|
234
243
|
|
|
235
|
-
#region Public Methods
|
|
236
|
-
|
|
244
|
+
#region Public Methods
|
|
245
|
+
private bool EnsureInitialized(string callerName)
|
|
246
|
+
{
|
|
247
|
+
if (_isInitialized && _config != null)
|
|
248
|
+
{
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
try
|
|
253
|
+
{
|
|
254
|
+
_config ??= LabTools.GetConfig<BrainBitConfig>(false);
|
|
255
|
+
if (_config == null)
|
|
256
|
+
{
|
|
257
|
+
LabTools.LogError($"[BrainBit] {callerName} failed - BrainBitConfig is missing");
|
|
258
|
+
OnError?.Invoke("BrainBit config is missing");
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
_lastEEGData ??= new BrainBit_EEGData();
|
|
263
|
+
_lastImpedanceData ??= new BrainBit_ImpedanceData();
|
|
264
|
+
|
|
265
|
+
if (_connectionMonitorCoroutine == null)
|
|
266
|
+
{
|
|
267
|
+
_connectionMonitorCoroutine = StartCoroutine(MonitorConnection());
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
_isInitialized = true;
|
|
271
|
+
LabTools.Log($"[BrainBit] Initialization ready for {callerName}");
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
catch (Exception e)
|
|
275
|
+
{
|
|
276
|
+
LabTools.LogError($"[BrainBit] {callerName} failed during initialization: {e.Message}");
|
|
277
|
+
OnError?.Invoke($"Initialization failed: {e.Message}");
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/// <summary>
|
|
237
283
|
/// 開始掃描 BrainBit 設備
|
|
238
284
|
/// </summary>
|
|
239
|
-
public void StartScan()
|
|
240
|
-
{
|
|
241
|
-
if (
|
|
242
|
-
{
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
285
|
+
public void StartScan()
|
|
286
|
+
{
|
|
287
|
+
if (!EnsureInitialized(nameof(StartScan)))
|
|
288
|
+
{
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (IsScanning)
|
|
293
|
+
{
|
|
294
|
+
LabTools.LogError("[BrainBit] Already scanning for devices");
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
lock (_sensorSelectionLock)
|
|
299
|
+
{
|
|
300
|
+
_targetDeviceMissAttempts = 0;
|
|
301
|
+
_nextTargetDeviceAttemptUtc = DateTime.MinValue;
|
|
302
|
+
_connectionSelectionQueued = false;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
try
|
|
306
|
+
{
|
|
307
|
+
LabTools.Log("[BrainBit] Starting device scan...");
|
|
250
308
|
|
|
251
309
|
// 重用 Scanner,避免重複建立造成 native 資源洩漏
|
|
252
310
|
if (_scanner == null)
|
|
@@ -281,10 +339,10 @@ public class BrainBitManager : LabSingleton<BrainBitManager>, IManager
|
|
|
281
339
|
private IEnumerator DebugScanStatus()
|
|
282
340
|
{
|
|
283
341
|
int attempts = 0;
|
|
284
|
-
while (IsScanning && attempts < 10)
|
|
285
|
-
{
|
|
286
|
-
yield return new WaitForSeconds(
|
|
287
|
-
attempts++;
|
|
342
|
+
while (IsScanning && attempts < 10)
|
|
343
|
+
{
|
|
344
|
+
yield return new WaitForSeconds((float)TargetDeviceAttemptIntervalSeconds);
|
|
345
|
+
attempts++;
|
|
288
346
|
|
|
289
347
|
try
|
|
290
348
|
{
|
|
@@ -295,12 +353,18 @@ public class BrainBitManager : LabSingleton<BrainBitManager>, IManager
|
|
|
295
353
|
if (foundDevices != null && foundDevices.Count > 0)
|
|
296
354
|
{
|
|
297
355
|
foreach (var device in foundDevices)
|
|
298
|
-
{
|
|
299
|
-
LabTools.Log($"[BrainBit] Found device: {device.Name} ({device.Address}) RSSI: {device.RSSI}");
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
356
|
+
{
|
|
357
|
+
LabTools.Log($"[BrainBit] Found device: {device.Name} ({device.Address}) RSSI: {device.RSSI}");
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
TrySelectAndQueueDevice(foundDevices);
|
|
361
|
+
if (_connectionSelectionQueued)
|
|
362
|
+
{
|
|
363
|
+
yield break;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
catch (Exception e)
|
|
304
368
|
{
|
|
305
369
|
LabTools.LogError($"[BrainBit] Error during scan debug: {e.Message}");
|
|
306
370
|
}
|
|
@@ -577,10 +641,18 @@ public class BrainBitManager : LabSingleton<BrainBitManager>, IManager
|
|
|
577
641
|
}
|
|
578
642
|
}
|
|
579
643
|
|
|
580
|
-
private IEnumerator ScanTimeout()
|
|
581
|
-
{
|
|
582
|
-
|
|
583
|
-
|
|
644
|
+
private IEnumerator ScanTimeout()
|
|
645
|
+
{
|
|
646
|
+
if (!EnsureInitialized(nameof(ScanTimeout)))
|
|
647
|
+
{
|
|
648
|
+
IsScanning = false;
|
|
649
|
+
_scanTimeoutCoroutine = null;
|
|
650
|
+
yield break;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
float timeoutSeconds = _config.ScanTimeoutSeconds;
|
|
654
|
+
LabTools.Log($"[BrainBit] Scan timeout set for {timeoutSeconds} seconds");
|
|
655
|
+
yield return new WaitForSeconds(timeoutSeconds);
|
|
584
656
|
|
|
585
657
|
if (_scanTimeoutCoroutine != null && _currentSensor == null)
|
|
586
658
|
{
|
|
@@ -595,54 +667,126 @@ public class BrainBitManager : LabSingleton<BrainBitManager>, IManager
|
|
|
595
667
|
_scanTimeoutCoroutine = null;
|
|
596
668
|
}
|
|
597
669
|
|
|
598
|
-
private void OnSensorsFound(IScanner scanner, IReadOnlyList<SensorInfo> sensors)
|
|
599
|
-
{
|
|
600
|
-
// 此回呼由 NeuroSDK 在背景執行緒觸發!
|
|
601
|
-
// 必須將所有 Unity API 呼叫(StartCoroutine、StopCoroutine 等)派發到主執行緒
|
|
670
|
+
private void OnSensorsFound(IScanner scanner, IReadOnlyList<SensorInfo> sensors)
|
|
671
|
+
{
|
|
672
|
+
// 此回呼由 NeuroSDK 在背景執行緒觸發!
|
|
673
|
+
// 必須將所有 Unity API 呼叫(StartCoroutine、StopCoroutine 等)派發到主執行緒
|
|
602
674
|
try
|
|
603
675
|
{
|
|
604
676
|
LabTools.Log($"[BrainBit] Found {sensors.Count} device(s) (background thread)");
|
|
605
|
-
|
|
606
|
-
if (sensors.Count > 0)
|
|
607
|
-
{
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
677
|
+
|
|
678
|
+
if (sensors.Count > 0)
|
|
679
|
+
{
|
|
680
|
+
TrySelectAndQueueDevice(sensors);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
catch (Exception e)
|
|
684
|
+
{
|
|
685
|
+
LabTools.LogError($"[BrainBit] Error in OnSensorsFound: {e.Message}");
|
|
686
|
+
_mainThreadActions.Enqueue(() => OnError?.Invoke($"Device discovery error: {e.Message}"));
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
private void TrySelectAndQueueDevice(IReadOnlyList<SensorInfo> sensors)
|
|
691
|
+
{
|
|
692
|
+
if (sensors == null || sensors.Count == 0)
|
|
693
|
+
{
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
SensorInfo targetSensor;
|
|
698
|
+
lock (_sensorSelectionLock)
|
|
699
|
+
{
|
|
700
|
+
if (_connectionSelectionQueued)
|
|
701
|
+
{
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
if (!TryChooseDevice(sensors, out targetSensor))
|
|
706
|
+
{
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
_connectionSelectionQueued = true;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
LabTools.Log($"[BrainBit] Selected device: {targetSensor.Name} (RSSI: {targetSensor.RSSI})");
|
|
714
|
+
|
|
715
|
+
// 先停止 Scanner(SDK 層級,非 Unity API)
|
|
716
|
+
if (_scanner != null)
|
|
717
|
+
{
|
|
718
|
+
_scanner.Stop();
|
|
719
|
+
_scanner.EventSensorsChanged -= OnSensorsFound;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
var foundSensors = new List<SensorInfo>(sensors);
|
|
723
|
+
|
|
724
|
+
// 將剩下的操作排入主執行緒執行
|
|
725
|
+
_mainThreadActions.Enqueue(() =>
|
|
726
|
+
{
|
|
727
|
+
IsScanning = false;
|
|
728
|
+
|
|
729
|
+
// 觸發設備發現事件
|
|
730
|
+
OnDeviceFound?.Invoke(foundSensors);
|
|
731
|
+
|
|
732
|
+
if (_scanTimeoutCoroutine != null)
|
|
733
|
+
{
|
|
734
|
+
StopCoroutine(_scanTimeoutCoroutine);
|
|
735
|
+
_scanTimeoutCoroutine = null;
|
|
736
|
+
LabTools.Log("[BrainBit] Scan timeout coroutine stopped");
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// 等待一段時間後建立連接(確保掃描完全停止)
|
|
740
|
+
StartCoroutine(DelayedConnect(targetSensor));
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
private bool TryChooseDevice(IReadOnlyList<SensorInfo> sensors, out SensorInfo targetSensor)
|
|
745
|
+
{
|
|
746
|
+
string targetAddress = _config.TargetDeviceAddress?.Trim();
|
|
747
|
+
if (string.IsNullOrEmpty(targetAddress))
|
|
748
|
+
{
|
|
749
|
+
targetSensor = SelectBestDevice(sensors);
|
|
750
|
+
return true;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
foreach (var sensor in sensors)
|
|
754
|
+
{
|
|
755
|
+
if (string.Equals(sensor.Address, targetAddress, StringComparison.OrdinalIgnoreCase))
|
|
756
|
+
{
|
|
757
|
+
_targetDeviceMissAttempts = 0;
|
|
758
|
+
_nextTargetDeviceAttemptUtc = DateTime.MinValue;
|
|
759
|
+
targetSensor = sensor;
|
|
760
|
+
return true;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
DateTime nowUtc = DateTime.UtcNow;
|
|
765
|
+
if (nowUtc < _nextTargetDeviceAttemptUtc)
|
|
766
|
+
{
|
|
767
|
+
targetSensor = default;
|
|
768
|
+
return false;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
_targetDeviceMissAttempts++;
|
|
772
|
+
_nextTargetDeviceAttemptUtc = nowUtc.AddSeconds(TargetDeviceAttemptIntervalSeconds);
|
|
773
|
+
if (_targetDeviceMissAttempts < TargetDeviceFallbackAttempts)
|
|
774
|
+
{
|
|
775
|
+
LabTools.Log($"[BrainBit] Target {targetAddress} not found among {sensors.Count} device(s); attempt {_targetDeviceMissAttempts}/{TargetDeviceFallbackAttempts}, retry in {TargetDeviceAttemptIntervalSeconds:0.#}s.");
|
|
776
|
+
targetSensor = default;
|
|
777
|
+
return false;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
LabTools.Log($"[BrainBit] Target {targetAddress} not found after {TargetDeviceFallbackAttempts} attempts; selecting another device.");
|
|
781
|
+
_targetDeviceMissAttempts = 0;
|
|
782
|
+
_nextTargetDeviceAttemptUtc = DateTime.MinValue;
|
|
783
|
+
targetSensor = SelectBestDevice(sensors);
|
|
784
|
+
return true;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/// <summary>
|
|
788
|
+
/// 選擇最佳設備
|
|
789
|
+
/// </summary>
|
|
646
790
|
/// <param name="sensors">發現的設備列表</param>
|
|
647
791
|
/// <returns>選中的設備</returns>
|
|
648
792
|
private SensorInfo SelectBestDevice(IReadOnlyList<SensorInfo> sensors)
|
|
@@ -681,12 +825,18 @@ public class BrainBitManager : LabSingleton<BrainBitManager>, IManager
|
|
|
681
825
|
/// 延遲連接設備
|
|
682
826
|
/// </summary>
|
|
683
827
|
/// <param name="sensorInfo">設備信息</param>
|
|
684
|
-
private IEnumerator DelayedConnect(SensorInfo sensorInfo)
|
|
685
|
-
{
|
|
686
|
-
|
|
828
|
+
private IEnumerator DelayedConnect(SensorInfo sensorInfo)
|
|
829
|
+
{
|
|
830
|
+
if (!EnsureInitialized(nameof(DelayedConnect)))
|
|
831
|
+
{
|
|
832
|
+
yield break;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
float connectDelaySeconds = _config.ConnectDelaySeconds;
|
|
836
|
+
LabTools.Log($"[BrainBit] Waiting {connectDelaySeconds}s before connecting...");
|
|
687
837
|
|
|
688
838
|
// 等待指定時間,確保掃描完全停止
|
|
689
|
-
yield return new WaitForSeconds(
|
|
839
|
+
yield return new WaitForSeconds(connectDelaySeconds);
|
|
690
840
|
|
|
691
841
|
// 建立連接
|
|
692
842
|
ConnectToDevice(sensorInfo);
|
|
@@ -777,12 +927,18 @@ public class BrainBitManager : LabSingleton<BrainBitManager>, IManager
|
|
|
777
927
|
}
|
|
778
928
|
}
|
|
779
929
|
|
|
780
|
-
private void HandleDisconnection()
|
|
781
|
-
{
|
|
782
|
-
LabTools.LogError($"[BrainBit] Device disconnected: {ConnectedDeviceName}");
|
|
783
|
-
|
|
784
|
-
if (
|
|
785
|
-
{
|
|
930
|
+
private void HandleDisconnection()
|
|
931
|
+
{
|
|
932
|
+
LabTools.LogError($"[BrainBit] Device disconnected: {ConnectedDeviceName}");
|
|
933
|
+
|
|
934
|
+
if (!EnsureInitialized(nameof(HandleDisconnection)))
|
|
935
|
+
{
|
|
936
|
+
Disconnect();
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
if (_config.DisconnectNotification)
|
|
941
|
+
{
|
|
786
942
|
LabPromptBox.Show($"BrainBit 設備已斷線!\nDevice {ConnectedDeviceName} disconnected!");
|
|
787
943
|
}
|
|
788
944
|
|
|
@@ -795,12 +951,18 @@ public class BrainBitManager : LabSingleton<BrainBitManager>, IManager
|
|
|
795
951
|
Disconnect();
|
|
796
952
|
}
|
|
797
953
|
|
|
798
|
-
private IEnumerator AttemptReconnect()
|
|
799
|
-
{
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
954
|
+
private IEnumerator AttemptReconnect()
|
|
955
|
+
{
|
|
956
|
+
if (!EnsureInitialized(nameof(AttemptReconnect)))
|
|
957
|
+
{
|
|
958
|
+
yield break;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
_reconnectAttempts++;
|
|
962
|
+
LabTools.Log($"[BrainBit] Attempting reconnection ({_reconnectAttempts}/{_config.AutoReconnectAttempts})...");
|
|
963
|
+
|
|
964
|
+
float reconnectIntervalSeconds = _config.ReconnectIntervalSeconds;
|
|
965
|
+
yield return new WaitForSeconds(reconnectIntervalSeconds);
|
|
804
966
|
|
|
805
967
|
if (!IsConnected)
|
|
806
968
|
{
|
|
@@ -908,13 +1070,18 @@ public class BrainBitManager : LabSingleton<BrainBitManager>, IManager
|
|
|
908
1070
|
IsEmotionsCalibrated = false;
|
|
909
1071
|
|
|
910
1072
|
// 重置狀態
|
|
911
|
-
IsConnected = false;
|
|
912
|
-
IsStreamingEEG = false;
|
|
913
|
-
IsStreamingImpedance = false;
|
|
914
|
-
IsScanning = false;
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
1073
|
+
IsConnected = false;
|
|
1074
|
+
IsStreamingEEG = false;
|
|
1075
|
+
IsStreamingImpedance = false;
|
|
1076
|
+
IsScanning = false;
|
|
1077
|
+
_isInitialized = false;
|
|
1078
|
+
_autoConnectScheduled = false;
|
|
1079
|
+
_targetDeviceMissAttempts = 0;
|
|
1080
|
+
_nextTargetDeviceAttemptUtc = DateTime.MinValue;
|
|
1081
|
+
_connectionSelectionQueued = false;
|
|
1082
|
+
|
|
1083
|
+
LabTools.Log("[BrainBit] Resources cleaned up");
|
|
1084
|
+
}
|
|
918
1085
|
catch (Exception e)
|
|
919
1086
|
{
|
|
920
1087
|
LabTools.LogError($"[BrainBit] Error during cleanup: {e.Message}");
|
|
@@ -928,15 +1095,20 @@ public class BrainBitManager : LabSingleton<BrainBitManager>, IManager
|
|
|
928
1095
|
/// 啟動情緒處理(MindData / SpectralData / 校正進度)。
|
|
929
1096
|
/// 若 EEG 串流未啟動,會自動啟動;呼叫 StopEmotionsProcessing 時會同步停止該 EEG 串流。
|
|
930
1097
|
/// </summary>
|
|
931
|
-
public void StartEmotionsProcessing(bool autoWriteToLabData = true,
|
|
932
|
-
string mindTag = "mind",
|
|
933
|
-
string spectralTag = "spectral")
|
|
934
|
-
{
|
|
935
|
-
if (!
|
|
936
|
-
{
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1098
|
+
public void StartEmotionsProcessing(bool autoWriteToLabData = true,
|
|
1099
|
+
string mindTag = "mind",
|
|
1100
|
+
string spectralTag = "spectral")
|
|
1101
|
+
{
|
|
1102
|
+
if (!EnsureInitialized(nameof(StartEmotionsProcessing)))
|
|
1103
|
+
{
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
if (!IsConnected)
|
|
1108
|
+
{
|
|
1109
|
+
LabTools.LogError("[BrainBit] Device not connected");
|
|
1110
|
+
OnError?.Invoke("Device not connected");
|
|
1111
|
+
return;
|
|
940
1112
|
}
|
|
941
1113
|
|
|
942
1114
|
if (IsProcessingEmotions)
|
|
@@ -948,8 +1120,8 @@ public class BrainBitManager : LabSingleton<BrainBitManager>, IManager
|
|
|
948
1120
|
try
|
|
949
1121
|
{
|
|
950
1122
|
_autoWriteEmotionData = autoWriteToLabData;
|
|
951
|
-
_currentMindTag
|
|
952
|
-
_currentSpectralTag
|
|
1123
|
+
_currentMindTag = string.IsNullOrEmpty(mindTag) ? "mind" : mindTag;
|
|
1124
|
+
_currentSpectralTag = string.IsNullOrEmpty(spectralTag) ? "spectral" : spectralTag;
|
|
953
1125
|
|
|
954
1126
|
// 若 EEG 未啟動,自動啟動並記錄
|
|
955
1127
|
if (!IsStreamingEEG)
|
|
@@ -969,9 +1141,9 @@ public class BrainBitManager : LabSingleton<BrainBitManager>, IManager
|
|
|
969
1141
|
|
|
970
1142
|
// 校正狀態重置
|
|
971
1143
|
IsEmotionsCalibrated = false;
|
|
972
|
-
CalibrationProgress
|
|
973
|
-
_lastMindData
|
|
974
|
-
_lastSpectralData
|
|
1144
|
+
CalibrationProgress = 0;
|
|
1145
|
+
_lastMindData = null;
|
|
1146
|
+
_lastSpectralData = null;
|
|
975
1147
|
|
|
976
1148
|
_emotionsController.StartCalibration();
|
|
977
1149
|
IsProcessingEmotions = true;
|
|
@@ -1010,8 +1182,8 @@ public class BrainBitManager : LabSingleton<BrainBitManager>, IManager
|
|
|
1010
1182
|
}
|
|
1011
1183
|
|
|
1012
1184
|
_autoWriteEmotionData = false;
|
|
1013
|
-
IsEmotionsCalibrated
|
|
1014
|
-
CalibrationProgress
|
|
1185
|
+
IsEmotionsCalibrated = false;
|
|
1186
|
+
CalibrationProgress = 0;
|
|
1015
1187
|
|
|
1016
1188
|
if (_emotionsStartedEEG && IsStreamingEEG)
|
|
1017
1189
|
{
|
|
@@ -1039,7 +1211,7 @@ public class BrainBitManager : LabSingleton<BrainBitManager>, IManager
|
|
|
1039
1211
|
}
|
|
1040
1212
|
|
|
1041
1213
|
IsEmotionsCalibrated = false;
|
|
1042
|
-
CalibrationProgress
|
|
1214
|
+
CalibrationProgress = 0;
|
|
1043
1215
|
_emotionsController.StartCalibration();
|
|
1044
1216
|
|
|
1045
1217
|
LabTools.Log("[BrainBit] Emotion calibration restarted");
|
|
@@ -1077,22 +1249,22 @@ public class BrainBitManager : LabSingleton<BrainBitManager>, IManager
|
|
|
1077
1249
|
{
|
|
1078
1250
|
if (_emotionsController == null) return;
|
|
1079
1251
|
|
|
1080
|
-
_emotionsController.progressCalibrationCallback
|
|
1081
|
-
_emotionsController.lastMindDataCallback
|
|
1082
|
-
_emotionsController.lastSpectralDataCallback
|
|
1083
|
-
_emotionsController.isArtefactedSequenceCallback
|
|
1084
|
-
_emotionsController.isBothSidesArtifactedCallback
|
|
1252
|
+
_emotionsController.progressCalibrationCallback = OnEmotionsCalibrationProgress;
|
|
1253
|
+
_emotionsController.lastMindDataCallback = OnRawMindDataReceived;
|
|
1254
|
+
_emotionsController.lastSpectralDataCallback = OnRawSpectralDataReceived;
|
|
1255
|
+
_emotionsController.isArtefactedSequenceCallback = OnEmotionsArtifactDetected;
|
|
1256
|
+
_emotionsController.isBothSidesArtifactedCallback = OnEmotionsArtifactDetected;
|
|
1085
1257
|
}
|
|
1086
1258
|
|
|
1087
1259
|
private void UnwireEmotionsCallbacks()
|
|
1088
1260
|
{
|
|
1089
1261
|
if (_emotionsController == null) return;
|
|
1090
1262
|
|
|
1091
|
-
_emotionsController.progressCalibrationCallback
|
|
1092
|
-
_emotionsController.lastMindDataCallback
|
|
1093
|
-
_emotionsController.lastSpectralDataCallback
|
|
1094
|
-
_emotionsController.isArtefactedSequenceCallback
|
|
1095
|
-
_emotionsController.isBothSidesArtifactedCallback
|
|
1263
|
+
_emotionsController.progressCalibrationCallback = null;
|
|
1264
|
+
_emotionsController.lastMindDataCallback = null;
|
|
1265
|
+
_emotionsController.lastSpectralDataCallback = null;
|
|
1266
|
+
_emotionsController.isArtefactedSequenceCallback = null;
|
|
1267
|
+
_emotionsController.isBothSidesArtifactedCallback = null;
|
|
1096
1268
|
}
|
|
1097
1269
|
|
|
1098
1270
|
private void OnEmotionsCalibrationProgress(int progress)
|
|
@@ -1150,4 +1322,4 @@ public class BrainBitManager : LabSingleton<BrainBitManager>, IManager
|
|
|
1150
1322
|
}
|
|
1151
1323
|
|
|
1152
1324
|
#endregion
|
|
1153
|
-
}
|
|
1325
|
+
}
|
package/package.json
CHANGED