@alexgyver/ble 1.0.8 → 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.
@@ -0,0 +1,32 @@
1
+
2
+ name: Publish to PlatformIO Registry
3
+
4
+ on:
5
+ release:
6
+ types: [published]
7
+
8
+ jobs:
9
+ publish:
10
+ runs-on: ubuntu-latest
11
+
12
+ steps:
13
+ - name: Checkout release tag
14
+ uses: actions/checkout@v4
15
+ with:
16
+ ref: ${{ github.event.release.tag_name }}
17
+
18
+ - name: Set up Python
19
+ uses: actions/setup-python@v5
20
+ with:
21
+ python-version: "3.x"
22
+
23
+ - name: Install PlatformIO
24
+ run: pip install -U platformio
25
+
26
+ - name: Check package
27
+ run: pio pkg pack
28
+
29
+ - name: Publish to PlatformIO Registry
30
+ env:
31
+ PLATFORMIO_AUTH_TOKEN: ${{ secrets.PLATFORMIO_AUTH_TOKEN }}
32
+ run: pio pkg publish --owner gyverlibs --non-interactive
@@ -0,0 +1,35 @@
1
+
2
+ name: Telegram Message
3
+ on:
4
+ release:
5
+ types: [published]
6
+ jobs:
7
+ build:
8
+ name: Send Message
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - name: Check if major or minor release
12
+ id: semver
13
+ shell: bash
14
+ run: |
15
+ TAG="${{ github.event.release.tag_name }}"
16
+ VERSION="${TAG#v}"
17
+ PATCH="$(echo "$VERSION" | cut -d. -f3)"
18
+
19
+ if [[ "$PATCH" == "0" ]]; then
20
+ echo "should_send=true" >> "$GITHUB_OUTPUT"
21
+ else
22
+ echo "should_send=false" >> "$GITHUB_OUTPUT"
23
+ fi
24
+
25
+ - name: send telegram message on release
26
+ if: steps.semver.outputs.should_send == 'true'
27
+ uses: appleboy/telegram-action@master
28
+ with:
29
+ to: ${{ secrets.TELEGRAM_TO }}
30
+ token: ${{ secrets.TELEGRAM_TOKEN }}
31
+ disable_web_page_preview: true
32
+ message: |
33
+ ${{ github.event.repository.name }} ${{ github.event.release.tag_name }}
34
+ ${{ github.event.release.body }}
35
+ https://github.com/${{ github.repository }}
package/README.md CHANGED
@@ -1,8 +1,9 @@
1
1
  # BLE.js
2
2
  Обёртка на Web Bluetooth API
3
3
  - Автоматическое переподключение
4
- - Буферизация отправки
5
- - Буферизация приёма, разделение текста по разделителю
4
+ - Последовательная отправка без пересечения записей
5
+ - Настраиваемое дробление больших отправок
6
+ - Отдельная атомарная отправка одного BLE frame
6
7
 
