com.xrlab.labframe_brainbit 1.0.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.
Files changed (35) hide show
  1. package/.github/workflows/main.yml +27 -0
  2. package/.github/workflows/main.yml.meta +7 -0
  3. package/Editor/BrainBitPostProcess.cs +31 -0
  4. package/Editor/BrainBitPostProcess.cs.meta +11 -0
  5. package/Editor.meta +8 -0
  6. package/LICENSE +21 -0
  7. package/LICENSE.meta +7 -0
  8. package/README.md +95 -0
  9. package/README.md.meta +7 -0
  10. package/Runtime/Plugins/Android/AndroidManifest.xml +15 -0
  11. package/Runtime/Plugins/Android/AndroidManifest.xml.meta +7 -0
  12. package/Runtime/Plugins/Android.meta +8 -0
  13. package/Runtime/Plugins.meta +8 -0
  14. package/Runtime.meta +8 -0
  15. package/Scripts/BrainBitData.cs +187 -0
  16. package/Scripts/BrainBitData.cs.meta +11 -0
  17. package/Scripts/BrainBitManager.cs +802 -0
  18. package/Scripts/BrainBitManager.cs.meta +11 -0
  19. package/Scripts/Brainbit.asmdef +18 -0
  20. package/Scripts/Brainbit.asmdef.meta +7 -0
  21. package/Scripts/BrainbitCheckController.cs +669 -0
  22. package/Scripts/BrainbitCheckController.cs.meta +11 -0
  23. package/Scripts/Resource/Config/BrainBitConfig.cs +51 -0
  24. package/Scripts/Resource/Config/BrainBitConfig.cs.meta +11 -0
  25. package/Scripts/Resource/Config.meta +8 -0
  26. package/Scripts/Resource/IManagers/BrainBitController.prefab +46 -0
  27. package/Scripts/Resource/IManagers/BrainBitController.prefab.meta +7 -0
  28. package/Scripts/Resource/IManagers.meta +8 -0
  29. package/Scripts/Resource.meta +8 -0
  30. package/Scripts/Sample/SampleScene.unity +4556 -0
  31. package/Scripts/Sample/SampleScene.unity.meta +7 -0
  32. package/Scripts/Sample.meta +8 -0
  33. package/Scripts.meta +8 -0
  34. package/package.json +28 -0
  35. package/package.json.meta +7 -0
