com.xrlab.labframe_brainbit 1.0.7 → 1.0.9
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/Scripts/BrainBitManager.cs +93 -40
- package/package.json +2 -3
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
using UnityEngine;
|
|
2
2
|
using System.Collections;
|
|
3
3
|
using System.Collections.Generic;
|
|
4
|
+
using System.Collections.Concurrent;
|
|
4
5
|
using LabFrame2023;
|
|
5
6
|
using UnityEngine.Events;
|
|
6
7
|
using NeuroSDK;
|
|
@@ -91,6 +92,9 @@ public class BrainBitManager : LabSingleton<BrainBitManager>, IManager
|
|
|
91
92
|
private string _currentEEGTag = "eeg";
|
|
92
93
|
// 用於記錄目前阻抗寫入資料的標籤
|
|
93
94
|
private string _currentImpedanceTag = "impedance";
|
|
95
|
+
|
|
96
|
+
// 主執行緒派發佇列,用於將背景執行緒的回呼安全地轉移到主執行緒執行
|
|
97
|
+
private readonly ConcurrentQueue<Action> _mainThreadActions = new ConcurrentQueue<Action>();
|
|
94
98
|
#endregion
|
|
95
99
|
|
|
96
100
|
#region IManager Implementation
|
|
@@ -119,6 +123,24 @@ public class BrainBitManager : LabSingleton<BrainBitManager>, IManager
|
|
|
119
123
|
LabTools.Log($"[BrainBit] Config - AutoSelectBestSignal: {_config.AutoSelectBestSignal}, ConnectDelay: {_config.ConnectDelaySeconds}s");
|
|
120
124
|
}
|
|
121
125
|
|
|
126
|
+
/// <summary>
|
|
127
|
+
/// 每幀執行主執行緒佇列中的 Action(處理從背景執行緒派發過來的回呼)
|
|
128
|
+
/// </summary>
|
|
129
|
+
private void Update()
|
|
130
|
+
{
|
|
131
|
+
while (_mainThreadActions.TryDequeue(out var action))
|
|
132
|
+
{
|
|
133
|
+
try
|
|
134
|
+
{
|
|
135
|
+
action?.Invoke();
|
|
136
|
+
}
|
|
137
|
+
catch (Exception e)
|
|
138
|
+
{
|
|
139
|
+
LabTools.LogError($"[BrainBit] Error executing main thread action: {e.Message}");
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
122
144
|
public IEnumerator ManagerDispose()
|
|
123
145
|
{
|
|
124
146
|
LabTools.Log("[BrainBit] Disposing BrainBit Manager...");
|
|
@@ -174,8 +196,11 @@ public class BrainBitManager : LabSingleton<BrainBitManager>, IManager
|
|
|
174
196
|
{
|
|
175
197
|
LabTools.Log("[BrainBit] Starting device scan...");
|
|
176
198
|
|
|
177
|
-
//
|
|
178
|
-
_scanner
|
|
199
|
+
// 重用 Scanner,避免重複建立造成 native 資源洩漏
|
|
200
|
+
if (_scanner == null)
|
|
201
|
+
{
|
|
202
|
+
_scanner = new Scanner(SensorFamily.SensorLEBrainBit);
|
|
203
|
+
}
|
|
179
204
|
_scanner.EventSensorsChanged += OnSensorsFound;
|
|
180
205
|
|
|
181
206
|
// 開始掃描
|
|
@@ -520,37 +545,46 @@ public class BrainBitManager : LabSingleton<BrainBitManager>, IManager
|
|
|
520
545
|
|
|
521
546
|
private void OnSensorsFound(IScanner scanner, IReadOnlyList<SensorInfo> sensors)
|
|
522
547
|
{
|
|
548
|
+
// 此回呼由 NeuroSDK 在背景執行緒觸發!
|
|
549
|
+
// 必須將所有 Unity API 呼叫(StartCoroutine、StopCoroutine 等)派發到主執行緒
|
|
523
550
|
try
|
|
524
551
|
{
|
|
525
|
-
LabTools.Log($"[BrainBit] Found {sensors.Count} device(s)");
|
|
526
|
-
|
|
527
|
-
// 觸發設備發現事件
|
|
528
|
-
OnDeviceFound?.Invoke(new List<SensorInfo>(sensors));
|
|
552
|
+
LabTools.Log($"[BrainBit] Found {sensors.Count} device(s) (background thread)");
|
|
529
553
|
|
|
530
554
|
if (sensors.Count > 0)
|
|
531
555
|
{
|
|
532
|
-
//
|
|
533
|
-
StopScan();
|
|
534
|
-
|
|
535
|
-
// 選擇要連接的設備
|
|
556
|
+
// 選擇要連接的設備(純邏輯,不涉及 Unity API,可以在背景執行緒執行)
|
|
536
557
|
SensorInfo targetSensor = SelectBestDevice(sensors);
|
|
537
|
-
|
|
538
558
|
LabTools.Log($"[BrainBit] Selected device: {targetSensor.Name} (RSSI: {targetSensor.RSSI})");
|
|
539
|
-
|
|
559
|
+
|
|
560
|
+
// 先在背景緒停止 Scanner(SDK 層級,非 Unity API)
|
|
561
|
+
_scanner?.Stop();
|
|
562
|
+
_scanner.EventSensorsChanged -= OnSensorsFound;
|
|
563
|
+
|
|
564
|
+
// 將剩下的操作排入主執行緒執行
|
|
565
|
+
_mainThreadActions.Enqueue(() =>
|
|
540
566
|
{
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
567
|
+
IsScanning = false;
|
|
568
|
+
|
|
569
|
+
// 觸發設備發現事件
|
|
570
|
+
OnDeviceFound?.Invoke(new List<SensorInfo>(sensors));
|
|
545
571
|
|
|
546
|
-
|
|
547
|
-
|
|
572
|
+
if (_scanTimeoutCoroutine != null)
|
|
573
|
+
{
|
|
574
|
+
StopCoroutine(_scanTimeoutCoroutine);
|
|
575
|
+
_scanTimeoutCoroutine = null;
|
|
576
|
+
LabTools.Log("[BrainBit] Scan timeout coroutine stopped");
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// 等待一段時間後建立連接(確保掃描完全停止)
|
|
580
|
+
StartCoroutine(DelayedConnect(targetSensor));
|
|
581
|
+
});
|
|
548
582
|
}
|
|
549
583
|
}
|
|
550
584
|
catch (Exception e)
|
|
551
585
|
{
|
|
552
586
|
LabTools.LogError($"[BrainBit] Error in OnSensorsFound: {e.Message}");
|
|
553
|
-
OnError?.Invoke($"Device discovery error: {e.Message}");
|
|
587
|
+
_mainThreadActions.Enqueue(() => OnError?.Invoke($"Device discovery error: {e.Message}"));
|
|
554
588
|
}
|
|
555
589
|
}
|
|
556
590
|
|
|
@@ -612,6 +646,13 @@ public class BrainBitManager : LabSingleton<BrainBitManager>, IManager
|
|
|
612
646
|
{
|
|
613
647
|
LabTools.Log($"[BrainBit] Connecting to device: {sensorInfo.Name} ({sensorInfo.Address})");
|
|
614
648
|
|
|
649
|
+
// 清理舊的 Sensor(如果有的話),避免 native 資源洩漏
|
|
650
|
+
if (_currentSensor != null)
|
|
651
|
+
{
|
|
652
|
+
try { _currentSensor.Dispose(); } catch (Exception) { }
|
|
653
|
+
_currentSensor = null;
|
|
654
|
+
}
|
|
655
|
+
|
|
615
656
|
// 創建 Sensor(此時掃描已停止)
|
|
616
657
|
_currentSensor = _scanner.CreateSensor(sensorInfo) as BrainBitSensor;
|
|
617
658
|
|
|
@@ -716,20 +757,26 @@ public class BrainBitManager : LabSingleton<BrainBitManager>, IManager
|
|
|
716
757
|
|
|
717
758
|
private void OnSignalDataReceived(ISensor sensor, BrainBitSignalData[] data)
|
|
718
759
|
{
|
|
760
|
+
// 此回呼由 NeuroSDK 在背景執行緒觸發!
|
|
719
761
|
try
|
|
720
762
|
{
|
|
721
763
|
foreach (var packet in data)
|
|
722
764
|
{
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
// 觸發事件
|
|
726
|
-
OnEEGDataReceived?.Invoke(_lastEEGData);
|
|
765
|
+
var eegData = new BrainBit_EEGData(packet.T3, packet.T4, packet.O1, packet.O2);
|
|
766
|
+
_lastEEGData = eegData;
|
|
727
767
|
|
|
728
|
-
//
|
|
729
|
-
|
|
768
|
+
// 將事件觸發和資料寫入排入主執行緒
|
|
769
|
+
_mainThreadActions.Enqueue(() =>
|
|
730
770
|
{
|
|
731
|
-
|
|
732
|
-
|
|
771
|
+
// 觸發事件(訂閱者可能更新 UI)
|
|
772
|
+
OnEEGDataReceived?.Invoke(eegData);
|
|
773
|
+
|
|
774
|
+
// 自動保存到 LabDataManager
|
|
775
|
+
if (_autoWriteEEGData && LabDataManager.Instance.IsInited)
|
|
776
|
+
{
|
|
777
|
+
LabDataManager.Instance.WriteData(eegData, _currentEEGTag);
|
|
778
|
+
}
|
|
779
|
+
});
|
|
733
780
|
}
|
|
734
781
|
}
|
|
735
782
|
catch (Exception e)
|
|
@@ -740,24 +787,30 @@ public class BrainBitManager : LabSingleton<BrainBitManager>, IManager
|
|
|
740
787
|
|
|
741
788
|
private void OnResistanceDataReceived(ISensor sensor, BrainBitResistData data)
|
|
742
789
|
{
|
|
790
|
+
// 此回呼由 NeuroSDK 在背景執行緒觸發!
|
|
743
791
|
try
|
|
744
792
|
{
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
// 觸發事件
|
|
748
|
-
OnImpedanceDataReceived?.Invoke(_lastImpedanceData);
|
|
793
|
+
var impedanceData = new BrainBit_ImpedanceData(data.T3, data.T4, data.O1, data.O2);
|
|
794
|
+
_lastImpedanceData = impedanceData;
|
|
749
795
|
|
|
750
|
-
//
|
|
751
|
-
|
|
796
|
+
// 將事件觸發和資料寫入排入主執行緒
|
|
797
|
+
_mainThreadActions.Enqueue(() =>
|
|
752
798
|
{
|
|
753
|
-
|
|
754
|
-
|
|
799
|
+
// 觸發事件(訂閱者可能更新 UI)
|
|
800
|
+
OnImpedanceDataReceived?.Invoke(impedanceData);
|
|
755
801
|
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
802
|
+
// 檢查阻抗警告: 當有任一通道阻抗超過 200,000 時
|
|
803
|
+
if (!impedanceData.IsImpedanceGood)
|
|
804
|
+
{
|
|
805
|
+
LabTools.LogError($"[BrainBit] High impedance detected: {impedanceData.GetImpedanceStatus()}");
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// 自動保存到 LabDataManager
|
|
809
|
+
if (_autoWriteImpedanceData && LabDataManager.Instance.IsInited)
|
|
810
|
+
{
|
|
811
|
+
LabDataManager.Instance.WriteData(impedanceData, _currentImpedanceTag);
|
|
812
|
+
}
|
|
813
|
+
});
|
|
761
814
|
}
|
|
762
815
|
catch (Exception e)
|
|
763
816
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "com.xrlab.labframe_brainbit",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"displayName": "Lab Frame 2023 - BrainBit Plugin",
|
|
5
5
|
"description": "BrainBit Support for LabFrame2023.\nNote: Currently only supports BrainBit in lab!!",
|
|
6
6
|
"unity": "2022.3",
|
|
@@ -10,8 +10,7 @@
|
|
|
10
10
|
"licensesUrl": "https://youtu.be/dQw4w9WgXcQ",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"com.xrlab.labframe": "0.0.9",
|
|
13
|
-
"com.neurotech.neurosdk2": "https://github.com/BrainbitLLC/unity_neurosdk2.git"
|
|
14
|
-
"com.neurotech.emstartifacts": "https://github.com/BrainbitLLC/unity_em_st_artifacts.git"
|
|
13
|
+
"com.neurotech.neurosdk2": "https://github.com/BrainbitLLC/unity_neurosdk2.git"
|
|
15
14
|
},
|
|
16
15
|
"author": {
|
|
17
16
|
"name": "NCU wmlab & xrlab",
|