@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.
- package/.github/workflows/pio-publish.yml +32 -0
- package/.github/workflows/tg-send.yml +35 -0
- package/README.md +45 -8
- package/README_EN.md +46 -0
- package/ble.js +30 -15
- package/ble.min.js +1 -1
- package/package.json +6 -6
- package/test/script.js +2 -2
|
@@ -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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
166
|
+
return this.sendBin((new TextEncoder()).encode(text), fast);
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
async
|
|
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
|
-
|
|
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
|
-
|
|
179
|
-
|
|
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
|
-
|
|
186
|
+
try {
|
|
187
|
+
for (let i = 0; i < bytes.length; i += size) {
|
|
188
|
+
if (!this.opened() || !this._rx) return false;
|
|
182
189
|
|
|
183
|
-
|
|
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
|
-
|
|
190
|
-
|
|
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){
|
|
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.
|
|
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.
|
|
17
|
+
"css-loader": "^7.1.5",
|
|
18
18
|
"style-loader": "^4.0.0",
|
|
19
|
-
"webpack": "^5.
|
|
20
|
-
"webpack-cli": "^7.
|
|
21
|
-
"webpack-dev-server": "^
|
|
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.
|
|
24
|
+
"@alexgyver/utils": "^1.2.18"
|
|
25
25
|
},
|
|
26
26
|
"author": "AlexGyver <alex@alexgyver.ru>",
|
|
27
27
|
"license": "MIT"
|
package/test/script.js
CHANGED