@adminide-stack/yantra-mobile 12.0.131-alpha.1 → 12.0.131
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/lib/features/devices/bleTransport.js +54 -20
- package/lib/features/devices/bleTransport.js.map +1 -1
- package/lib/features/devices/brands/channel.js +19 -0
- package/lib/features/devices/brands/channel.js.map +1 -0
- package/lib/features/devices/brands/detect.js +318 -0
- package/lib/features/devices/brands/detect.js.map +1 -0
- package/lib/features/devices/brands/frames.js +103 -0
- package/lib/features/devices/brands/frames.js.map +1 -0
- package/lib/features/devices/brands/index.js +30 -0
- package/lib/features/devices/brands/index.js.map +1 -0
- package/lib/features/devices/brands/qnScale.js +98 -0
- package/lib/features/devices/brands/qnScale.js.map +1 -0
- package/lib/features/devices/brands/renphoBloodPressure.js +92 -0
- package/lib/features/devices/brands/renphoBloodPressure.js.map +1 -0
- package/lib/features/devices/brands/renphoFoodScale.js +42 -0
- package/lib/features/devices/brands/renphoFoodScale.js.map +1 -0
- package/lib/features/devices/brands/renphoJumpRope.js +71 -0
- package/lib/features/devices/brands/renphoJumpRope.js.map +1 -0
- package/lib/features/devices/brands/renphoRing.js +82 -0
- package/lib/features/devices/brands/renphoRing.js.map +1 -0
- package/lib/features/devices/brands/renphoScale.js +181 -0
- package/lib/features/devices/brands/renphoScale.js.map +1 -0
- package/lib/features/devices/brands/renphoTape.js +68 -0
- package/lib/features/devices/brands/renphoTape.js.map +1 -0
- package/lib/features/devices/brands/uuids.js +38 -0
- package/lib/features/devices/brands/uuids.js.map +1 -0
- package/lib/features/devices/catalog.js +40 -0
- package/lib/features/devices/catalog.js.map +1 -1
- package/lib/features/devices/deviceStore.js +5 -2
- package/lib/features/devices/deviceStore.js.map +1 -1
- package/lib/features/devices/drivers.js +5 -1
- package/lib/features/devices/drivers.js.map +1 -1
- package/lib/features/devices/gatt/ftms.js +129 -0
- package/lib/features/devices/gatt/ftms.js.map +1 -0
- package/lib/features/devices/gatt/uuids.js +8 -2
- package/lib/features/devices/gatt/uuids.js.map +1 -1
- package/lib/features/devices/link.js.map +1 -1
- package/lib/features/devices/readings.js +32 -1
- package/lib/features/devices/readings.js.map +1 -1
- package/lib/features/devices/session.js +24 -5
- package/lib/features/devices/session.js.map +1 -1
- package/package.json +4 -4
|
@@ -39,16 +39,30 @@ function getManager() {
|
|
|
39
39
|
}
|
|
40
40
|
return manager;
|
|
41
41
|
}
|
|
42
|
+
function advertisementOf(device) {
|
|
43
|
+
var _a, _b, _c, _d, _e;
|
|
44
|
+
const serviceData = {};
|
|
45
|
+
for (const [uuid, value] of Object.entries((_a = device.serviceData) != null ? _a : {})) serviceData[uuid] = fromBase64(value);
|
|
46
|
+
return {
|
|
47
|
+
id: device.id,
|
|
48
|
+
name: (_c = (_b = device.localName) != null ? _b : device.name) != null ? _c : void 0,
|
|
49
|
+
rssi: (_d = device.rssi) != null ? _d : void 0,
|
|
50
|
+
manufacturerData: device.manufacturerData ? fromBase64(device.manufacturerData) : void 0,
|
|
51
|
+
serviceUUIDs: (_e = device.serviceUUIDs) != null ? _e : void 0,
|
|
52
|
+
serviceData
|
|
53
|
+
};
|
|
54
|
+
}
|
|
42
55
|
function linkFor(m, device, offered) {
|
|
43
56
|
var _a, _b;
|
|
44
|
-
const
|
|
45
|
-
const [s, c] = entry.split("|");
|
|
46
|
-
return sameUuid(s, service) && (characteristic === void 0 || c !== void 0 && sameUuid(c, characteristic));
|
|
47
|
-
});
|
|
57
|
+
const find = (service, characteristic) => offered.find((o) => sameUuid(o.service, service) && (characteristic === void 0 || o.characteristic !== void 0 && sameUuid(o.characteristic, characteristic)));
|
|
48
58
|
return {
|
|
49
59
|
id: device.id,
|
|
50
60
|
name: (_b = (_a = device.name) != null ? _a : device.localName) != null ? _b : "Bluetooth device",
|
|
51
|
-
has,
|
|
61
|
+
has: (service, characteristic) => find(service, characteristic) !== void 0,
|
|
62
|
+
canWriteWithoutResponse: (service, characteristic) => {
|
|
63
|
+
var _a2;
|
|
64
|
+
return ((_a2 = find(service, characteristic)) == null ? void 0 : _a2.writeWithoutResponse) === true;
|
|
65
|
+
},
|
|
52
66
|
monitor(service, characteristic, onValue) {
|
|
53
67
|
const sub = device.monitorCharacteristicForService(service, characteristic, (error, c) => {
|
|
54
68
|
if (error || !(c == null ? void 0 : c.value)) return;
|
|
@@ -56,8 +70,12 @@ function linkFor(m, device, offered) {
|
|
|
56
70
|
});
|
|
57
71
|
return () => sub.remove();
|
|
58
72
|
},
|
|
59
|
-
async write(service, characteristic, bytes) {
|
|
60
|
-
|
|
73
|
+
async write(service, characteristic, bytes, options) {
|
|
74
|
+
if (options == null ? void 0 : options.withoutResponse) {
|
|
75
|
+
await device.writeCharacteristicWithoutResponseForService(service, characteristic, toBase64(bytes));
|
|
76
|
+
} else {
|
|
77
|
+
await device.writeCharacteristicWithResponseForService(service, characteristic, toBase64(bytes));
|
|
78
|
+
}
|
|
61
79
|
},
|
|
62
80
|
async read(service, characteristic) {
|
|
63
81
|
const c = await device.readCharacteristicForService(service, characteristic);
|
|
@@ -90,24 +108,32 @@ async function createBleTransport() {
|
|
|
90
108
|
const result = await PermissionsAndroid.requestMultiple(wanted);
|
|
91
109
|
return wanted.every((p) => result[p] === PermissionsAndroid.RESULTS.GRANTED);
|
|
92
110
|
},
|
|
93
|
-
scan(
|
|
111
|
+
scan(request, onFound, onError) {
|
|
94
112
|
const seen = /* @__PURE__ */ new Set();
|
|
95
|
-
|
|
96
|
-
|
|
113
|
+
const filter = request.match ? null : [...request.services];
|
|
114
|
+
void m.startDeviceScan(filter, {
|
|
115
|
+
allowDuplicates: Boolean(request.onAdvertisement)
|
|
97
116
|
}, (error, device) => {
|
|
98
|
-
var _a, _b, _c;
|
|
117
|
+
var _a, _b, _c, _d;
|
|
99
118
|
if (error) {
|
|
100
119
|
onError(describeBleError(error));
|
|
101
120
|
return;
|
|
102
121
|
}
|
|
103
|
-
if (!device
|
|
122
|
+
if (!device) return;
|
|
123
|
+
const adv = advertisementOf(device);
|
|
124
|
+
(_a = request.onAdvertisement) == null ? void 0 : _a.call(request, adv);
|
|
125
|
+
if (seen.has(device.id)) return;
|
|
126
|
+
const standard = kindForServices(adv.serviceUUIDs);
|
|
127
|
+
const wantsStandard = standard !== void 0 && ((_b = adv.serviceUUIDs) != null ? _b : []).some((u) => request.services.some((s) => sameUuid(u, s)));
|
|
128
|
+
const brand = wantsStandard ? void 0 : (_c = request.match) == null ? void 0 : _c.call(request, adv);
|
|
129
|
+
if (!wantsStandard && !brand && request.match) return;
|
|
104
130
|
seen.add(device.id);
|
|
105
|
-
onFound({
|
|
131
|
+
onFound(__spreadValues({
|
|
106
132
|
id: device.id,
|
|
107
|
-
name: (
|
|
108
|
-
rssi:
|
|
109
|
-
kind:
|
|
110
|
-
});
|
|
133
|
+
name: (_d = adv.name) != null ? _d : "Bluetooth device",
|
|
134
|
+
rssi: adv.rssi,
|
|
135
|
+
kind: standard
|
|
136
|
+
}, brand));
|
|
111
137
|
}).catch((err) => onError(describeBleError(err)));
|
|
112
138
|
return () => {
|
|
113
139
|
void m.stopDeviceScan();
|
|
@@ -123,10 +149,18 @@ async function createBleTransport() {
|
|
|
123
149
|
if (Platform.OS === "android") {
|
|
124
150
|
await device.requestMTU(185).catch(() => void 0);
|
|
125
151
|
}
|
|
126
|
-
const offered =
|
|
152
|
+
const offered = [];
|
|
127
153
|
for (const service of await device.services()) {
|
|
128
|
-
offered.
|
|
129
|
-
|
|
154
|
+
offered.push({
|
|
155
|
+
service: service.uuid
|
|
156
|
+
});
|
|
157
|
+
for (const c of await service.characteristics()) {
|
|
158
|
+
offered.push({
|
|
159
|
+
service: service.uuid,
|
|
160
|
+
characteristic: c.uuid,
|
|
161
|
+
writeWithoutResponse: c.isWritableWithoutResponse
|
|
162
|
+
});
|
|
163
|
+
}
|
|
130
164
|
}
|
|
131
165
|
const sub = m.onDeviceDisconnected(id, () => {
|
|
132
166
|
sub.remove();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bleTransport.js","sources":["../../../src/features/devices/bleTransport.ts"],"sourcesContent":["/**\n * The phone's Bluetooth radio, through react-native-ble-plx: is it on and\n * allowed, scanning for health devices, and one connected device as a\n * GattLink for the drivers (drivers.ts).\n *\n * The library is loaded with a dynamic import: it creates its native object\n * when first imported, and a JavaScript update reaching an app binary built\n * before Bluetooth was added would crash at startup on a static import. Here it\n * only means \"not available in this build\".\n */\nimport { PermissionsAndroid, Platform } from 'react-native';\nimport type { BleManager, Device } from 'react-native-ble-plx';\nimport { kindForServices } from './catalog';\nimport { describeBleError, type BleTransport, type ConnectedLink, type RadioState } from './link';\nimport { fromBase64, toBase64 } from './gatt/bytes';\nimport { sameUuid } from './gatt/uuids';\n\nexport type { BleTransport, ConnectedLink, FoundDevice, RadioState } from './link';\n\nconst CONNECT_TIMEOUT_MS = 15_000;\n\nfunction radioState(state: string): RadioState {\n switch (state) {\n case 'PoweredOn':\n return 'ready';\n case 'PoweredOff':\n return 'off';\n case 'Unauthorized':\n return 'unauthorized';\n case 'Unsupported':\n return 'unsupported';\n default:\n return 'unknown';\n }\n}\n\nlet manager: Promise<BleManager | null> | null = null;\n\nfunction getManager(): Promise<BleManager | null> {\n if (!manager) {\n manager = import('react-native-ble-plx').then((m) => new m.BleManager()).catch(() => null);\n }\n return manager;\n}\n\nfunction linkFor(m: BleManager, device: Device, offered: Set<string>): ConnectedLink {\n const has = (service: string, characteristic?: string) =>\n [...offered].some((entry) => {\n const [s, c] = entry.split('|');\n return (\n sameUuid(s, service) &&\n (characteristic === undefined || (c !== undefined && sameUuid(c, characteristic)))\n );\n });\n return {\n id: device.id,\n name: device.name ?? device.localName ?? 'Bluetooth device',\n has,\n monitor(service, characteristic, onValue) {\n const sub = device.monitorCharacteristicForService(service, characteristic, (error, c) => {\n // A monitor error is the connection ending; onDisconnect tells the screen.\n if (error || !c?.value) return;\n onValue(fromBase64(c.value));\n });\n return () => sub.remove();\n },\n async write(service, characteristic, bytes) {\n await device.writeCharacteristicWithResponseForService(service, characteristic, toBase64(bytes));\n },\n async read(service, characteristic) {\n const c = await device.readCharacteristicForService(service, characteristic);\n return fromBase64(c.value);\n },\n async close() {\n await m.cancelDeviceConnection(device.id).catch(() => undefined);\n },\n };\n}\n\nexport async function createBleTransport(): Promise<BleTransport | null> {\n const m = await getManager();\n if (!m) return null;\n return {\n async radio() {\n try {\n return radioState(await m.state());\n } catch {\n return 'unknown';\n }\n },\n\n onRadioChange(listener) {\n const sub = m.onStateChange((state) => listener(radioState(state)), true);\n return () => sub.remove();\n },\n\n async requestPermissions() {\n if (Platform.OS !== 'android') return true;\n const P = PermissionsAndroid.PERMISSIONS;\n const wanted =\n Number(Platform.Version) >= 31 ? [P.BLUETOOTH_SCAN, P.BLUETOOTH_CONNECT] : [P.ACCESS_FINE_LOCATION];\n const result = await PermissionsAndroid.requestMultiple(wanted);\n return wanted.every((p) => result[p] === PermissionsAndroid.RESULTS.GRANTED);\n },\n\n scan(services, onFound, onError) {\n const seen = new Set<string>();\n void m\n .startDeviceScan([...services], { allowDuplicates: false }, (error, device) => {\n if (error) {\n onError(describeBleError(error));\n return;\n }\n if (!device || seen.has(device.id)) return;\n seen.add(device.id);\n onFound({\n id: device.id,\n name: device.localName ?? device.name ?? 'Bluetooth device',\n rssi: device.rssi ?? undefined,\n kind: kindForServices(device.serviceUUIDs),\n });\n })\n .catch((err: unknown) => onError(describeBleError(err)));\n return () => {\n void m.stopDeviceScan();\n };\n },\n\n async connect(id, name, onDisconnect) {\n await m.stopDeviceScan();\n const device = await m.connectToDevice(id, { timeout: CONNECT_TIMEOUT_MS });\n await device.discoverAllServicesAndCharacteristics();\n if (Platform.OS === 'android') {\n // Body composition packets are longer than the default 20 bytes.\n await device.requestMTU(185).catch(() => undefined);\n }\n const offered = new Set<string>();\n for (const service of await device.services()) {\n offered.add(service.uuid);\n for (const c of await service.characteristics()) offered.add(`${service.uuid}|${c.uuid}`);\n }\n const sub = m.onDeviceDisconnected(id, () => {\n sub.remove();\n onDisconnect();\n });\n const link = linkFor(m, device, offered);\n return { ...link, name: device.name ?? device.localName ?? name };\n },\n };\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"bleTransport.js","sources":["../../../src/features/devices/bleTransport.ts"],"sourcesContent":["/**\n * The phone's Bluetooth radio, through react-native-ble-plx: is it on and\n * allowed, scanning for health devices, and one connected device as a\n * GattLink for the drivers (drivers.ts).\n *\n * The library is loaded with a dynamic import: it creates its native object\n * when first imported, and a JavaScript update reaching an app binary built\n * before Bluetooth was added would crash at startup on a static import. Here it\n * only means \"not available in this build\".\n */\nimport { PermissionsAndroid, Platform } from 'react-native';\nimport type { BleManager, Device } from 'react-native-ble-plx';\nimport { kindForServices } from './catalog';\nimport {\n describeBleError,\n type Advertisement,\n type BleTransport,\n type ConnectedLink,\n type RadioState,\n} from './link';\nimport { fromBase64, toBase64 } from './gatt/bytes';\nimport { sameUuid } from './gatt/uuids';\n\nexport type { BleTransport, ConnectedLink, FoundDevice, RadioState } from './link';\n\nconst CONNECT_TIMEOUT_MS = 15_000;\n\nfunction radioState(state: string): RadioState {\n switch (state) {\n case 'PoweredOn':\n return 'ready';\n case 'PoweredOff':\n return 'off';\n case 'Unauthorized':\n return 'unauthorized';\n case 'Unsupported':\n return 'unsupported';\n default:\n return 'unknown';\n }\n}\n\nlet manager: Promise<BleManager | null> | null = null;\n\nfunction getManager(): Promise<BleManager | null> {\n if (!manager) {\n manager = import('react-native-ble-plx').then((m) => new m.BleManager()).catch(() => null);\n }\n return manager;\n}\n\nfunction advertisementOf(device: Device): Advertisement {\n const serviceData: Record<string, Uint8Array> = {};\n for (const [uuid, value] of Object.entries(device.serviceData ?? {})) serviceData[uuid] = fromBase64(value);\n return {\n id: device.id,\n name: device.localName ?? device.name ?? undefined,\n rssi: device.rssi ?? undefined,\n manufacturerData: device.manufacturerData ? fromBase64(device.manufacturerData) : undefined,\n serviceUUIDs: device.serviceUUIDs ?? undefined,\n serviceData,\n };\n}\n\ninterface Offered {\n service: string;\n characteristic?: string;\n writeWithoutResponse?: boolean;\n}\n\nfunction linkFor(m: BleManager, device: Device, offered: readonly Offered[]): ConnectedLink {\n const find = (service: string, characteristic?: string) =>\n offered.find(\n (o) =>\n sameUuid(o.service, service) &&\n (characteristic === undefined || (o.characteristic !== undefined && sameUuid(o.characteristic, characteristic))),\n );\n return {\n id: device.id,\n name: device.name ?? device.localName ?? 'Bluetooth device',\n has: (service, characteristic) => find(service, characteristic) !== undefined,\n canWriteWithoutResponse: (service, characteristic) => find(service, characteristic)?.writeWithoutResponse === true,\n monitor(service, characteristic, onValue) {\n const sub = device.monitorCharacteristicForService(service, characteristic, (error, c) => {\n // A monitor error is the connection ending; onDisconnect tells the screen.\n if (error || !c?.value) return;\n onValue(fromBase64(c.value));\n });\n return () => sub.remove();\n },\n async write(service, characteristic, bytes, options) {\n if (options?.withoutResponse) {\n await device.writeCharacteristicWithoutResponseForService(service, characteristic, toBase64(bytes));\n } else {\n await device.writeCharacteristicWithResponseForService(service, characteristic, toBase64(bytes));\n }\n },\n async read(service, characteristic) {\n const c = await device.readCharacteristicForService(service, characteristic);\n return fromBase64(c.value);\n },\n async close() {\n await m.cancelDeviceConnection(device.id).catch(() => undefined);\n },\n };\n}\n\nexport async function createBleTransport(): Promise<BleTransport | null> {\n const m = await getManager();\n if (!m) return null;\n return {\n async radio() {\n try {\n return radioState(await m.state());\n } catch {\n return 'unknown';\n }\n },\n\n onRadioChange(listener) {\n const sub = m.onStateChange((state) => listener(radioState(state)), true);\n return () => sub.remove();\n },\n\n async requestPermissions() {\n if (Platform.OS !== 'android') return true;\n const P = PermissionsAndroid.PERMISSIONS;\n const wanted =\n Number(Platform.Version) >= 31 ? [P.BLUETOOTH_SCAN, P.BLUETOOTH_CONNECT] : [P.ACCESS_FINE_LOCATION];\n const result = await PermissionsAndroid.requestMultiple(wanted);\n return wanted.every((p) => result[p] === PermissionsAndroid.RESULTS.GRANTED);\n },\n\n scan(request, onFound, onError) {\n const seen = new Set<string>();\n // Brand devices advertise no standard service: listen to everything and let the matcher decide.\n const filter = request.match ? null : [...request.services];\n void m\n .startDeviceScan(filter, { allowDuplicates: Boolean(request.onAdvertisement) }, (error, device) => {\n if (error) {\n onError(describeBleError(error));\n return;\n }\n if (!device) return;\n const adv = advertisementOf(device);\n request.onAdvertisement?.(adv);\n if (seen.has(device.id)) return;\n const standard = kindForServices(adv.serviceUUIDs);\n const wantsStandard =\n standard !== undefined &&\n (adv.serviceUUIDs ?? []).some((u) => request.services.some((s) => sameUuid(u, s)));\n const brand = wantsStandard ? undefined : request.match?.(adv);\n if (!wantsStandard && !brand && request.match) return;\n seen.add(device.id);\n onFound({\n id: device.id,\n name: adv.name ?? 'Bluetooth device',\n rssi: adv.rssi,\n kind: standard,\n ...brand,\n });\n })\n .catch((err: unknown) => onError(describeBleError(err)));\n return () => {\n void m.stopDeviceScan();\n };\n },\n\n async connect(id, name, onDisconnect) {\n await m.stopDeviceScan();\n const device = await m.connectToDevice(id, { timeout: CONNECT_TIMEOUT_MS });\n await device.discoverAllServicesAndCharacteristics();\n if (Platform.OS === 'android') {\n // Body composition packets are longer than the default 20 bytes.\n await device.requestMTU(185).catch(() => undefined);\n }\n const offered: Offered[] = [];\n for (const service of await device.services()) {\n offered.push({ service: service.uuid });\n for (const c of await service.characteristics()) {\n offered.push({\n service: service.uuid,\n characteristic: c.uuid,\n writeWithoutResponse: c.isWritableWithoutResponse,\n });\n }\n }\n const sub = m.onDeviceDisconnected(id, () => {\n sub.remove();\n onDisconnect();\n });\n const link = linkFor(m, device, offered);\n return { ...link, name: device.name ?? device.localName ?? name };\n },\n };\n}\n"],"names":["_a"],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {FrameAssembler,renphoFrame}from'./frames.js';function openRenphoChannel(link, uuids, onFrame) {
|
|
2
|
+
var _a, _b;
|
|
3
|
+
const assembler = new FrameAssembler();
|
|
4
|
+
const withoutResponse = (_b = (_a = link.canWriteWithoutResponse) == null ? void 0 : _a.call(link, uuids.service, uuids.write)) != null ? _b : false;
|
|
5
|
+
const unsub = link.monitor(uuids.service, uuids.notify, (bytes) => assembler.push(bytes, onFrame));
|
|
6
|
+
return {
|
|
7
|
+
async send(cmd, payload) {
|
|
8
|
+
await link.write(uuids.service, uuids.write, renphoFrame(cmd, payload), {
|
|
9
|
+
withoutResponse
|
|
10
|
+
});
|
|
11
|
+
},
|
|
12
|
+
async sendRaw(bytes) {
|
|
13
|
+
await link.write(uuids.service, uuids.write, bytes, {
|
|
14
|
+
withoutResponse
|
|
15
|
+
});
|
|
16
|
+
},
|
|
17
|
+
stop: unsub
|
|
18
|
+
};
|
|
19
|
+
}export{openRenphoChannel};//# sourceMappingURL=channel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel.js","sources":["../../../../src/features/devices/brands/channel.ts"],"sourcesContent":["/**\n * The piece every RENPHO common-frame device shares: subscribe to the notify\n * characteristic, reassemble frames that span notifications, and send framed\n * commands on the write characteristic.\n */\nimport type { GattLink } from '../drivers';\nimport { FrameAssembler, renphoFrame, type Frame } from './frames';\n\nexport interface RenphoChannel {\n /** Send a common frame (`55 AA cmd len payload sum`). */\n send(cmd: number, payload?: ArrayLike<number>): Promise<void>;\n /** Send bytes exactly as given (already-framed, or a raw protocol like the rope's). */\n sendRaw(bytes: number[]): Promise<void>;\n stop(): void;\n}\n\nexport function openRenphoChannel(\n link: GattLink,\n uuids: { service: string; write: string; notify: string },\n onFrame: (frame: Frame) => void,\n): RenphoChannel {\n const assembler = new FrameAssembler();\n const withoutResponse = link.canWriteWithoutResponse?.(uuids.service, uuids.write) ?? false;\n const unsub = link.monitor(uuids.service, uuids.notify, (bytes) => assembler.push(bytes, onFrame));\n return {\n async send(cmd, payload) {\n await link.write(uuids.service, uuids.write, renphoFrame(cmd, payload), { withoutResponse });\n },\n async sendRaw(bytes) {\n await link.write(uuids.service, uuids.write, bytes, { withoutResponse });\n },\n stop: unsub,\n };\n}\n"],"names":[],"mappings":"qDAcgB,SAAA,iBAAA,CAAkB,IAAgB,EAAA,KAAA,EAI/C,OAAgD,EAAA;AAlBnD,EAAA,IAAA,EAAA,EAAA,EAAA;AAmBE,EAAM,MAAA,SAAA,GAAY,IAAI,cAAe,EAAA;AACrC,EAAM,MAAA,eAAA,GAAA,CAAkB,gBAAK,uBAAL,KAAA,IAAA,GAAA,MAAA,GAAA,EAAA,CAAA,IAAA,CAAA,IAAA,EAA+B,MAAM,OAAS,EAAA,KAAA,CAAM,WAApD,IAA8D,GAAA,EAAA,GAAA,KAAA;AACtF,EAAA,MAAM,KAAQ,GAAA,IAAA,CAAK,OAAQ,CAAA,KAAA,CAAM,OAAS,EAAA,KAAA,CAAM,MAAQ,EAAA,CAAA,KAAA,KAAS,SAAU,CAAA,IAAA,CAAK,KAAO,EAAA,OAAO,CAAC,CAAA;AAC/F,EAAO,OAAA;AAAA,IACL,MAAM,IAAK,CAAA,GAAA,EAAK,OAAS,EAAA;AACvB,MAAM,MAAA,IAAA,CAAK,MAAM,KAAM,CAAA,OAAA,EAAS,MAAM,KAAO,EAAA,WAAA,CAAY,GAAK,EAAA,OAAO,CAAG,EAAA;AAAA,QACtE;AAAA,OACD,CAAA;AAAA,KACH;AAAA,IACA,MAAM,QAAQ,KAAO,EAAA;AACnB,MAAA,MAAM,KAAK,KAAM,CAAA,KAAA,CAAM,OAAS,EAAA,KAAA,CAAM,OAAO,KAAO,EAAA;AAAA,QAClD;AAAA,OACD,CAAA;AAAA,KACH;AAAA,IACA,IAAM,EAAA;AAAA,GACR;AACF"}
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import {u16be}from'./frames.js';var __defProp = Object.defineProperty;
|
|
2
|
+
var __defProps = Object.defineProperties;
|
|
3
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
4
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
7
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
8
|
+
var __spreadValues = (a, b) => {
|
|
9
|
+
for (var prop in b || (b = {}))
|
|
10
|
+
if (__hasOwnProp.call(b, prop))
|
|
11
|
+
__defNormalProp(a, prop, b[prop]);
|
|
12
|
+
if (__getOwnPropSymbols)
|
|
13
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
14
|
+
if (__propIsEnum.call(b, prop))
|
|
15
|
+
__defNormalProp(a, prop, b[prop]);
|
|
16
|
+
}
|
|
17
|
+
return a;
|
|
18
|
+
};
|
|
19
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
20
|
+
const RENPHO_TYPES = {
|
|
21
|
+
// Body scales -------------------------------------------------------------
|
|
22
|
+
2: {
|
|
23
|
+
kind: "scale",
|
|
24
|
+
protocol: "renpho-scale"
|
|
25
|
+
},
|
|
26
|
+
// old Lefu-style
|
|
27
|
+
3: {
|
|
28
|
+
kind: "scale",
|
|
29
|
+
protocol: "renpho-scale"
|
|
30
|
+
},
|
|
31
|
+
// ES-24M, R-A001/2/9
|
|
32
|
+
16: {
|
|
33
|
+
kind: "scale",
|
|
34
|
+
protocol: "renpho-scale"
|
|
35
|
+
},
|
|
36
|
+
63: {
|
|
37
|
+
kind: "scale",
|
|
38
|
+
protocol: "renpho-scale"
|
|
39
|
+
},
|
|
40
|
+
// R-A033
|
|
41
|
+
32: {
|
|
42
|
+
kind: "scale",
|
|
43
|
+
protocol: "renpho-scale"
|
|
44
|
+
},
|
|
45
|
+
// common-device scale
|
|
46
|
+
33: {
|
|
47
|
+
kind: "scale",
|
|
48
|
+
protocol: "renpho-scale"
|
|
49
|
+
},
|
|
50
|
+
34: {
|
|
51
|
+
kind: "scale",
|
|
52
|
+
protocol: "renpho-a017-scale"
|
|
53
|
+
},
|
|
54
|
+
// R-A017
|
|
55
|
+
46: {
|
|
56
|
+
kind: "scale",
|
|
57
|
+
protocol: "renpho-a017-scale"
|
|
58
|
+
},
|
|
59
|
+
// R-A017 (alt id)
|
|
60
|
+
44: {
|
|
61
|
+
kind: "scale",
|
|
62
|
+
protocol: "renpho-a017-scale"
|
|
63
|
+
},
|
|
64
|
+
// ES-CS20M
|
|
65
|
+
48: {
|
|
66
|
+
kind: "scale",
|
|
67
|
+
protocol: "renpho-a017-scale"
|
|
68
|
+
},
|
|
69
|
+
// ES-32MD
|
|
70
|
+
49: {
|
|
71
|
+
kind: "scale",
|
|
72
|
+
protocol: "renpho-a017-scale"
|
|
73
|
+
},
|
|
74
|
+
59: {
|
|
75
|
+
kind: "scale",
|
|
76
|
+
protocol: "renpho-a017-scale"
|
|
77
|
+
},
|
|
78
|
+
// ES-32MD (alt id)
|
|
79
|
+
62: {
|
|
80
|
+
kind: "scale",
|
|
81
|
+
protocol: "renpho-a017-scale"
|
|
82
|
+
},
|
|
83
|
+
// ES-CS20M (alt id)
|
|
84
|
+
51: {
|
|
85
|
+
kind: "scale",
|
|
86
|
+
protocol: "renpho-a017-scale"
|
|
87
|
+
},
|
|
88
|
+
// R-A031
|
|
89
|
+
54: {
|
|
90
|
+
kind: "scale",
|
|
91
|
+
protocol: "renpho-a017-scale"
|
|
92
|
+
},
|
|
93
|
+
// R-MSB05
|
|
94
|
+
52: {
|
|
95
|
+
kind: "scale",
|
|
96
|
+
protocol: "renpho-a017-scale"
|
|
97
|
+
},
|
|
98
|
+
// R-A018
|
|
99
|
+
38: {
|
|
100
|
+
kind: "scale",
|
|
101
|
+
protocol: "renpho-a017-scale"
|
|
102
|
+
},
|
|
103
|
+
// R-MSB02
|
|
104
|
+
50: {
|
|
105
|
+
kind: "scale",
|
|
106
|
+
protocol: "renpho-a017-scale"
|
|
107
|
+
},
|
|
108
|
+
// R-MSB04
|
|
109
|
+
// Blood pressure ----------------------------------------------------------
|
|
110
|
+
58: {
|
|
111
|
+
kind: "blood-pressure",
|
|
112
|
+
protocol: "renpho-blood-pressure"
|
|
113
|
+
},
|
|
114
|
+
// TMB-2276-B
|
|
115
|
+
// Food (kitchen) scales ---------------------------------------------------
|
|
116
|
+
23: {
|
|
117
|
+
kind: "kitchen-scale",
|
|
118
|
+
protocol: "renpho-food-scale"
|
|
119
|
+
},
|
|
120
|
+
42: {
|
|
121
|
+
kind: "kitchen-scale",
|
|
122
|
+
protocol: "renpho-food-scale"
|
|
123
|
+
},
|
|
124
|
+
43: {
|
|
125
|
+
kind: "kitchen-scale",
|
|
126
|
+
protocol: "renpho-food-scale"
|
|
127
|
+
},
|
|
128
|
+
57: {
|
|
129
|
+
kind: "kitchen-scale",
|
|
130
|
+
protocol: "renpho-food-scale"
|
|
131
|
+
},
|
|
132
|
+
60: {
|
|
133
|
+
kind: "kitchen-scale",
|
|
134
|
+
protocol: "renpho-food-scale"
|
|
135
|
+
},
|
|
136
|
+
// R-MSK03 nutrition scale
|
|
137
|
+
// Tape measures -----------------------------------------------------------
|
|
138
|
+
10: {
|
|
139
|
+
kind: "tape",
|
|
140
|
+
protocol: "renpho-tape"
|
|
141
|
+
},
|
|
142
|
+
// ES-Tape
|
|
143
|
+
25: {
|
|
144
|
+
kind: "tape",
|
|
145
|
+
protocol: "renpho-tape"
|
|
146
|
+
},
|
|
147
|
+
// R-Y001
|
|
148
|
+
36: {
|
|
149
|
+
kind: "tape",
|
|
150
|
+
protocol: "renpho-tape"
|
|
151
|
+
},
|
|
152
|
+
// R-MTX01
|
|
153
|
+
70: {
|
|
154
|
+
kind: "tape",
|
|
155
|
+
protocol: "renpho-tape"
|
|
156
|
+
},
|
|
157
|
+
// RF-BMF01
|
|
158
|
+
27: {
|
|
159
|
+
kind: "tape",
|
|
160
|
+
protocol: "renpho-tape-new"
|
|
161
|
+
},
|
|
162
|
+
// R-Y002 family
|
|
163
|
+
35: {
|
|
164
|
+
kind: "tape",
|
|
165
|
+
protocol: "renpho-tape-new"
|
|
166
|
+
},
|
|
167
|
+
// R-Y002
|
|
168
|
+
// Jump ropes --------------------------------------------------------------
|
|
169
|
+
1: {
|
|
170
|
+
kind: "jump-rope",
|
|
171
|
+
protocol: "renpho-jump-rope"
|
|
172
|
+
},
|
|
173
|
+
// RENPHO-ROPE-R1
|
|
174
|
+
11: {
|
|
175
|
+
kind: "jump-rope",
|
|
176
|
+
protocol: "renpho-jump-rope"
|
|
177
|
+
},
|
|
178
|
+
// R-Q008
|
|
179
|
+
24: {
|
|
180
|
+
kind: "jump-rope",
|
|
181
|
+
protocol: "renpho-jump-rope"
|
|
182
|
+
},
|
|
183
|
+
// R-Q011
|
|
184
|
+
61: {
|
|
185
|
+
kind: "jump-rope",
|
|
186
|
+
protocol: "renpho-jump-rope"
|
|
187
|
+
},
|
|
188
|
+
// R-FAR01
|
|
189
|
+
// Treadmill ---------------------------------------------------------------
|
|
190
|
+
6: {
|
|
191
|
+
kind: "treadmill",
|
|
192
|
+
protocol: "renpho-treadmill"
|
|
193
|
+
},
|
|
194
|
+
// R-Q004 (FTMS)
|
|
195
|
+
// Smart ring --------------------------------------------------------------
|
|
196
|
+
45: {
|
|
197
|
+
kind: "ring",
|
|
198
|
+
protocol: "renpho-ring"
|
|
199
|
+
},
|
|
200
|
+
// Massagers / fascia guns / foot machines: recognised, nothing to store ---
|
|
201
|
+
29: {
|
|
202
|
+
kind: "scale",
|
|
203
|
+
protocol: "renpho-control-only"
|
|
204
|
+
},
|
|
205
|
+
// R-C001S fascia gun
|
|
206
|
+
47: {
|
|
207
|
+
kind: "scale",
|
|
208
|
+
protocol: "renpho-control-only"
|
|
209
|
+
},
|
|
210
|
+
53: {
|
|
211
|
+
kind: "scale",
|
|
212
|
+
protocol: "renpho-control-only"
|
|
213
|
+
},
|
|
214
|
+
55: {
|
|
215
|
+
kind: "scale",
|
|
216
|
+
protocol: "renpho-control-only"
|
|
217
|
+
},
|
|
218
|
+
64: {
|
|
219
|
+
kind: "scale",
|
|
220
|
+
protocol: "renpho-control-only"
|
|
221
|
+
},
|
|
222
|
+
65: {
|
|
223
|
+
kind: "scale",
|
|
224
|
+
protocol: "renpho-control-only"
|
|
225
|
+
},
|
|
226
|
+
67: {
|
|
227
|
+
kind: "scale",
|
|
228
|
+
protocol: "renpho-control-only"
|
|
229
|
+
},
|
|
230
|
+
72: {
|
|
231
|
+
kind: "scale",
|
|
232
|
+
protocol: "renpho-control-only"
|
|
233
|
+
},
|
|
234
|
+
73: {
|
|
235
|
+
kind: "scale",
|
|
236
|
+
protocol: "renpho-control-only"
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
function macOf(bytes, start) {
|
|
240
|
+
return Array.from(bytes.subarray(start, start + 6)).map((b) => b.toString(16).padStart(2, "0").toUpperCase()).join(":");
|
|
241
|
+
}
|
|
242
|
+
function detectBrand(adv) {
|
|
243
|
+
var _a, _b;
|
|
244
|
+
const md = adv.manufacturerData;
|
|
245
|
+
const name = (_a = adv.name) != null ? _a : "";
|
|
246
|
+
if (md && md.length === 12 && md[0] === 241 && md[1] === 255) {
|
|
247
|
+
return {
|
|
248
|
+
kind: "ring",
|
|
249
|
+
protocol: "renpho-ring",
|
|
250
|
+
deviceType: 45,
|
|
251
|
+
mac: macOf(md, 4),
|
|
252
|
+
model: name.split(" ")[0] || void 0
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
if (md && md.length >= 14 && md[0] === 16 && md[1] === 26) {
|
|
256
|
+
const deviceType = u16be(md, 4);
|
|
257
|
+
const info = RENPHO_TYPES[deviceType];
|
|
258
|
+
if (info) {
|
|
259
|
+
return __spreadProps(__spreadValues({}, identity(info)), {
|
|
260
|
+
deviceType,
|
|
261
|
+
mac: macOf(md, 6),
|
|
262
|
+
model: name || void 0
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (md && md.length >= 10) {
|
|
267
|
+
const info = RENPHO_TYPES[md[1]];
|
|
268
|
+
if (info && info.protocol === "renpho-jump-rope") {
|
|
269
|
+
return __spreadProps(__spreadValues({}, identity(info)), {
|
|
270
|
+
deviceType: md[1],
|
|
271
|
+
mac: macOf(md, 3)
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
const serviceData = adv.serviceData ? Object.values(adv.serviceData) : [];
|
|
276
|
+
const sd = serviceData[serviceData.length - 1];
|
|
277
|
+
if (sd && sd.length >= 24) {
|
|
278
|
+
const deviceType = u16be(sd, 2);
|
|
279
|
+
const info = RENPHO_TYPES[deviceType];
|
|
280
|
+
if (info && info.protocol === "renpho-scale") {
|
|
281
|
+
return __spreadProps(__spreadValues({}, identity(info)), {
|
|
282
|
+
deviceType,
|
|
283
|
+
mac: macOf(sd, 5),
|
|
284
|
+
model: cString(sd, 12, 12) || void 0
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const services = ((_b = adv.serviceUUIDs) != null ? _b : []).map((u) => u.toLowerCase());
|
|
289
|
+
const hasQnService = services.some((u) => u.includes("ffe0") || u.includes("fff0") || u.includes("feb3"));
|
|
290
|
+
const qnName = /^QN-/.test(name) || /^Yolanda/i.test(name) || name === "QN-Scale" || name === "QN-Scale1";
|
|
291
|
+
const qnMd = md && md.length >= 10 && (md[0] === 255 && md[1] === 255 || md[0] === 168 && md[1] === 1);
|
|
292
|
+
if ((hasQnService || qnName) && (qnMd || hasQnService)) {
|
|
293
|
+
return {
|
|
294
|
+
kind: "scale",
|
|
295
|
+
protocol: "qn-scale",
|
|
296
|
+
model: name || void 0
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
return void 0;
|
|
300
|
+
}
|
|
301
|
+
function identity(info) {
|
|
302
|
+
return info.protocol === "renpho-control-only" ? {
|
|
303
|
+
protocol: info.protocol
|
|
304
|
+
} : {
|
|
305
|
+
kind: info.kind,
|
|
306
|
+
protocol: info.protocol
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
function cString(bytes, start, maxLen) {
|
|
310
|
+
let s = "";
|
|
311
|
+
for (let i = start; i < start + maxLen && i < bytes.length; i++) {
|
|
312
|
+
const c = bytes[i];
|
|
313
|
+
if (c === 0) break;
|
|
314
|
+
if (c < 32 || c > 126) return "";
|
|
315
|
+
s += String.fromCharCode(c);
|
|
316
|
+
}
|
|
317
|
+
return s;
|
|
318
|
+
}export{detectBrand};//# sourceMappingURL=detect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect.js","sources":["../../../../src/features/devices/brands/detect.ts"],"sourcesContent":["/**\n * Which brand protocol a device speaks, worked out from its advertisement the\n * way RENPHO's own app does it (RHBLEBroadcastParserFactory, QNPScaleManager\n * centralDiscoverPeripheral; ~/Downloads/renpho-rea/PROTOCOLS.md section 1).\n *\n * A brand device does not advertise the Bluetooth SIG service of its kind, so\n * the scan would never surface it on the standard profiles. Here we recognise\n * it by its manufacturer data, service data or name and say which protocol and\n * which kind of device it is, so the session can pick the right driver.\n */\nimport type { DeviceKindId } from '../catalog';\nimport type { Advertisement, DeviceIdentity } from '../link';\nimport { u16be } from './frames';\n\nexport type DeviceProtocolId =\n /** RENPHO common-frame devices (newer, \"10 1A\" manufacturer data). */\n | 'renpho-scale'\n | 'renpho-a017-scale'\n | 'renpho-blood-pressure'\n | 'renpho-food-scale'\n | 'renpho-tape'\n | 'renpho-tape-new'\n | 'renpho-jump-rope'\n | 'renpho-ring'\n | 'renpho-treadmill'\n /** QN / Yolanda scales. */\n | 'qn-scale'\n /** Recognised but no reading to store (massagers, foot machines). */\n | 'renpho-control-only';\n\n/** The RENPHO \"device type\" byte -> what the device is and how Yantra talks to it. */\ninterface TypeInfo {\n kind: DeviceKindId;\n protocol: DeviceProtocolId;\n}\n\n/**\n * RENPHO device types, from RHBLEDeviceModel.commHandler (the switch that maps\n * each type to its handler) and allowDeviceList.json (which model is which\n * type). Only the kinds Yantra reads get a real driver; the massagers are\n * control-only.\n */\nconst RENPHO_TYPES: Record<number, TypeInfo> = {\n // Body scales -------------------------------------------------------------\n 0x02: { kind: 'scale', protocol: 'renpho-scale' }, // old Lefu-style\n 0x03: { kind: 'scale', protocol: 'renpho-scale' }, // ES-24M, R-A001/2/9\n 0x10: { kind: 'scale', protocol: 'renpho-scale' },\n 0x3f: { kind: 'scale', protocol: 'renpho-scale' }, // R-A033\n 0x20: { kind: 'scale', protocol: 'renpho-scale' }, // common-device scale\n 0x21: { kind: 'scale', protocol: 'renpho-scale' },\n 0x22: { kind: 'scale', protocol: 'renpho-a017-scale' }, // R-A017\n 0x2e: { kind: 'scale', protocol: 'renpho-a017-scale' }, // R-A017 (alt id)\n 0x2c: { kind: 'scale', protocol: 'renpho-a017-scale' }, // ES-CS20M\n 0x30: { kind: 'scale', protocol: 'renpho-a017-scale' }, // ES-32MD\n 0x31: { kind: 'scale', protocol: 'renpho-a017-scale' },\n 0x3b: { kind: 'scale', protocol: 'renpho-a017-scale' }, // ES-32MD (alt id)\n 0x3e: { kind: 'scale', protocol: 'renpho-a017-scale' }, // ES-CS20M (alt id)\n 0x33: { kind: 'scale', protocol: 'renpho-a017-scale' }, // R-A031\n 0x36: { kind: 'scale', protocol: 'renpho-a017-scale' }, // R-MSB05\n 0x34: { kind: 'scale', protocol: 'renpho-a017-scale' }, // R-A018\n 0x26: { kind: 'scale', protocol: 'renpho-a017-scale' }, // R-MSB02\n 0x32: { kind: 'scale', protocol: 'renpho-a017-scale' }, // R-MSB04\n // Blood pressure ----------------------------------------------------------\n 0x3a: { kind: 'blood-pressure', protocol: 'renpho-blood-pressure' }, // TMB-2276-B\n // Food (kitchen) scales ---------------------------------------------------\n 0x17: { kind: 'kitchen-scale', protocol: 'renpho-food-scale' },\n 0x2a: { kind: 'kitchen-scale', protocol: 'renpho-food-scale' },\n 0x2b: { kind: 'kitchen-scale', protocol: 'renpho-food-scale' },\n 0x39: { kind: 'kitchen-scale', protocol: 'renpho-food-scale' },\n 0x3c: { kind: 'kitchen-scale', protocol: 'renpho-food-scale' }, // R-MSK03 nutrition scale\n // Tape measures -----------------------------------------------------------\n 0x0a: { kind: 'tape', protocol: 'renpho-tape' }, // ES-Tape\n 0x19: { kind: 'tape', protocol: 'renpho-tape' }, // R-Y001\n 0x24: { kind: 'tape', protocol: 'renpho-tape' }, // R-MTX01\n 0x46: { kind: 'tape', protocol: 'renpho-tape' }, // RF-BMF01\n 0x1b: { kind: 'tape', protocol: 'renpho-tape-new' }, // R-Y002 family\n 0x23: { kind: 'tape', protocol: 'renpho-tape-new' }, // R-Y002\n // Jump ropes --------------------------------------------------------------\n 0x01: { kind: 'jump-rope', protocol: 'renpho-jump-rope' }, // RENPHO-ROPE-R1\n 0x0b: { kind: 'jump-rope', protocol: 'renpho-jump-rope' }, // R-Q008\n 0x18: { kind: 'jump-rope', protocol: 'renpho-jump-rope' }, // R-Q011\n 0x3d: { kind: 'jump-rope', protocol: 'renpho-jump-rope' }, // R-FAR01\n // Treadmill ---------------------------------------------------------------\n 0x06: { kind: 'treadmill', protocol: 'renpho-treadmill' }, // R-Q004 (FTMS)\n // Smart ring --------------------------------------------------------------\n 0x2d: { kind: 'ring', protocol: 'renpho-ring' },\n // Massagers / fascia guns / foot machines: recognised, nothing to store ---\n 0x1d: { kind: 'scale', protocol: 'renpho-control-only' }, // R-C001S fascia gun\n 0x2f: { kind: 'scale', protocol: 'renpho-control-only' },\n 0x35: { kind: 'scale', protocol: 'renpho-control-only' },\n 0x37: { kind: 'scale', protocol: 'renpho-control-only' },\n 0x40: { kind: 'scale', protocol: 'renpho-control-only' },\n 0x41: { kind: 'scale', protocol: 'renpho-control-only' },\n 0x43: { kind: 'scale', protocol: 'renpho-control-only' },\n 0x48: { kind: 'scale', protocol: 'renpho-control-only' },\n 0x49: { kind: 'scale', protocol: 'renpho-control-only' },\n};\n\n/** Six bytes -> \"AA:BB:CC:DD:EE:FF\". */\nfunction macOf(bytes: Uint8Array, start: number): string {\n return Array.from(bytes.subarray(start, start + 6))\n .map((b) => b.toString(16).padStart(2, '0').toUpperCase())\n .join(':');\n}\n\n/** Only these kinds have a Yantra driver (control-only devices store nothing). */\nexport function kindForProtocol(protocol: DeviceProtocolId): DeviceKindId | undefined {\n if (protocol === 'renpho-control-only') return undefined;\n if (protocol === 'qn-scale') return 'scale';\n return RENPHO_TYPES[typeForProtocolSample(protocol)]?.kind;\n}\n\n// A protocol maps to one kind; pick any type that uses it to look the kind up.\nfunction typeForProtocolSample(protocol: DeviceProtocolId): number {\n for (const [type, info] of Object.entries(RENPHO_TYPES)) {\n if (info.protocol === protocol) return Number(type);\n }\n return -1;\n}\n\n/**\n * Recognise a brand device from one advertisement. Returns what it is, or\n * undefined when it is not a brand device Yantra knows (a standard-profile\n * device is matched elsewhere, by its advertised service).\n */\nexport function detectBrand(adv: Advertisement): DeviceIdentity | undefined {\n const md = adv.manufacturerData;\n const name = adv.name ?? '';\n\n // Smart ring: 12-byte manufacturer data starting F1 FF.\n if (md && md.length === 12 && md[0] === 0xf1 && md[1] === 0xff) {\n return {\n kind: 'ring',\n protocol: 'renpho-ring',\n deviceType: 0x2d,\n mac: macOf(md, 4),\n model: name.split(' ')[0] || undefined,\n };\n }\n\n // Newer RENPHO devices: \"10 1A\" manufacturer data with the device type inside.\n if (md && md.length >= 14 && md[0] === 0x10 && md[1] === 0x1a) {\n const deviceType = u16be(md, 4);\n const info = RENPHO_TYPES[deviceType];\n if (info) {\n return { ...identity(info), deviceType, mac: macOf(md, 6), model: name || undefined };\n }\n }\n\n // Jump rope: manufacturer data [manufacturer, deviceType, _, MAC(6)].\n if (md && md.length >= 10) {\n const info = RENPHO_TYPES[md[1]];\n if (info && info.protocol === 'renpho-jump-rope') {\n return { ...identity(info), deviceType: md[1], mac: macOf(md, 3) };\n }\n }\n\n // Old Lefu-style scale: service data, last value, >= 24 bytes.\n const serviceData = adv.serviceData ? Object.values(adv.serviceData) : [];\n const sd = serviceData[serviceData.length - 1];\n if (sd && sd.length >= 24) {\n const deviceType = u16be(sd, 2);\n const info = RENPHO_TYPES[deviceType];\n if (info && info.protocol === 'renpho-scale') {\n return {\n ...identity(info),\n deviceType,\n mac: macOf(sd, 5),\n model: cString(sd, 12, 12) || undefined,\n };\n }\n }\n\n // QN / Yolanda scale: a QN service, or a QN-style name with FF FF / A8 01 data.\n const services = (adv.serviceUUIDs ?? []).map((u) => u.toLowerCase());\n const hasQnService = services.some((u) => u.includes('ffe0') || u.includes('fff0') || u.includes('feb3'));\n const qnName = /^QN-/.test(name) || /^Yolanda/i.test(name) || name === 'QN-Scale' || name === 'QN-Scale1';\n const qnMd = md && md.length >= 10 && ((md[0] === 0xff && md[1] === 0xff) || (md[0] === 0xa8 && md[1] === 0x01));\n if ((hasQnService || qnName) && (qnMd || hasQnService)) {\n return { kind: 'scale', protocol: 'qn-scale', model: name || undefined };\n }\n\n return undefined;\n}\n\nfunction identity(info: TypeInfo): DeviceIdentity {\n return info.protocol === 'renpho-control-only'\n ? { protocol: info.protocol }\n : { kind: info.kind, protocol: info.protocol };\n}\n\n/** A NUL-terminated ASCII string inside a byte range. */\nfunction cString(bytes: Uint8Array, start: number, maxLen: number): string {\n let s = '';\n for (let i = start; i < start + maxLen && i < bytes.length; i++) {\n const c = bytes[i];\n if (c === 0) break;\n if (c < 0x20 || c > 0x7e) return '';\n s += String.fromCharCode(c);\n }\n return s;\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, key + "" , value);
|
|
4
|
+
function sum8(bytes, start = 0, end = bytes.length) {
|
|
5
|
+
let s = 0;
|
|
6
|
+
for (let i = start; i < end; i++) s = s + bytes[i] & 255;
|
|
7
|
+
return s;
|
|
8
|
+
}
|
|
9
|
+
const u16be = (b, i) => (b[i] << 8 | b[i + 1]) >>> 0;
|
|
10
|
+
const u32be = (b, i) => (b[i] << 24 | b[i + 1] << 16 | b[i + 2] << 8 | b[i + 3]) >>> 0;
|
|
11
|
+
const u16le = (b, i) => (b[i] | b[i + 1] << 8) >>> 0;
|
|
12
|
+
const u32le = (b, i) => (b[i] | b[i + 1] << 8 | b[i + 2] << 16 | b[i + 3] << 24) >>> 0;
|
|
13
|
+
const be16 = (n) => [n >> 8 & 255, n & 255];
|
|
14
|
+
const be32 = (n) => [n >>> 24 & 255, n >>> 16 & 255, n >>> 8 & 255, n & 255];
|
|
15
|
+
const le16 = (n) => [n & 255, n >> 8 & 255];
|
|
16
|
+
const le32 = (n) => [n & 255, n >>> 8 & 255, n >>> 16 & 255, n >>> 24 & 255];
|
|
17
|
+
function renphoFrame(cmd, payload = []) {
|
|
18
|
+
const body = [85, 170, cmd & 255, ...be16(payload.length), ...Array.from(payload)];
|
|
19
|
+
return [...body, sum8(body)];
|
|
20
|
+
}
|
|
21
|
+
function parseRenphoFrame(bytes) {
|
|
22
|
+
if (bytes.length < 6 || bytes[0] !== 85 || bytes[1] !== 170) return null;
|
|
23
|
+
const len = u16be(bytes, 3);
|
|
24
|
+
if (bytes.length !== len + 6) return null;
|
|
25
|
+
if (sum8(bytes, 0, bytes.length - 1) !== bytes[bytes.length - 1]) return null;
|
|
26
|
+
return {
|
|
27
|
+
cmd: bytes[2],
|
|
28
|
+
payload: bytes.subarray(5, 5 + len)
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
class FrameAssembler {
|
|
32
|
+
constructor() {
|
|
33
|
+
__publicField(this, "buf", []);
|
|
34
|
+
}
|
|
35
|
+
push(chunk, onFrame) {
|
|
36
|
+
for (const b of chunk) this.buf.push(b);
|
|
37
|
+
for (; ; ) {
|
|
38
|
+
while (this.buf.length >= 2 && !(this.buf[0] === 85 && this.buf[1] === 170)) this.buf.shift();
|
|
39
|
+
if (this.buf.length < 5) return;
|
|
40
|
+
const len = u16be(this.buf, 3);
|
|
41
|
+
const total = len + 6;
|
|
42
|
+
if (len > 1024) {
|
|
43
|
+
this.buf.shift();
|
|
44
|
+
this.buf.shift();
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (this.buf.length < total) return;
|
|
48
|
+
const frame = parseRenphoFrame(Uint8Array.from(this.buf.slice(0, total)));
|
|
49
|
+
this.buf = this.buf.slice(total);
|
|
50
|
+
if (frame) onFrame(frame);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
reset() {
|
|
54
|
+
this.buf = [];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const CRC_HI = [];
|
|
58
|
+
const CRC_LO = [];
|
|
59
|
+
(() => {
|
|
60
|
+
for (let i = 0; i < 256; i++) {
|
|
61
|
+
let crc = i;
|
|
62
|
+
for (let j = 0; j < 8; j++) crc = crc & 1 ? crc >> 1 ^ 40961 : crc >> 1;
|
|
63
|
+
CRC_LO[i] = crc & 255;
|
|
64
|
+
CRC_HI[i] = crc >> 8 & 255;
|
|
65
|
+
}
|
|
66
|
+
})();
|
|
67
|
+
function ropeCrc(bytes, start = 0, end = bytes.length) {
|
|
68
|
+
let hi = 255;
|
|
69
|
+
let lo = 255;
|
|
70
|
+
for (let i = start; i < end; i++) {
|
|
71
|
+
const idx = lo ^ bytes[i];
|
|
72
|
+
lo = hi ^ CRC_LO[idx];
|
|
73
|
+
hi = CRC_HI[idx];
|
|
74
|
+
}
|
|
75
|
+
return [hi, lo];
|
|
76
|
+
}
|
|
77
|
+
function ropeFrame(cmd, payload = []) {
|
|
78
|
+
const body = [cmd & 255, ...be16(payload.length), ...Array.from(payload)];
|
|
79
|
+
return [...body, ...ropeCrc(body)];
|
|
80
|
+
}
|
|
81
|
+
function parseRopeFrame(bytes) {
|
|
82
|
+
if (bytes.length < 5) return null;
|
|
83
|
+
const len = u16be(bytes, 1);
|
|
84
|
+
if (bytes.length !== len + 5) return null;
|
|
85
|
+
const [hi, lo] = ropeCrc(bytes, 0, bytes.length - 2);
|
|
86
|
+
if (bytes[bytes.length - 2] !== hi || bytes[bytes.length - 1] !== lo) return null;
|
|
87
|
+
return {
|
|
88
|
+
cmd: bytes[0],
|
|
89
|
+
payload: bytes.subarray(3, 3 + len)
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function ringFrame(bytes) {
|
|
93
|
+
const out = new Array(16).fill(0);
|
|
94
|
+
for (let i = 0; i < Math.min(15, bytes.length); i++) out[i] = bytes[i] & 255;
|
|
95
|
+
out[15] = sum8(out, 0, 15);
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
function qnPacket(bytes) {
|
|
99
|
+
return [...bytes, sum8(bytes)];
|
|
100
|
+
}
|
|
101
|
+
function qnChecksumOk(bytes) {
|
|
102
|
+
return bytes.length >= 1 && sum8(bytes, 0, bytes.length - 1) === bytes[bytes.length - 1];
|
|
103
|
+
}export{FrameAssembler,be16,be32,le16,le32,parseRenphoFrame,parseRopeFrame,qnChecksumOk,qnPacket,renphoFrame,ringFrame,ropeCrc,ropeFrame,sum8,u16be,u16le,u32be,u32le};//# sourceMappingURL=frames.js.map
|