7
8
  [demo](https://gyverlibs.github.io/BLE.js/test/)
8
9
 
@@ -14,12 +15,13 @@
14
15
  ```js
15
16
  constructor(params = {});
16
17
  config(params = {});
17
- // eol: /\r?\n/
18
18
  // serviceUUID: '0000ffe0-0000-1000-8000-00805f9b34fb'
19
- // charxUUID: '0000ffe1-0000-1000-8000-00805f9b34fb'
19
+ // rxUUID: '0000ffe1-0000-1000-8000-00805f9b34fb'
20
+ // txUUID: '0000ffe2-0000-1000-8000-00805f9b34fb'
20
21
  // auto_open: false
21
- // max_tx: 20
22
22
  // reconnect: 1000
23
+ // chunkSize: 500
24
+ // chunkDelay: 0
23
25
 
24
26
  onbin(b);
25
27
  ontext(t);
@@ -39,6 +41,41 @@ select();
39
41
  open();
40
42
  close();
41
43
 
42
- sendBin(data);
43
- sendText(text);
44
- ```
44
+ sendBin(data, fast = true, chunkSize = config.chunkSize);
45
+ sendFrame(data, fast = true);
46
+ sendText(text, fast = true);
47
+ ```
48
+
49
+ Все методы отправки возвращают `Promise<boolean>`.
50
+
51
+ ## Дробление отправки
52
+
53
+ `sendBin()` делит данные на несколько BLE characteristic writes по `chunkSize`. Задержка между записями задаётся через `chunkDelay`:
54
+
55
+ ```js
56
+ const ble = new BLEJS({
57
+ chunkSize: 100,
58
+ chunkDelay: 5,
59
+ });
60
+
61
+ await ble.sendBin(data);
62
+ ```
63
+
64
+ `chunkSize <= 0` отключает дробление и выполняет одну characteristic write:
65
+
66
+ ```js
67
+ await ble.sendBin(data, true, 0);
68
+ ```
69
+
70
+ Для протоколов, где одна BLE write является одним транспортным frame, следует использовать `sendFrame()`:
71
+
72
+ ```js
73
+ const ok = await ble.sendFrame(frame);
74
+ ```
75
+
76
+ `sendFrame()` никогда не делит данные. Если characteristic или устройство не принимает запись такого размера, метод вызовет `onerror` и вернёт `false`.
77
+
78
+ Параметр `fast` выбирает способ записи:
79
+
80
+ - `true` — `writeValueWithoutResponse`
81
+ - `false` — `writeValueWithResponse`
package/README_EN.md ADDED
@@ -0,0 +1,46 @@
1
+ This is an automatic translation and may be incorrect in some places. See the source README and examples for authoritative information.
2
+
3
+ # BLE.js
4
+ Wrapper on Web Bluetooth API
5
+ - Automatic reconnection
6
+ - Shipment buffering
7
+ - Reception buffering, separation of text by separator
8
+
9
+ [demo](https://gyverlibs.github.io/BLE.js/test/)
10
+
11
+ > **Browser**: https://gyverlibs.github.io/BLE.js/BLE.min.js
12
+
13
+ > **Node**: npm i @alexgyver/ble
14
+
15
+ ## Doc.
16
+ ```js
17
+ constructor(params = {});
18
+ config(params = {});
19
+ // eol: /\r?\n/
20
+ // serviceUUID: '0000ffe0-0000-1000-8000-00805f9b34fb'
21
+ // charxUUID: '0000ffe1-0000-1000-8000-00805f9b34fb'
22
+ // auto_open: false
23
+ // max_tx: 20
24
+ // reconnect: 1000
25
+
26
+ onbin(b);
27
+ ontext(t);
28
+
29
+ onopen():
30
+ onclose():
31
+ onchange(s):
32
+ onselect(name);
33
+ onerror(e);
34
+
35
+ static supported();
36
+ opened();
37
+ selected();
38
+ getName();
39
+
40
+ select();
41
+ open();
42
+ close();
43
+
44
+ sendBin(data);
45
+ sendText(text);
46
+ ```
package/ble.js CHANGED
@@ -163,36 +163,51 @@ export default class BLEJS {
163
163
  }
164
164
 
165
165
  async sendText(text, fast = true) {
166
- await this.sendBin((new TextEncoder()).encode(text), fast);
166
+ return this.sendBin((new TextEncoder()).encode(text), fast);
167
167
  }
168
168
 
169
- async sendBin(data, fast = true) {
169
+ async sendFrame(data, fast = true) {
170
+ return this.sendBin(data, fast, 0);
171
+ }
172
+
173
+ async sendBin(data, fast = true, chunkSize = this.cfg.chunkSize) {
170
174
  if (!this.opened() || !this._rx) return false;
171
175
 
172
- return this._sender.runNothrow(async () => {
176
+ const result = await this._sender.runNothrow(async () => {
173
177
  if (!this.opened() || !this._rx) return false;
174
178
 
175
- const chunkSize = this.cfg.chunkSize;
176
179
  const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);
180
+ if (!bytes.length) return true;
177
181
 
178
- for (let i = 0; i < bytes.length; i += chunkSize) {
179
- if (!this.opened() || !this._rx) return false;
182
+ let size = Number(chunkSize);
183
+ if (!Number.isFinite(size) || size <= 0) size = bytes.length;
184
+ else size = Math.max(1, Math.floor(size));
180
185
 
181
- const chunk = bytes.slice(i, i + chunkSize);
186
+ try {
187
+ for (let i = 0; i < bytes.length; i += size) {
188
+ if (!this.opened() || !this._rx) return false;
182
189
 
183
- if (fast) {
184
- await this._rx.writeValueWithoutResponse(chunk);
185
- } else {
186
- await this._rx.writeValueWithResponse(chunk);
187
- }
190
+ const chunk = bytes.subarray(i, i + size);
188
191
 
189
- if (this.cfg.chunkDelay > 0) {
190
- await sleep(this.cfg.chunkDelay);
192
+ if (fast) {
193
+ await this._rx.writeValueWithoutResponse(chunk);
194
+ } else {
195
+ await this._rx.writeValueWithResponse(chunk);
196
+ }
197
+
198
+ if (i + size < bytes.length && this.cfg.chunkDelay > 0) {
199
+ await sleep(this.cfg.chunkDelay);
200
+ }
191
201
  }
202
+ } catch (e) {
203
+ this._error(e);
204
+ return false;
192
205
  }
193
206
 
194
207
  return true;
195
208
  });
209
+
210
+ return result === true;
196
211
  }
197
212
 
198
213
  _state = BLEJS.State.Closed;
@@ -271,4 +286,4 @@ export default class BLEJS {
271
286
  break;
272
287
  }
273
288
  }
274
- }
289
+ }
package/ble.min.js CHANGED
@@ -1 +1 @@
1
- const t=t=>new Promise(e=>setTimeout(e,t));class e{constructor(){this._session=0,this.reset()}run(t){const e=this._session,s=this._queue.then(()=>{if(e!==this._session)throw new Error("Queue empty");return t()});return this._queue=s.catch(()=>{}),s}runNothrow(t){return this.run(t).catch(()=>null)}reset(){this._session++,this._queue=Promise.resolve()}}class s{static State={Closed:"closed",Opening:"opening",Open:"open",Closing:"closing"};ontext=null;onbin(t){}onopen(){}onclose(){}onchange(t){}onselect(t){}onerror(t){}constructor(t={}){this.cfg={serviceUUID:"0000ffe0-0000-1000-8000-00805f9b34fb",rxUUID:"0000ffe1-0000-1000-8000-00805f9b34fb",txUUID:"0000ffe2-0000-1000-8000-00805f9b34fb",auto_open:!1,reconnect:1e3,chunkSize:500,chunkDelay:0,...t}}config(t={}){this.cfg={...this.cfg,...t}}static supported(){return"bluetooth"in navigator}opened(){return this._state===s.State.Open}selected(){return!!this._device}getName(){return this._device?this._device.name:"None"}async select(){try{await this.close(),this._device&&this._device.removeEventListener("gattserverdisconnected",this._disconnect_h),this._device=await navigator.bluetooth.requestDevice({filters:[{services:[this.cfg.serviceUUID]}],optionalServices:[this.cfg.serviceUUID]}),this._device.addEventListener("gattserverdisconnected",this._disconnect_h)}catch(t){this._error(t),this._device=null}return this.onselect(this.getName()),this.cfg.auto_open&&this.open(),this.selected()}async open(){return this._lifecycle.runNothrow(async()=>this._device?!!this.opened()||(this.cfg.reconnect&&(this.retry=!0),await this._open(),this.opened()):(this._error("No device"),!1))}async _open(){if(this._state===s.State.Closed){this._change(s.State.Opening);try{const t=await this._device.gatt.connect(),e=await t.getPrimaryService(this.cfg.serviceUUID);this._rx=await e.getCharacteristic(this.cfg.rxUUID),this._tx=await e.getCharacteristic(this.cfg.txUUID),await this._tx.startNotifications(),this._tx.addEventListener("characteristicvaluechanged",this._data_h),this._sender.reset(),this._change(s.State.Open)}catch(e){this._error(e),this._sender.reset(),this._change(s.State.Closed),this.retry&&t(this.cfg.reconnect).then(()=>{this._lifecycle.runNothrow(()=>this._open())})}}}async close(){return this._lifecycle.runNothrow(async()=>(this.retry=!1,this._sender.reset(),this._state!==s.State.Closed&&await this._close(),!0))}async _close(){if(this._state!==s.State.Closed){this._change(s.State.Closing);try{this._device?.gatt?.connected?this._device.gatt.disconnect():await this._disconnect()}catch(t){this._error(t),await this._disconnect()}}}async sendText(t,e=!0){await this.sendBin((new TextEncoder).encode(t),e)}async sendBin(e,s=!0){return!(!this.opened()||!this._rx)&&this._sender.runNothrow(async()=>{if(!this.opened()||!this._rx)return!1;const i=this.cfg.chunkSize,n=e instanceof Uint8Array?e:new Uint8Array(e);for(let e=0;e<n.length;e+=i){if(!this.opened()||!this._rx)return!1;const c=n.slice(e,e+i);s?await this._rx.writeValueWithoutResponse(c):await this._rx.writeValueWithResponse(c),this.cfg.chunkDelay>0&&await t(this.cfg.chunkDelay)}return!0})}_state=s.State.Closed;_device=null;_rx=null;_tx=null;retry=!1;_sender=new e;_lifecycle=new e;async _disconnect(e){if(this._sender.reset(),this._tx)try{this._tx.removeEventListener("characteristicvaluechanged",this._data_h)}catch(e){}this._rx=null,this._tx=null,this._change(s.State.Closed),this.retry&&t(this.cfg.reconnect).then(()=>{this._lifecycle.runNothrow(()=>this._open())}),await t(50)}_disconnect_h=this._disconnect.bind(this);_data(t){try{const e=t.target.value,s=new Uint8Array(e.buffer,e.byteOffset,e.byteLength);this.onbin(s),this.ontext&&this.ontext((new TextDecoder).decode(s))}catch(t){this._error(t)}}_data_h=this._data.bind(this);_error(t){this.onerror("[BLE] "+t)}_change(t){if(this._state!==t)switch(this._state=t,this.onchange(t),t){case s.State.Open:this.onopen();break;case s.State.Closed:this.onclose()}}}export{s as default};
1
+ const t=t=>new Promise(e=>setTimeout(e,t));class e{constructor(){this._session=0,this.reset()}run(t){const e=this._session,s=this._queue.then(()=>{if(e!==this._session)throw new Error("Queue empty");return t()});return this._queue=s.catch(()=>{}),s}runNothrow(t){return this.run(t).catch(()=>null)}reset(){this._session++,this._queue=Promise.resolve()}}class s{static State={Closed:"closed",Opening:"opening",Open:"open",Closing:"closing"};ontext=null;onbin(t){}onopen(){}onclose(){}onchange(t){}onselect(t){}onerror(t){}constructor(t={}){this.cfg={serviceUUID:"0000ffe0-0000-1000-8000-00805f9b34fb",rxUUID:"0000ffe1-0000-1000-8000-00805f9b34fb",txUUID:"0000ffe2-0000-1000-8000-00805f9b34fb",auto_open:!1,reconnect:1e3,chunkSize:500,chunkDelay:0,...t}}config(t={}){this.cfg={...this.cfg,...t}}static supported(){return"bluetooth"in navigator}opened(){return this._state===s.State.Open}selected(){return!!this._device}getName(){return this._device?this._device.name:"None"}async select(){try{await this.close(),this._device&&this._device.removeEventListener("gattserverdisconnected",this._disconnect_h),this._device=await navigator.bluetooth.requestDevice({filters:[{services:[this.cfg.serviceUUID]}],optionalServices:[this.cfg.serviceUUID]}),this._device.addEventListener("gattserverdisconnected",this._disconnect_h)}catch(t){this._error(t),this._device=null}return this.onselect(this.getName()),this.cfg.auto_open&&this.open(),this.selected()}async open(){return this._lifecycle.runNothrow(async()=>this._device?!!this.opened()||(this.cfg.reconnect&&(this.retry=!0),await this._open(),this.opened()):(this._error("No device"),!1))}async _open(){if(this._state===s.State.Closed){this._change(s.State.Opening);try{const t=await this._device.gatt.connect(),e=await t.getPrimaryService(this.cfg.serviceUUID);this._rx=await e.getCharacteristic(this.cfg.rxUUID),this._tx=await e.getCharacteristic(this.cfg.txUUID),await this._tx.startNotifications(),this._tx.addEventListener("characteristicvaluechanged",this._data_h),this._sender.reset(),this._change(s.State.Open)}catch(e){this._error(e),this._sender.reset(),this._change(s.State.Closed),this.retry&&t(this.cfg.reconnect).then(()=>{this._lifecycle.runNothrow(()=>this._open())})}}}async close(){return this._lifecycle.runNothrow(async()=>(this.retry=!1,this._sender.reset(),this._state!==s.State.Closed&&await this._close(),!0))}async _close(){if(this._state!==s.State.Closed){this._change(s.State.Closing);try{this._device?.gatt?.connected?this._device.gatt.disconnect():await this._disconnect()}catch(t){this._error(t),await this._disconnect()}}}async sendText(t,e=!0){return this.sendBin((new TextEncoder).encode(t),e)}async sendFrame(t,e=!0){return this.sendBin(t,e,0)}async sendBin(e,s=!0,i=this.cfg.chunkSize){return!(!this.opened()||!this._rx)&&!0===await this._sender.runNothrow(async()=>{if(!this.opened()||!this._rx)return!1;const n=e instanceof Uint8Array?e:new Uint8Array(e);if(!n.length)return!0;let r=Number(i);r=!Number.isFinite(r)||r<=0?n.length:Math.max(1,Math.floor(r));try{for(let e=0;e<n.length;e+=r){if(!this.opened()||!this._rx)return!1;const i=n.subarray(e,e+r);s?await this._rx.writeValueWithoutResponse(i):await this._rx.writeValueWithResponse(i),e+r<n.length&&this.cfg.chunkDelay>0&&await t(this.cfg.chunkDelay)}}catch(t){return this._error(t),!1}return!0})}_state=s.State.Closed;_device=null;_rx=null;_tx=null;retry=!1;_sender=new e;_lifecycle=new e;async _disconnect(e){if(this._sender.reset(),this._tx)try{this._tx.removeEventListener("characteristicvaluechanged",this._data_h)}catch(e){}this._rx=null,this._tx=null,this._change(s.State.Closed),this.retry&&t(this.cfg.reconnect).then(()=>{this._lifecycle.runNothrow(()=>this._open())}),await t(50)}_disconnect_h=this._disconnect.bind(this);_data(t){try{const e=t.target.value,s=new Uint8Array(e.buffer,e.byteOffset,e.byteLength);this.onbin(s),this.ontext&&this.ontext((new TextDecoder).decode(s))}catch(t){this._error(t)}}_data_h=this._data.bind(this);_error(t){this.onerror("[BLE] "+t)}_change(t){if(this._state!==t)switch(this._state=t,this.onchange(t),t){case s.State.Open:this.onopen();break;case s.State.Closed:this.onclose()}}}export{s as default};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alexgyver/ble",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "Web BLE Serial",
5
5
  "main": "./ble.js",
6
6
  "module": "./ble.js",
@@ -14,14 +14,14 @@
14
14
  "url": "git+https://github.com/GyverLibs/ble.js.git"
15
15
  },
16
16
  "devDependencies": {
17
- "css-loader": "^7.1.4",
17
+ "css-loader": "^7.1.5",
18
18
  "style-loader": "^4.0.0",
19
- "webpack": "^5.106.2",
20
- "webpack-cli": "^7.0.2",
21
- "webpack-dev-server": "^5.2.4"
19
+ "webpack": "^5.110.2",
20
+ "webpack-cli": "^7.2.3",
21
+ "webpack-dev-server": "^6.0.0"
22
22
  },
23
23
  "dependencies": {
24
- "@alexgyver/utils": "^1.2.8"
24
+ "@alexgyver/utils": "^1.2.18"
25
25
  },
26
26
  "author": "AlexGyver <alex@alexgyver.ru>",
27
27
  "license": "MIT"
package/test/script.js CHANGED
@@ -1,5 +1,5 @@
1
- // import BLEJS from "https://gyverlibs.github.io/BLE.js/ble.min.js";
2
- import BLEJS from "../ble.js";
1
+ import BLEJS from "https://gyverlibs.github.io/BLE.js/ble.min.js";
2
+ // import BLEJS from "../ble.js";
3
3
 
4
4
  let i = 0;
5
5
  let ble = new BLEJS({ auto_open: true });