@@ -0,0 +1,27 @@
1
+ name: Push to npm
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+ - main
8
+
9
+ jobs:
10
+ build:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
14
+ - uses: actions/checkout@v2
15
+ # Install Node.js, with the version 12 and using the registry URL of npm, this could be changed to a custom registry or the GitHub registry.
16
+ - uses: actions/setup-node@v1
17
+ with:
18
+ node-version: 12
19
+ registry-url: https://registry.npmjs.org/
20
+
21
+ # Command to install the package dependencies
22
+ - run: yarn install
23
+
24
+ # Publish to npm
25
+ - run: npm publish --access public
26
+ env:
27
+ NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
@@ -0,0 +1,7 @@
1
+ fileFormatVersion: 2
2
+ guid: ed7ffa529d231784c97c9306d57f9c47
3
+ DefaultImporter:
4
+ externalObjects: {}
5
+ userData:
6
+ assetBundleName:
7
+ assetBundleVariant:
@@ -0,0 +1,31 @@
1
+ using UnityEditor;
2
+ using UnityEditor.Callbacks;
3
+
4
+ #if UNITY_IOS
5
+ using UnityEditor.iOS.Xcode;
6
+ using System.IO;
7
+ #endif
8
+
9
+ public sealed class BrainBitPostProcessor
10
+ {
11
+ [PostProcessBuild(1)]
12
+ public static void OnPostProcessBuild(BuildTarget target, string path)
13
+ {
14
+ #if UNITY_IOS
15
+ if (target == BuildTarget.iOS)
16
+ {
17
+ var infoPlistPath = Path.Combine(path, "Info.plist");
18
+ var infoPlist = new PlistDocument();
19
+ infoPlist.ReadFromFile(infoPlistPath);
20
+
21
+ PlistElementDict dict = infoPlist.root.AsDict();
22
+ dict.SetString("NSBluetoothAlwaysUsageDescription",
23
+ "App requires access to Bluetooth to allow you connect to device");
24
+ dict.SetString("NSBluetoothPeripheralUsageDescription",
25
+ "App uses Bluetooth to connect with your Brainbit device");
26
+
27
+ infoPlist.WriteToFile(infoPlistPath);
28
+ }
29
+ #endif
30
+ }
31
+ }
@@ -0,0 +1,11 @@
1
+ fileFormatVersion: 2
2
+ guid: 85dc6e8141c759241a61fb2a35a4d67e
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant:
package/Editor.meta ADDED
@@ -0,0 +1,8 @@
1
+ fileFormatVersion: 2
2
+ guid: 0224d21d402208c499ca9a5d1432514e
3
+ folderAsset: yes
4
+ DefaultImporter:
5
+ externalObjects: {}
6
+ userData:
7
+ assetBundleName:
8
+ assetBundleVariant:
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 NCU WMLAB
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/LICENSE.meta ADDED
@@ -0,0 +1,7 @@
1
+ fileFormatVersion: 2
2
+ guid: a696d53fc286ac54e8d916f64ccf6535
3
+ DefaultImporter:
4
+ externalObjects: {}
5
+ userData:
6
+ assetBundleName:
7
+ assetBundleVariant:
package/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # LabFrame 2023 - BrainBit Plugin
2
+
3
+ 此套件為 LabFrame 2023 專用的 BrainBit 設備插件,用於連接與管理 BrainBit 腦波儀。
4
+
5
+ ## 支援功能
6
+ 1. **設備連線管理:** 自動搜尋並手動觸發連接藍牙 BrainBit 設備。
7
+ 2. **EEG 腦波數據收集:** 自動收集四個通道 (T3, T4, O1, O2) 的腦波數據。
8
+ 3. **即時阻抗檢查:** 確認電極與頭皮的接觸阻抗值是否過高 (> 200,000Ω)。
9
+ 4. **多階段資料分流儲存:** 收集期間可動態切換儲存 Tag(依照遊戲階段無縫寫入不同檔案)。
10
+
11
+ ---
12
+
13
+ ## 基本使用方式
14
+
15
+ ### 1. 手動觸發設備連線
16
+ 預設啟動遊戲時**不會**自動連線,需在適當時間點(例如點擊按鈕或進入準備階段時)透過程式碼手動掃描並連線。
17
+ ```csharp
18
+ BrainBitManager.Instance.ManualConnect();
19
+ ```
20
+
21
+ ---
22
+
23
+ ### 2. 關於 EEG (持續腦波) 的收集方式
24
+
25
+ #### 👉 開始 / 停止收集
26
+ 開始收集時,可傳入對應的遊戲階段 Tag:
27
+ ```csharp
28
+ // "Intro_Phase" 將作為存檔檔名的後綴
29
+ BrainBitManager.Instance.StartEEGStream(true, "Intro_Phase");
30
+
31
+ // 停止收集
32
+ BrainBitManager.Instance.StopEEGStream();
33
+ ```
34
+
35
+ #### 👉 動態無縫切換儲存階段 (Tag)
36
+ 當遊戲進入下一個階段時,你**不需**停止收集,只需修改 Tag:
37
+ ```csharp
38
+ if (BrainBitManager.Instance.IsStreamingEEG)
39
+ {
40
+ // 下一毫秒收集的封包,就會被寫進 "Tutorial_Phase" 的檔案中
41
+ BrainBitManager.Instance.SetEEGTag("Tutorial_Phase");
42
+ }
43
+ ```
44
+
45
+ #### 👉 取得最新的 EEG 值
46
+ 若需要在遊戲邏輯中使用即時腦波:
47
+ ```csharp
48
+ var eegData = BrainBitManager.Instance.GetLatestEEGData();
49
+ if (eegData != null)
50
+ {
51
+ Debug.Log($"T3:{eegData.T3} | T4:{eegData.T4} | O1:{eegData.O1} | O2:{eegData.O2}");
52
+ }
53
+ ```
54
+
55
+ ---
56
+
57
+ ### 3. 關於 Impedance (阻抗) 的檢測方式
58
+
59
+ > 阻抗 (Impedance) 資料流與 EEG (腦波) 資料流是**獨立**的。通常在實驗或遊戲開始前,先開啟阻抗檢測確認配戴良好,然後停止阻抗檢測,再開始 EEG 腦波收集。
60
+
61
+ #### 👉 開啟 / 停止阻抗檢測與修改 Tag (與 EEG 邏輯完全相同)
62
+ ```csharp
63
+ // 啟動阻抗數據流並儲存至 "Preparation_Impedance"
64
+ BrainBitManager.Instance.StartImpedanceStream(true, "Preparation_Impedance");
65
+
66
+ // 也可呼叫這個無縫切換 Tag
67
+ BrainBitManager.Instance.SetImpedanceTag("Another_Phase_Impedance");
68
+
69
+ // 停止阻抗流
70
+ BrainBitManager.Instance.StopImpedanceStream();
71
+ ```
72
+
73
+ #### 👉 判斷各通道阻抗是否過高
74
+ 在檢測狀態下,呼叫以下的 API 一次取得四個通道的數值與警示結果:
75
+ ```csharp
76
+ var data = BrainBitManager.Instance.GetLatestImpedanceData();
77
+ if (data != null)
78
+ {
79
+ // 直接一次性取得所有數值與 Boolean (檢查是否大於 200,000)
80
+ var (t3_val, t3_high, t4_val, t4_high, o1_val, o1_high, o2_val, o2_high) = data.GetImpedanceValues();
81
+
82
+ if (t3_high)
83
+ Debug.LogWarning($"T3 沒接好,當前阻抗值: {t3_val}");
84
+ if (t4_high)
85
+ Debug.LogWarning($"T4 沒接好,當前阻抗值: {t4_val}");
86
+ }
87
+ ```
88
+
89
+ ---
90
+
91
+ ## 平台需求與建置 (Android / iOS)
92
+
93
+ 本套件已內附相關處理機制:
94
+ - **Android:** 必須包含定位 (`ACCESS_FINE_LOCATION`) 與藍牙及掃描 (`BLUETOOTH_CONNECT`, `BLUETOOTH_SCAN` 等) 權限。相關權限已配置在 `Runtime/Plugins/Android/AndroidManifest.xml` 中,Unity 建置時將自動打包。
95
+ - **iOS:** 打包後製腳本 `BrainBitPostProcess.cs` 會自動為 `Info.plist` 加入必要藍牙權限 (`NSBluetoothAlwaysUsageDescription`)。
package/README.md.meta ADDED
@@ -0,0 +1,7 @@
1
+ fileFormatVersion: 2
2
+ guid: 188932e280f293942935c05a53ba02ce
3
+ TextScriptImporter:
4
+ externalObjects: {}
5
+ userData:
6
+ assetBundleName:
7
+ assetBundleVariant:
@@ -0,0 +1,15 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
+ xmlns:tools="http://schemas.android.com/tools">
3
+
4
+ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
5
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
6
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
7
+ <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
8
+ <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
9
+ <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
10
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
11
+ <uses-permission android:name="android.permission.BLUETOOTH_SCAN"
12
+ tools:targetApi="31"
13
+ android:usesPermissionFlags="neverForLocation" />
14
+
15
+ </manifest>
@@ -0,0 +1,7 @@
1
+ fileFormatVersion: 2
2
+ guid: 2d795f31e57226543990547310ab486c
3
+ TextScriptImporter:
4
+ externalObjects: {}
5
+ userData:
6
+ assetBundleName:
7
+ assetBundleVariant:
@@ -0,0 +1,8 @@
1
+ fileFormatVersion: 2
2
+ guid: 331151d800deb7545bc70319e8a4356c
3
+ folderAsset: yes
4
+ DefaultImporter:
5
+ externalObjects: {}
6
+ userData:
7
+ assetBundleName:
8
+ assetBundleVariant:
@@ -0,0 +1,8 @@
1
+ fileFormatVersion: 2
2
+ guid: 3dfb332a598e99f4caa78a38877c63f3
3
+ folderAsset: yes
4
+ DefaultImporter:
5
+ externalObjects: {}
6
+ userData:
7
+ assetBundleName:
8
+ assetBundleVariant:
package/Runtime.meta ADDED
@@ -0,0 +1,8 @@
1
+ fileFormatVersion: 2
2
+ guid: 6db1f38d3f0bbe24dace8a0e72a63a61
3
+ folderAsset: yes
4
+ DefaultImporter:
5
+ externalObjects: {}
6
+ userData:
7
+ assetBundleName:
8
+ assetBundleVariant:
@@ -0,0 +1,187 @@
1
+ using UnityEngine;
2
+ using System.Collections;
3
+ using System.Collections.Generic;
4
+ using LabFrame2023;
5
+ using System;
6
+
7
+ /// <summary>
8
+ /// BrainBit EEG 數據
9
+ /// </summary>
10
+ [Serializable]
11
+ public class BrainBit_EEGData : LabDataBase
12
+ {
13
+ /// <summary>
14
+ /// T3 通道 EEG 值
15
+ /// </summary>
16
+ public double T3;
17
+
18
+ /// <summary>
19
+ /// T4 通道 EEG 值
20
+ /// </summary>
21
+ public double T4;
22
+
23
+ /// <summary>
24
+ /// O1 通道 EEG 值
25
+ /// </summary>
26
+ public double O1;
27
+
28
+ /// <summary>
29
+ /// O2 通道 EEG 值
30
+ /// </summary>
31
+ public double O2;
32
+
33
+ /// <summary>
34
+ /// 所有 EEG 值的列表 (T3, T4, O1, O2)
35
+ /// </summary>
36
+ public List<double> EEGValues => new List<double>() { T3, T4, O1, O2 };
37
+
38
+ public BrainBit_EEGData() : base() { }
39
+
40
+ public BrainBit_EEGData(double t3, double t4, double o1, double o2) : base()
41
+ {
42
+ T3 = t3;
43
+ T4 = t4;
44
+ O1 = o1;
45
+ O2 = o2;
46
+ }
47
+
48
+ public override string ToString()
49
+ {
50
+ return $"T3: {T3:N4} | T4: {T4:N4} | O1: {O1:N4} | O2: {O2:N4}";
51
+ }
52
+ }
53
+
54
+ /// <summary>
55
+ /// BrainBit 阻抗數據
56
+ /// </summary>
57
+ [Serializable]
58
+ public class BrainBit_ImpedanceData : LabDataBase
59
+ {
60
+ /// <summary>
61
+ /// T3 通道阻抗值
62
+ /// </summary>
63
+ public double T3;
64
+
65
+ /// <summary>
66
+ /// T4 通道阻抗值
67
+ /// </summary>
68
+ public double T4;
69
+
70
+ /// <summary>
71
+ /// O1 通道阻抗值
72
+ /// </summary>
73
+ public double O1;
74
+
75
+ /// <summary>
76
+ /// O2 通道阻抗值
77
+ /// </summary>
78
+ public double O2;
79
+
80
+ /// <summary>
81
+ /// 所有阻抗值的列表 (T3, T4, O1, O2)
82
+ /// </summary>
83
+ public List<double> ImpedanceValues => new List<double>() { T3, T4, O1, O2 };
84
+
85
+ public BrainBit_ImpedanceData() : base() { }
86
+
87
+ public BrainBit_ImpedanceData(double t3, double t4, double o1, double o2) : base()
88
+ {
89
+ T3 = t3;
90
+ T4 = t4;
91
+ O1 = o1;
92
+ O2 = o2;
93
+ }
94
+
95
+ /// <summary>
96
+ /// 檢查所有通道阻抗是否皆正常(小於閾值)
97
+ /// </summary>
98
+ public bool IsImpedanceGood
99
+ {
100
+ get
101
+ {
102
+ double threshold = 200000.0;
103
+ return T3 < threshold && T4 < threshold && O1 < threshold && O2 < threshold;
104
+ }
105
+ }
106
+
107
+ /// <summary>
108
+ /// 一次取得各通道數值以及各通道是否阻抗過高(大於200,000)
109
+ /// </summary>
110
+ /// <returns>(T3數值, T3太高?, T4數值, T4太高?, O1數值, O1太高?, O2數值, O2太高?)</returns>
111
+ public (double t3_val, bool t3_high, double t4_val, bool t4_high, double o1_val, bool o1_high, double o2_val, bool o2_high) GetImpedanceValues()
112
+ {
113
+ double threshold = 200000.0;
114
+ return (
115
+ T3, T3 > threshold,
116
+ T4, T4 > threshold,
117
+ O1, O1 > threshold,
118
+ O2, O2 > threshold
119
+ );
120
+ }
121
+
122
+ /// <summary>
123
+ /// 獲取阻抗狀態描述
124
+ /// </summary>
125
+ /// <param name="threshold">阻抗警告閾值</param>
126
+ /// <returns>阻抗狀態字符串</returns>
127
+ public string GetImpedanceStatus(double threshold = 200000.0)
128
+ {
129
+ var status = new List<string>();
130
+ if (T3 > threshold) status.Add("T3");
131
+ if (T4 > threshold) status.Add("T4");
132
+ if (O1 > threshold) status.Add("O1");
133
+ if (O2 > threshold) status.Add("O2");
134
+
135
+ if (status.Count == 0)
136
+ return "All channels good";
137
+ else
138
+ return $"High impedance: {string.Join(", ", status)}";
139
+ }
140
+
141
+ public override string ToString()
142
+ {
143
+ return $"T3: {T3:N0}Ω | T4: {T4:N0}Ω | O1: {O1:N0}Ω | O2: {O2:N0}Ω";
144
+ }
145
+ }
146
+
147
+ /// <summary>
148
+ /// BrainBit 連接狀態數據
149
+ /// </summary>
150
+ [Serializable]
151
+ public class BrainBit_ConnectionData : LabDataBase
152
+ {
153
+ /// <summary>
154
+ /// 連接狀態
155
+ /// </summary>
156
+ public bool IsConnected;
157
+
158
+ /// <summary>
159
+ /// 設備名稱
160
+ /// </summary>
161
+ public string DeviceName;
162
+
163
+ /// <summary>
164
+ /// 設備地址
165
+ /// </summary>
166
+ public string DeviceAddress;
167
+
168
+ /// <summary>
169
+ /// 連接/斷開時間戳
170
+ /// </summary>
171
+ public string EventType; // "Connected" or "Disconnected"
172
+
173
+ public BrainBit_ConnectionData() : base() { }
174
+
175
+ public BrainBit_ConnectionData(bool isConnected, string deviceName, string deviceAddress, string eventType) : base()
176
+ {
177
+ IsConnected = isConnected;
178
+ DeviceName = deviceName;
179
+ DeviceAddress = deviceAddress;
180
+ EventType = eventType;
181
+ }
182
+
183
+ public override string ToString()
184
+ {
185
+ return $"{EventType}: {DeviceName} ({DeviceAddress}) - Connected: {IsConnected}";
186
+ }
187
+ }
@@ -0,0 +1,11 @@
1
+ fileFormatVersion: 2
2
+ guid: 9ee0213436b29314eb44951c6825aa25
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant: