@adminide-stack/yantra-mobile 12.0.131-alpha.1 → 12.0.131-alpha.2
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 +3 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frames.js","sources":["../../../../src/features/devices/brands/frames.ts"],"sourcesContent":["/**\n * The byte framing RENPHO devices use, as their app builds it\n * (~/Downloads/renpho-rea/PROTOCOLS.md, sections 2 and 12):\n *\n * - RENPHO common frame: `55 AA <cmd> <len hi> <len lo> <payload> <sum>`,\n * sum = 8-bit sum of every byte before it. Scales, blood pressure monitors,\n * food scales and the newer tape measures. A frame longer than one\n * notification arrives in pieces; FrameAssembler puts it back together.\n * - Rope frame: `<cmd> <len hi> <len lo> <payload> <crc hi> <crc lo>`,\n * CRC-16/MODBUS over everything before it.\n * - Ring command: 16 bytes, the command zero-padded to 15, then the 8-bit sum.\n * - QN (Yolanda) scale packet: `<cmd> <len> ... <sum>`, the sum over the rest.\n */\n\nexport function sum8(bytes: ArrayLike<number>, start = 0, end = bytes.length): number {\n let s = 0;\n for (let i = start; i < end; i++) s = (s + bytes[i]) & 0xff;\n return s;\n}\n\nexport const u16be = (b: ArrayLike<number>, i: number) => ((b[i] << 8) | b[i + 1]) >>> 0;\nexport const u32be = (b: ArrayLike<number>, i: number) =>\n ((b[i] << 24) | (b[i + 1] << 16) | (b[i + 2] << 8) | b[i + 3]) >>> 0;\nexport const u16le = (b: ArrayLike<number>, i: number) => (b[i] | (b[i + 1] << 8)) >>> 0;\nexport const u32le = (b: ArrayLike<number>, i: number) =>\n (b[i] | (b[i + 1] << 8) | (b[i + 2] << 16) | (b[i + 3] << 24)) >>> 0;\nexport const be16 = (n: number) => [(n >> 8) & 0xff, n & 0xff];\nexport const be32 = (n: number) => [(n >>> 24) & 0xff, (n >>> 16) & 0xff, (n >>> 8) & 0xff, n & 0xff];\nexport const le16 = (n: number) => [n & 0xff, (n >> 8) & 0xff];\nexport const le32 = (n: number) => [n & 0xff, (n >>> 8) & 0xff, (n >>> 16) & 0xff, (n >>> 24) & 0xff];\n\n// ------------------------------------------------------- RENPHO common frame\n\nexport interface Frame {\n cmd: number;\n payload: Uint8Array;\n}\n\n/** `55 AA cmd lenHi lenLo <payload> sum`. */\nexport function renphoFrame(cmd: number, payload: ArrayLike<number> = []): number[] {\n const body = [0x55, 0xaa, cmd & 0xff, ...be16(payload.length), ...Array.from(payload)];\n return [...body, sum8(body)];\n}\n\n/** Parse one whole common frame; null when it is short or the checksum is wrong. */\nexport function parseRenphoFrame(bytes: Uint8Array): Frame | null {\n if (bytes.length < 6 || bytes[0] !== 0x55 || bytes[1] !== 0xaa) return null;\n const len = u16be(bytes, 3);\n if (bytes.length !== len + 6) return null;\n if (sum8(bytes, 0, bytes.length - 1) !== bytes[bytes.length - 1]) return null;\n return { cmd: bytes[2], payload: bytes.subarray(5, 5 + len) };\n}\n\n/**\n * Reassembles common frames that span several notifications: a frame starts\n * with `55 AA`, and its total length is `payload length + 6`. Bytes that are\n * not part of a started frame are dropped.\n */\nexport class FrameAssembler {\n private buf: number[] = [];\n\n push(chunk: Uint8Array, onFrame: (frame: Frame) => void): void {\n for (const b of chunk) this.buf.push(b);\n for (;;) {\n // Resync to the next 55 AA.\n while (this.buf.length >= 2 && !(this.buf[0] === 0x55 && this.buf[1] === 0xaa)) this.buf.shift();\n if (this.buf.length < 5) return;\n const len = u16be(this.buf, 3);\n const total = len + 6;\n if (len > 1024) {\n // Not a real frame; drop this 55 AA and resync.\n this.buf.shift();\n this.buf.shift();\n continue;\n }\n if (this.buf.length < total) return;\n const frame = parseRenphoFrame(Uint8Array.from(this.buf.slice(0, total)));\n this.buf = this.buf.slice(total);\n if (frame) onFrame(frame);\n }\n }\n\n reset(): void {\n this.buf = [];\n }\n}\n\n// --------------------------------------------------------------- Rope frame\n\n// CRC-16/MODBUS, the standard table pair RENPHO's rope protocol uses.\nconst CRC_HI: number[] = [];\nconst CRC_LO: number[] = [];\n(() => {\n for (let i = 0; i < 256; i++) {\n let crc = i;\n for (let j = 0; j < 8; j++) crc = crc & 1 ? (crc >> 1) ^ 0xa001 : crc >> 1;\n CRC_LO[i] = crc & 0xff;\n CRC_HI[i] = (crc >> 8) & 0xff;\n }\n})();\n\n/** CRC-16/MODBUS (little-endian value), returned as [hi, lo] the way the rope sends it. */\nexport function ropeCrc(bytes: ArrayLike<number>, start = 0, end = bytes.length): [number, number] {\n let hi = 0xff;\n let lo = 0xff;\n for (let i = start; i < end; i++) {\n const idx = lo ^ bytes[i];\n lo = hi ^ CRC_LO[idx];\n hi = CRC_HI[idx];\n }\n return [hi, lo];\n}\n\n/** `cmd lenHi lenLo <payload> crcHi crcLo`. */\nexport function ropeFrame(cmd: number, payload: ArrayLike<number> = []): number[] {\n const body = [cmd & 0xff, ...be16(payload.length), ...Array.from(payload)];\n return [...body, ...ropeCrc(body)];\n}\n\nexport function parseRopeFrame(bytes: Uint8Array): Frame | null {\n if (bytes.length < 5) return null;\n const len = u16be(bytes, 1);\n if (bytes.length !== len + 5) return null;\n const [hi, lo] = ropeCrc(bytes, 0, bytes.length - 2);\n if (bytes[bytes.length - 2] !== hi || bytes[bytes.length - 1] !== lo) return null;\n return { cmd: bytes[0], payload: bytes.subarray(3, 3 + len) };\n}\n\n// --------------------------------------------------------------- Ring frame\n\n/** 16 bytes: the command zero-padded to 15, then the 8-bit sum of those 15. */\nexport function ringFrame(bytes: ArrayLike<number>): number[] {\n const out = new Array(16).fill(0);\n for (let i = 0; i < Math.min(15, bytes.length); i++) out[i] = bytes[i] & 0xff;\n out[15] = sum8(out, 0, 15);\n return out;\n}\n\n// --------------------------------------------------------------- QN packet\n\n/** `cmd len <body> sum`, sum over everything before it. QN scales. */\nexport function qnPacket(bytes: number[]): number[] {\n return [...bytes, sum8(bytes)];\n}\n\n/** Checks the trailing 8-bit sum of a QN packet. */\nexport function qnChecksumOk(bytes: Uint8Array): boolean {\n return bytes.length >= 1 && sum8(bytes, 0, bytes.length - 1) === bytes[bytes.length - 1];\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {startRenphoScale}from'./renphoScale.js';import {startRenphoBloodPressure}from'./renphoBloodPressure.js';import {startRenphoFoodScale}from'./renphoFoodScale.js';import {startRenphoJumpRope}from'./renphoJumpRope.js';import {startNewTape,startOldTape}from'./renphoTape.js';import {startRenphoRing}from'./renphoRing.js';import {startQnScale}from'./qnScale.js';import {startFtmsTreadmill}from'../gatt/ftms.js';import'./frames.js';function startBrandDriver(protocol, link, ctx) {
|
|
2
|
+
switch (protocol) {
|
|
3
|
+
case "renpho-scale":
|
|
4
|
+
return startRenphoScale("common", link, ctx);
|
|
5
|
+
case "renpho-a017-scale":
|
|
6
|
+
return startRenphoScale("a017", link, ctx);
|
|
7
|
+
case "qn-scale":
|
|
8
|
+
return startQnScale(link, ctx);
|
|
9
|
+
case "renpho-blood-pressure":
|
|
10
|
+
return startRenphoBloodPressure(link, ctx);
|
|
11
|
+
case "renpho-food-scale":
|
|
12
|
+
return startRenphoFoodScale(link, ctx);
|
|
13
|
+
case "renpho-jump-rope":
|
|
14
|
+
return startRenphoJumpRope(link, ctx);
|
|
15
|
+
case "renpho-tape":
|
|
16
|
+
return startOldTape(link, ctx);
|
|
17
|
+
case "renpho-tape-new":
|
|
18
|
+
return startNewTape(link, ctx);
|
|
19
|
+
case "renpho-ring":
|
|
20
|
+
return startRenphoRing(link, ctx);
|
|
21
|
+
case "renpho-treadmill":
|
|
22
|
+
return startFtmsTreadmill(link, ctx);
|
|
23
|
+
case "renpho-control-only":
|
|
24
|
+
throw new Error("This device has nothing for Yantra to read.");
|
|
25
|
+
default: {
|
|
26
|
+
const never = protocol;
|
|
27
|
+
throw new Error(`Unknown device protocol ${String(never)}.`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}export{startBrandDriver};//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../../src/features/devices/brands/index.ts"],"sourcesContent":["/**\n * Pick the driver for a brand device from the protocol its advertisement named\n * (brands/detect.ts). A standard-profile device never reaches here; it runs the\n * Bluetooth SIG driver in drivers.ts.\n */\nimport type { DriverContext, DriverHandle, GattLink } from '../drivers';\nimport type { DeviceProtocolId } from './detect';\nimport { startRenphoScale } from './renphoScale';\nimport { startRenphoBloodPressure } from './renphoBloodPressure';\nimport { startRenphoFoodScale } from './renphoFoodScale';\nimport { startRenphoJumpRope } from './renphoJumpRope';\nimport { startOldTape, startNewTape } from './renphoTape';\nimport { startRenphoRing } from './renphoRing';\nimport { startQnScale } from './qnScale';\nimport { startFtmsTreadmill } from '../gatt/ftms';\n\nexport { detectBrand, type DeviceProtocolId } from './detect';\n\nexport function startBrandDriver(\n protocol: DeviceProtocolId,\n link: GattLink,\n ctx: DriverContext,\n): DriverHandle | Promise<DriverHandle> {\n switch (protocol) {\n case 'renpho-scale':\n return startRenphoScale('common', link, ctx);\n case 'renpho-a017-scale':\n return startRenphoScale('a017', link, ctx);\n case 'qn-scale':\n return startQnScale(link, ctx);\n case 'renpho-blood-pressure':\n return startRenphoBloodPressure(link, ctx);\n case 'renpho-food-scale':\n return startRenphoFoodScale(link, ctx);\n case 'renpho-jump-rope':\n return startRenphoJumpRope(link, ctx);\n case 'renpho-tape':\n return startOldTape(link, ctx);\n case 'renpho-tape-new':\n return startNewTape(link, ctx);\n case 'renpho-ring':\n return startRenphoRing(link, ctx);\n case 'renpho-treadmill':\n return startFtmsTreadmill(link, ctx);\n case 'renpho-control-only':\n throw new Error('This device has nothing for Yantra to read.');\n default: {\n const never: never = protocol;\n throw new Error(`Unknown device protocol ${String(never)}.`);\n }\n }\n}\n"],"names":[],"mappings":"ibAgBgB,SAAA,gBAAA,CAAiB,QAA4B,EAAA,IAAA,EAAgB,GAA0D,EAAA;AACrI,EAAA,QAAQ,QAAU;AAAA,IAChB,KAAK,cAAA;AACH,MAAO,OAAA,gBAAA,CAAiB,QAAU,EAAA,IAAA,EAAM,GAAG,CAAA;AAAA,IAC7C,KAAK,mBAAA;AACH,MAAO,OAAA,gBAAA,CAAiB,MAAQ,EAAA,IAAA,EAAM,GAAG,CAAA;AAAA,IAC3C,KAAK,UAAA;AACH,MAAO,OAAA,YAAA,CAAa,MAAM,GAAG,CAAA;AAAA,IAC/B,KAAK,uBAAA;AACH,MAAO,OAAA,wBAAA,CAAyB,MAAM,GAAG,CAAA;AAAA,IAC3C,KAAK,mBAAA;AACH,MAAO,OAAA,oBAAA,CAAqB,MAAM,GAAG,CAAA;AAAA,IACvC,KAAK,kBAAA;AACH,MAAO,OAAA,mBAAA,CAAoB,MAAM,GAAG,CAAA;AAAA,IACtC,KAAK,aAAA;AACH,MAAO,OAAA,YAAA,CAAa,MAAM,GAAG,CAAA;AAAA,IAC/B,KAAK,iBAAA;AACH,MAAO,OAAA,YAAA,CAAa,MAAM,GAAG,CAAA;AAAA,IAC/B,KAAK,aAAA;AACH,MAAO,OAAA,eAAA,CAAgB,MAAM,GAAG,CAAA;AAAA,IAClC,KAAK,kBAAA;AACH,MAAO,OAAA,kBAAA,CAAmB,MAAM,GAAG,CAAA;AAAA,IACrC,KAAK,qBAAA;AACH,MAAM,MAAA,IAAI,MAAM,6CAA6C,CAAA;AAAA,IAC/D,SACE;AACE,MAAA,MAAM,KAAe,GAAA,QAAA;AACrB,MAAA,MAAM,IAAI,KAAM,CAAA,CAAA,wBAAA,EAA2B,MAAO,CAAA,KAAK,CAAC,CAAG,CAAA,CAAA,CAAA;AAAA;AAC7D;AAEN"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import {readingId}from'../readings.js';import {qnChecksumOk,u16be,qnPacket,le32}from'./frames.js';import {QN}from'./uuids.js';var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
3
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
4
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
5
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
6
|
+
var __spreadValues = (a, b) => {
|
|
7
|
+
for (var prop in b || (b = {}))
|
|
8
|
+
if (__hasOwnProp.call(b, prop))
|
|
9
|
+
__defNormalProp(a, prop, b[prop]);
|
|
10
|
+
if (__getOwnPropSymbols)
|
|
11
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
12
|
+
if (__propIsEnum.call(b, prop))
|
|
13
|
+
__defNormalProp(a, prop, b[prop]);
|
|
14
|
+
}
|
|
15
|
+
return a;
|
|
16
|
+
};
|
|
17
|
+
function qnTime(now) {
|
|
18
|
+
return Math.max(0, Math.floor((now - Date.UTC(2e3, 0, 1)) / 1e3));
|
|
19
|
+
}
|
|
20
|
+
function startQnScale(link, ctx) {
|
|
21
|
+
const useAlt = !link.has(QN.service) && link.has(QN.serviceAlt);
|
|
22
|
+
const service = useAlt ? QN.serviceAlt : QN.service;
|
|
23
|
+
const writeChar = (cmd) => useAlt ? QN.writeAlt : cmd === 32 || cmd === 34 ? QN.write20 : QN.write13;
|
|
24
|
+
const withoutResponse = (char) => {
|
|
25
|
+
var _a, _b;
|
|
26
|
+
return (_b = (_a = link.canWriteWithoutResponse) == null ? void 0 : _a.call(link, service, char)) != null ? _b : false;
|
|
27
|
+
};
|
|
28
|
+
const send = (packet) => {
|
|
29
|
+
const char = writeChar(packet[0]);
|
|
30
|
+
return link.write(service, char, packet, {
|
|
31
|
+
withoutResponse: withoutResponse(char)
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
let deviceByte = 0;
|
|
35
|
+
let resolution = 0;
|
|
36
|
+
let done = false;
|
|
37
|
+
const weightKgOf = (raw) => {
|
|
38
|
+
const w = raw * (resolution === 1 ? 0.1 : 0.01);
|
|
39
|
+
return w > 300 ? w * 0.1 : w;
|
|
40
|
+
};
|
|
41
|
+
const emitFinal = (weightKg, impedance) => {
|
|
42
|
+
const at = ctx.now();
|
|
43
|
+
const reading = __spreadValues({
|
|
44
|
+
kind: "body",
|
|
45
|
+
id: readingId(link.id, "body", at),
|
|
46
|
+
at,
|
|
47
|
+
deviceId: link.id,
|
|
48
|
+
deviceName: link.name,
|
|
49
|
+
weightKg
|
|
50
|
+
}, impedance ? {
|
|
51
|
+
impedanceOhm: impedance
|
|
52
|
+
} : {});
|
|
53
|
+
ctx.onReading(reading);
|
|
54
|
+
};
|
|
55
|
+
const sendConfig = () => {
|
|
56
|
+
var _a, _b;
|
|
57
|
+
const unitByte = 1;
|
|
58
|
+
const height = Math.round((_a = ctx.person.heightCm) != null ? _a : 0);
|
|
59
|
+
const age = Math.round((_b = ctx.person.age) != null ? _b : 0);
|
|
60
|
+
const male = ctx.person.sex === "male" ? 1 : 0;
|
|
61
|
+
void send(qnPacket([19, 9, deviceByte, unitByte, 16, height, age, male])).catch(() => void 0);
|
|
62
|
+
void send(qnPacket([32, 8, deviceByte, ...le32(qnTime(ctx.now()))])).catch(() => void 0);
|
|
63
|
+
};
|
|
64
|
+
const unsub = link.monitor(service, QN.notify, (p) => {
|
|
65
|
+
var _a, _b;
|
|
66
|
+
if (p.length === 1) {
|
|
67
|
+
(_a = ctx.onLive) == null ? void 0 : _a.call(ctx, {
|
|
68
|
+
battery: p[0]
|
|
69
|
+
});
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (!qnChecksumOk(p)) return;
|
|
73
|
+
const cmd = p[0];
|
|
74
|
+
if (cmd === 18 && p.length >= 11) {
|
|
75
|
+
deviceByte = p[2];
|
|
76
|
+
resolution = (p[10] & 1) === 0 ? 1 : 0;
|
|
77
|
+
sendConfig();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (cmd === 16 && p.length >= 6) {
|
|
81
|
+
const weightKg = weightKgOf(u16be(p, 3));
|
|
82
|
+
const state = p[5];
|
|
83
|
+
if (state === 0 || state === 17 || state === 18) {
|
|
84
|
+
(_b = ctx.onLive) == null ? void 0 : _b.call(ctx, {
|
|
85
|
+
weightKg
|
|
86
|
+
});
|
|
87
|
+
} else if ((state === 1 || state === 2) && !done) {
|
|
88
|
+
done = true;
|
|
89
|
+
const impedance = p.length >= 8 ? u16be(p, 6) || void 0 : void 0;
|
|
90
|
+
emitFinal(weightKg, impedance);
|
|
91
|
+
}
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
return {
|
|
96
|
+
stop: unsub
|
|
97
|
+
};
|
|
98
|
+
}export{startQnScale};//# sourceMappingURL=qnScale.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qnScale.js","sources":["../../../../src/features/devices/brands/qnScale.ts"],"sourcesContent":["/**\n * QN / Yolanda public scales (PROTOCOLS.md section 6). Packets are\n * `cmd len deviceByte ... sum`, notified on FFE1; writes go to FFE3/FFE4 (or\n * FFF2 when the scale exposes the FFF0 service).\n *\n * Yantra reads weight (reliable on the public protocol) and, when the scale\n * does not scramble it, the impedance. Body composition is left to Yantra's own\n * estimate: the scale's own figure needs QN's proprietary algorithm.\n */\nimport type { DriverContext, DriverHandle, GattLink } from '../drivers';\nimport { readingId, type BodyReading } from '../readings';\nimport { le32, qnChecksumOk, qnPacket, u16be } from './frames';\nimport { QN } from './uuids';\n\n/** Seconds from 2000-01-01 UTC, the epoch QN scales stamp with. */\nfunction qnTime(now: number): number {\n return Math.max(0, Math.floor((now - Date.UTC(2000, 0, 1)) / 1000));\n}\n\nexport function startQnScale(link: GattLink, ctx: DriverContext): DriverHandle {\n const useAlt = !link.has(QN.service) && link.has(QN.serviceAlt);\n const service = useAlt ? QN.serviceAlt : QN.service;\n const writeChar = (cmd: number) => (useAlt ? QN.writeAlt : cmd === 0x20 || cmd === 0x22 ? QN.write20 : QN.write13);\n const withoutResponse = (char: string) => link.canWriteWithoutResponse?.(service, char) ?? false;\n\n const send = (packet: number[]) => {\n const char = writeChar(packet[0]);\n return link.write(service, char, packet, { withoutResponse: withoutResponse(char) });\n };\n\n let deviceByte = 0;\n let resolution = 0; // 0 = 0.01 kg, 1 = 0.1 kg\n let done = false;\n\n const weightKgOf = (raw: number) => {\n const w = raw * (resolution === 1 ? 0.1 : 0.01);\n // The scale's own guard: a value over 300 is still in the finer unit.\n return w > 300 ? w * 0.1 : w;\n };\n\n const emitFinal = (weightKg: number, impedance: number | undefined) => {\n const at = ctx.now();\n const reading: BodyReading = {\n kind: 'body',\n id: readingId(link.id, 'body', at),\n at,\n deviceId: link.id,\n deviceName: link.name,\n weightKg,\n ...(impedance ? { impedanceOhm: impedance } : {}),\n };\n ctx.onReading(reading);\n };\n\n const sendConfig = () => {\n const unitByte = 0x01; // kg\n const height = Math.round(ctx.person.heightCm ?? 0);\n const age = Math.round(ctx.person.age ?? 0);\n const male = ctx.person.sex === 'male' ? 1 : 0;\n void send(qnPacket([0x13, 0x09, deviceByte, unitByte, 0x10, height, age, male])).catch(() => undefined);\n void send(qnPacket([0x20, 0x08, deviceByte, ...le32(qnTime(ctx.now()))])).catch(() => undefined);\n };\n\n const unsub = link.monitor(service, QN.notify, (p) => {\n if (p.length === 1) {\n ctx.onLive?.({ battery: p[0] });\n return;\n }\n if (!qnChecksumOk(p)) return;\n const cmd = p[0];\n if (cmd === 0x12 && p.length >= 11) {\n deviceByte = p[2];\n resolution = (p[10] & 1) === 0 ? 1 : 0; // flag bit0 set => 0.01 kg\n sendConfig();\n return;\n }\n if (cmd === 0x10 && p.length >= 6) {\n const weightKg = weightKgOf(u16be(p, 3));\n const state = p[5];\n if (state === 0 || state === 0x11 || state === 0x12) {\n ctx.onLive?.({ weightKg });\n } else if ((state === 1 || state === 2) && !done) {\n done = true;\n // Impedance is reported raw only when the scale does not scramble it.\n const impedance = p.length >= 8 ? u16be(p, 6) || undefined : undefined;\n emitFinal(weightKg, impedance);\n }\n return;\n }\n });\n\n return { stop: unsub };\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import {readingId}from'../readings.js';import {u16be}from'./frames.js';import {openRenphoChannel}from'./channel.js';import {RENPHO_BP}from'./uuids.js';var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
3
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
4
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
5
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
6
|
+
var __spreadValues = (a, b) => {
|
|
7
|
+
for (var prop in b || (b = {}))
|
|
8
|
+
if (__hasOwnProp.call(b, prop))
|
|
9
|
+
__defNormalProp(a, prop, b[prop]);
|
|
10
|
+
if (__getOwnPropSymbols)
|
|
11
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
12
|
+
if (__propIsEnum.call(b, prop))
|
|
13
|
+
__defNormalProp(a, prop, b[prop]);
|
|
14
|
+
}
|
|
15
|
+
return a;
|
|
16
|
+
};
|
|
17
|
+
function problemsOf(flags) {
|
|
18
|
+
const out = [];
|
|
19
|
+
if (flags >> 4 & 1) out.push("Irregular heartbeat detected");
|
|
20
|
+
if (flags >> 5 & 1) out.push("You moved during the reading");
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
function startRenphoBloodPressure(link, ctx) {
|
|
24
|
+
let channel = null;
|
|
25
|
+
const emit = (at, sys, dia, pulse, flags) => {
|
|
26
|
+
const problems = problemsOf(flags);
|
|
27
|
+
const reading = __spreadValues(__spreadValues({
|
|
28
|
+
kind: "blood-pressure",
|
|
29
|
+
id: readingId(link.id, "blood-pressure", at, `${sys}-${dia}`),
|
|
30
|
+
at,
|
|
31
|
+
deviceId: link.id,
|
|
32
|
+
deviceName: link.name,
|
|
33
|
+
systolic: sys,
|
|
34
|
+
diastolic: dia
|
|
35
|
+
}, pulse ? {
|
|
36
|
+
pulse
|
|
37
|
+
} : {}), problems.length ? {
|
|
38
|
+
problems
|
|
39
|
+
} : {});
|
|
40
|
+
ctx.onReading(reading);
|
|
41
|
+
};
|
|
42
|
+
const handleFrame = (frame) => {
|
|
43
|
+
var _a, _b;
|
|
44
|
+
const p = frame.payload;
|
|
45
|
+
const pkt = p[0];
|
|
46
|
+
switch (frame.cmd) {
|
|
47
|
+
case 16:
|
|
48
|
+
void (channel == null ? void 0 : channel.send(144, [pkt, 1]));
|
|
49
|
+
if (p.length >= 3) (_a = ctx.onLive) == null ? void 0 : _a.call(ctx, {
|
|
50
|
+
battery: p[2] & 1 ? 10 : 100
|
|
51
|
+
});
|
|
52
|
+
break;
|
|
53
|
+
case 18:
|
|
54
|
+
void (channel == null ? void 0 : channel.send(146, [pkt, 1]));
|
|
55
|
+
if (p.length >= 7) emit(ctx.now(), u16be(p, 2), u16be(p, 4), p[6] || void 0, p[1]);
|
|
56
|
+
break;
|
|
57
|
+
case 19:
|
|
58
|
+
void (channel == null ? void 0 : channel.send(147, [pkt, 1]));
|
|
59
|
+
(_b = ctx.onNotice) == null ? void 0 : _b.call(ctx, "The monitor could not finish the reading. Sit still and try again.");
|
|
60
|
+
break;
|
|
61
|
+
case 21: {
|
|
62
|
+
void (channel == null ? void 0 : channel.send(149, [pkt, 1]));
|
|
63
|
+
if (p.length >= 14) {
|
|
64
|
+
const at = Date.UTC(2e3 + p[8], p[9] - 1, p[10], p[11], p[12], p[13]);
|
|
65
|
+
emit(Number.isFinite(at) ? at : ctx.now(), u16be(p, 3), u16be(p, 5), p[7] || void 0, p[2]);
|
|
66
|
+
}
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
channel = openRenphoChannel(link, RENPHO_BP, handleFrame);
|
|
72
|
+
const indicateUnsub = link.has(RENPHO_BP.service, RENPHO_BP.indicate) ? link.monitor(RENPHO_BP.service, RENPHO_BP.indicate, (bytes) => {
|
|
73
|
+
const frame = parseIfWhole(bytes);
|
|
74
|
+
if (frame) handleFrame(frame);
|
|
75
|
+
}) : void 0;
|
|
76
|
+
return {
|
|
77
|
+
stop() {
|
|
78
|
+
channel == null ? void 0 : channel.stop();
|
|
79
|
+
indicateUnsub == null ? void 0 : indicateUnsub();
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function parseIfWhole(bytes) {
|
|
84
|
+
if (bytes.length >= 6 && bytes[0] === 85 && bytes[1] === 170) {
|
|
85
|
+
const len = u16be(bytes, 3);
|
|
86
|
+
if (bytes.length === len + 6) return {
|
|
87
|
+
cmd: bytes[2],
|
|
88
|
+
payload: bytes.subarray(5, 5 + len)
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}export{startRenphoBloodPressure};//# sourceMappingURL=renphoBloodPressure.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"renphoBloodPressure.js","sources":["../../../../src/features/devices/brands/renphoBloodPressure.ts"],"sourcesContent":["/**\n * RENPHO blood pressure monitor (TMB-2276-B, PROTOCOLS.md section 7). Common\n * frames on service 1A10, notify 1A12, write 1A11. Every device packet starts\n * with a packet number; the app acks `<cmd> [pkt, 01]`.\n */\nimport type { DriverContext, DriverHandle, GattLink } from '../drivers';\nimport { readingId, type BloodPressureReading } from '../readings';\nimport { u16be, type Frame } from './frames';\nimport { openRenphoChannel, type RenphoChannel } from './channel';\nimport { RENPHO_BP } from './uuids';\n\nfunction problemsOf(flags: number): string[] {\n const out: string[] = [];\n if ((flags >> 4) & 1) out.push('Irregular heartbeat detected');\n if ((flags >> 5) & 1) out.push('You moved during the reading');\n return out;\n}\n\nexport function startRenphoBloodPressure(link: GattLink, ctx: DriverContext): DriverHandle {\n let channel: RenphoChannel | null = null;\n\n const emit = (at: number, sys: number, dia: number, pulse: number | undefined, flags: number) => {\n const problems = problemsOf(flags);\n const reading: BloodPressureReading = {\n kind: 'blood-pressure',\n id: readingId(link.id, 'blood-pressure', at, `${sys}-${dia}`),\n at,\n deviceId: link.id,\n deviceName: link.name,\n systolic: sys,\n diastolic: dia,\n ...(pulse ? { pulse } : {}),\n ...(problems.length ? { problems } : {}),\n };\n ctx.onReading(reading);\n };\n\n const handleFrame = (frame: Frame) => {\n const p = frame.payload;\n const pkt = p[0];\n switch (frame.cmd) {\n case 0x10: // status\n void channel?.send(0x90, [pkt, 0x01]);\n if (p.length >= 3) ctx.onLive?.({ battery: p[2] & 1 ? 10 : 100 });\n break;\n case 0x12: // live measurement result\n void channel?.send(0x92, [pkt, 0x01]);\n if (p.length >= 7) emit(ctx.now(), u16be(p, 2), u16be(p, 4), p[6] || undefined, p[1]);\n break;\n case 0x13: // error code\n void channel?.send(0x93, [pkt, 0x01]);\n ctx.onNotice?.('The monitor could not finish the reading. Sit still and try again.');\n break;\n case 0x15: {\n // Stored reading with its own timestamp.\n void channel?.send(0x95, [pkt, 0x01]);\n if (p.length >= 14) {\n const at = Date.UTC(2000 + p[8], p[9] - 1, p[10], p[11], p[12], p[13]);\n emit(Number.isFinite(at) ? at : ctx.now(), u16be(p, 3), u16be(p, 5), p[7] || undefined, p[2]);\n }\n break;\n }\n default:\n break;\n }\n };\n\n channel = openRenphoChannel(link, RENPHO_BP, handleFrame);\n // Indicated characteristic carries the stored records on some units.\n const indicateUnsub = link.has(RENPHO_BP.service, RENPHO_BP.indicate)\n ? link.monitor(RENPHO_BP.service, RENPHO_BP.indicate, (bytes) => {\n const frame = parseIfWhole(bytes);\n if (frame) handleFrame(frame);\n })\n : undefined;\n\n return {\n stop() {\n channel?.stop();\n indicateUnsub?.();\n },\n };\n}\n\n// The indicate channel sends whole frames; parse one directly.\nfunction parseIfWhole(bytes: Uint8Array): Frame | null {\n if (bytes.length >= 6 && bytes[0] === 0x55 && bytes[1] === 0xaa) {\n const len = u16be(bytes, 3);\n if (bytes.length === len + 6) return { cmd: bytes[2], payload: bytes.subarray(5, 5 + len) };\n }\n return null;\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import {u32be}from'./frames.js';import {openRenphoChannel}from'./channel.js';import {RENPHO_COMMON}from'./uuids.js';const GRAMS_PER_UNIT = 0.1;
|
|
2
|
+
function signed32(v) {
|
|
3
|
+
return v >= 2147483648 ? v - 4294967296 : v;
|
|
4
|
+
}
|
|
5
|
+
function startRenphoFoodScale(link, ctx) {
|
|
6
|
+
let channel = null;
|
|
7
|
+
const handle = (frame) => {
|
|
8
|
+
var _a, _b, _c;
|
|
9
|
+
const p = frame.payload;
|
|
10
|
+
switch (frame.cmd) {
|
|
11
|
+
case 16:
|
|
12
|
+
void (channel == null ? void 0 : channel.send(144, [251]));
|
|
13
|
+
if (p.length >= 4) (_a = ctx.onLive) == null ? void 0 : _a.call(ctx, {
|
|
14
|
+
grams: signed32(u32be(p, 0)) * GRAMS_PER_UNIT
|
|
15
|
+
});
|
|
16
|
+
break;
|
|
17
|
+
case 20:
|
|
18
|
+
void (channel == null ? void 0 : channel.send(148, [251]));
|
|
19
|
+
if (p.length >= 4) (_b = ctx.onLive) == null ? void 0 : _b.call(ctx, {
|
|
20
|
+
grams: signed32(u32be(p, 0)) * GRAMS_PER_UNIT
|
|
21
|
+
});
|
|
22
|
+
break;
|
|
23
|
+
case 26:
|
|
24
|
+
// device state
|
|
25
|
+
case 28:
|
|
26
|
+
void (channel == null ? void 0 : channel.send(frame.cmd | 128, [251]));
|
|
27
|
+
if (p.length >= 1) (_c = ctx.onLive) == null ? void 0 : _c.call(ctx, {
|
|
28
|
+
battery: p[0] === 1 ? 10 : 100
|
|
29
|
+
});
|
|
30
|
+
break;
|
|
31
|
+
default:
|
|
32
|
+
if (frame.cmd < 128) void (channel == null ? void 0 : channel.send(frame.cmd | 128, [251]));
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
channel = openRenphoChannel(link, RENPHO_COMMON, handle);
|
|
37
|
+
return {
|
|
38
|
+
stop() {
|
|
39
|
+
channel == null ? void 0 : channel.stop();
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}export{startRenphoFoodScale};//# sourceMappingURL=renphoFoodScale.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"renphoFoodScale.js","sources":["../../../../src/features/devices/brands/renphoFoodScale.ts"],"sourcesContent":["/**\n * RENPHO kitchen (food) scale (PROTOCOLS.md section 8). Common frames on\n * service 1A10. The scale streams the weight on the plate; Yantra shows it\n * live (food weight is not a body metric, so nothing is stored).\n *\n * The weight's unit is not certain from static analysis (the app sends weights\n * back multiplied by 10, so the wire value is likely tenths of a gram); the\n * divisor is the one thing to confirm against a real scale.\n */\nimport type { DriverContext, DriverHandle, GattLink } from '../drivers';\nimport { u32be, type Frame } from './frames';\nimport { openRenphoChannel, type RenphoChannel } from './channel';\nimport { RENPHO_COMMON } from './uuids';\n\n/** Tenths of a gram on the wire (to confirm with a real scale). */\nconst GRAMS_PER_UNIT = 0.1;\n\nfunction signed32(v: number): number {\n return v >= 0x80000000 ? v - 0x100000000 : v;\n}\n\nexport function startRenphoFoodScale(link: GattLink, ctx: DriverContext): DriverHandle {\n let channel: RenphoChannel | null = null;\n\n const handle = (frame: Frame) => {\n const p = frame.payload;\n switch (frame.cmd) {\n case 0x10: // live weight\n void channel?.send(0x90, [0xfb]);\n if (p.length >= 4) ctx.onLive?.({ grams: signed32(u32be(p, 0)) * GRAMS_PER_UNIT });\n break;\n case 0x14: // stable weight\n void channel?.send(0x94, [0xfb]);\n if (p.length >= 4) ctx.onLive?.({ grams: signed32(u32be(p, 0)) * GRAMS_PER_UNIT });\n break;\n case 0x1a: // device state\n case 0x1c:\n void channel?.send(frame.cmd | 0x80, [0xfb]);\n if (p.length >= 1) ctx.onLive?.({ battery: p[0] === 1 ? 10 : 100 });\n break;\n default:\n // Other cmds (unit, light ...) are acked so the scale keeps talking.\n if (frame.cmd < 0x80) void channel?.send(frame.cmd | 0x80, [0xfb]);\n break;\n }\n };\n\n channel = openRenphoChannel(link, RENPHO_COMMON, handle);\n return {\n stop() {\n channel?.stop();\n },\n };\n}\n"],"names":[],"mappings":"oHAeA,MAAM,cAAiB,GAAA,GAAA;AACvB,SAAS,SAAS,CAAmB,EAAA;AACnC,EAAO,OAAA,CAAA,IAAK,UAAa,GAAA,CAAA,GAAI,UAAc,GAAA,CAAA;AAC7C;AACgB,SAAA,oBAAA,CAAqB,MAAgB,GAAkC,EAAA;AACrF,EAAA,IAAI,OAAgC,GAAA,IAAA;AACpC,EAAM,MAAA,MAAA,GAAS,CAAC,KAAiB,KAAA;AArBnC,IAAA,IAAA,EAAA,EAAA,EAAA,EAAA,EAAA;AAsBI,IAAA,MAAM,IAAI,KAAM,CAAA,OAAA;AAChB,IAAA,QAAQ,MAAM,GAAK;AAAA,MACjB,KAAK,EAAA;AAEH,QAAA,MAAK,OAAS,IAAA,IAAA,GAAA,MAAA,GAAA,OAAA,CAAA,IAAA,CAAK,GAAM,EAAA,CAAC,GAAI,CAAA,CAAA,CAAA;AAC9B,QAAA,IAAI,CAAE,CAAA,MAAA,IAAU,CAAG,EAAA,CAAA,EAAA,GAAA,GAAA,CAAI,WAAJ,IAAa,GAAA,MAAA,GAAA,EAAA,CAAA,IAAA,CAAA,GAAA,EAAA;AAAA,UAC9B,OAAO,QAAS,CAAA,KAAA,CAAM,CAAG,EAAA,CAAC,CAAC,CAAI,GAAA;AAAA,SACjC,CAAA;AACA,QAAA;AAAA,MACF,KAAK,EAAA;AAEH,QAAA,MAAK,OAAS,IAAA,IAAA,GAAA,MAAA,GAAA,OAAA,CAAA,IAAA,CAAK,GAAM,EAAA,CAAC,GAAI,CAAA,CAAA,CAAA;AAC9B,QAAA,IAAI,CAAE,CAAA,MAAA,IAAU,CAAG,EAAA,CAAA,EAAA,GAAA,GAAA,CAAI,WAAJ,IAAa,GAAA,MAAA,GAAA,EAAA,CAAA,IAAA,CAAA,GAAA,EAAA;AAAA,UAC9B,OAAO,QAAS,CAAA,KAAA,CAAM,CAAG,EAAA,CAAC,CAAC,CAAI,GAAA;AAAA,SACjC,CAAA;AACA,QAAA;AAAA,MACF,KAAK,EAAA;AAAA;AAAA,MACL,KAAK,EAAA;AACH,QAAA,MAAK,mCAAS,IAAK,CAAA,KAAA,CAAM,GAAM,GAAA,GAAA,EAAM,CAAC,GAAI,CAAA,CAAA,CAAA;AAC1C,QAAA,IAAI,CAAE,CAAA,MAAA,IAAU,CAAG,EAAA,CAAA,EAAA,GAAA,GAAA,CAAI,WAAJ,IAAa,GAAA,MAAA,GAAA,EAAA,CAAA,IAAA,CAAA,GAAA,EAAA;AAAA,UAC9B,OAAS,EAAA,CAAA,CAAE,CAAC,CAAA,KAAM,IAAI,EAAK,GAAA;AAAA,SAC7B,CAAA;AACA,QAAA;AAAA,MACF;AAEE,QAAI,IAAA,KAAA,CAAM,GAAM,GAAA,GAAA,EAAW,MAAA,OAAA,IAAA,IAAA,GAAA,MAAA,GAAA,OAAA,CAAS,KAAK,KAAM,CAAA,GAAA,GAAM,GAAM,EAAA,CAAC,GAAI,CAAA,CAAA,CAAA;AAChE,QAAA;AAAA;AACJ,GACF;AACA,EAAU,OAAA,GAAA,iBAAA,CAAkB,IAAM,EAAA,aAAA,EAAe,MAAM,CAAA;AACvD,EAAO,OAAA;AAAA,IACL,IAAO,GAAA;AACL,MAAS,OAAA,IAAA,IAAA,GAAA,MAAA,GAAA,OAAA,CAAA,IAAA,EAAA;AAAA;AACX,GACF;AACF"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import {readingId}from'../readings.js';import {u16be,parseRopeFrame,u32be,ropeFrame}from'./frames.js';import {RENPHO_ROPE}from'./uuids.js';function startRenphoJumpRope(link, ctx) {
|
|
2
|
+
var _a, _b;
|
|
3
|
+
const withoutResponse = (_b = (_a = link.canWriteWithoutResponse) == null ? void 0 : _a.call(link, RENPHO_ROPE.service, RENPHO_ROPE.write)) != null ? _b : false;
|
|
4
|
+
const buf = [];
|
|
5
|
+
const startedAt = ctx.now();
|
|
6
|
+
const send = (cmd, payload = []) => link.write(RENPHO_ROPE.service, RENPHO_ROPE.write, ropeFrame(cmd, payload), {
|
|
7
|
+
withoutResponse
|
|
8
|
+
});
|
|
9
|
+
const handle = (frame) => {
|
|
10
|
+
var _a2, _b2;
|
|
11
|
+
const p = frame.payload;
|
|
12
|
+
switch (frame.cmd) {
|
|
13
|
+
case 64:
|
|
14
|
+
if (p.length >= 11) {
|
|
15
|
+
(_a2 = ctx.onLive) == null ? void 0 : _a2.call(ctx, {
|
|
16
|
+
jumps: u32be(p, 5),
|
|
17
|
+
elapsedS: u16be(p, 3),
|
|
18
|
+
battery: p[16]
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
break;
|
|
22
|
+
case 129:
|
|
23
|
+
if (p.length >= 2) (_b2 = ctx.onLive) == null ? void 0 : _b2.call(ctx, {
|
|
24
|
+
battery: p[1]
|
|
25
|
+
});
|
|
26
|
+
break;
|
|
27
|
+
case 160:
|
|
28
|
+
// session summary
|
|
29
|
+
case 132:
|
|
30
|
+
if (p.length >= 11) {
|
|
31
|
+
const unix = u32be(p, 0);
|
|
32
|
+
const at = unix > 15e8 ? unix * 1e3 : startedAt;
|
|
33
|
+
const totalSeconds = u16be(p, 7);
|
|
34
|
+
const reading = {
|
|
35
|
+
kind: "workout",
|
|
36
|
+
id: readingId(link.id, "workout", at),
|
|
37
|
+
at,
|
|
38
|
+
end: at + totalSeconds * 1e3,
|
|
39
|
+
deviceId: link.id,
|
|
40
|
+
deviceName: link.name,
|
|
41
|
+
workoutType: "jump-rope",
|
|
42
|
+
durationMin: totalSeconds / 60,
|
|
43
|
+
jumps: u16be(p, 9)
|
|
44
|
+
};
|
|
45
|
+
if (reading.jumps) ctx.onReading(reading);
|
|
46
|
+
}
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const unsub = link.monitor(RENPHO_ROPE.service, RENPHO_ROPE.notify, (bytes) => {
|
|
51
|
+
for (const b of bytes) buf.push(b);
|
|
52
|
+
for (; ; ) {
|
|
53
|
+
if (buf.length < 5) return;
|
|
54
|
+
const len = u16be(buf, 1);
|
|
55
|
+
const total = len + 5;
|
|
56
|
+
if (len > 512) {
|
|
57
|
+
buf.shift();
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (buf.length < total) return;
|
|
61
|
+
const frame = parseRopeFrame(Uint8Array.from(buf.slice(0, total)));
|
|
62
|
+
buf.splice(0, total);
|
|
63
|
+
if (frame) handle(frame);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
const d = new Date(startedAt);
|
|
67
|
+
void send(1, [d.getSeconds(), d.getMinutes(), d.getHours(), d.getDate(), d.getMonth() + 1, d.getFullYear() - 2e3]).then(() => send(4, [0, 0])).catch(() => void 0);
|
|
68
|
+
return {
|
|
69
|
+
stop: unsub
|
|
70
|
+
};
|
|
71
|
+
}export{startRenphoJumpRope};//# sourceMappingURL=renphoJumpRope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"renphoJumpRope.js","sources":["../../../../src/features/devices/brands/renphoJumpRope.ts"],"sourcesContent":["/**\n * RENPHO smart jump rope (PROTOCOLS.md section 9). Alpwise service, rope frames\n * (`cmd lenHi lenLo payload crc16`). The rope streams the live count and, when\n * a session ends, sends a summary Yantra keeps as one workout.\n */\nimport type { DriverContext, DriverHandle, GattLink } from '../drivers';\nimport { readingId, type WorkoutReading } from '../readings';\nimport { parseRopeFrame, ropeFrame, u16be, u32be, type Frame } from './frames';\nimport { RENPHO_ROPE } from './uuids';\n\nexport function startRenphoJumpRope(link: GattLink, ctx: DriverContext): DriverHandle {\n const withoutResponse = link.canWriteWithoutResponse?.(RENPHO_ROPE.service, RENPHO_ROPE.write) ?? false;\n const buf: number[] = [];\n const startedAt = ctx.now();\n\n const send = (cmd: number, payload: number[] = []) =>\n link.write(RENPHO_ROPE.service, RENPHO_ROPE.write, ropeFrame(cmd, payload), { withoutResponse });\n\n const handle = (frame: Frame) => {\n const p = frame.payload;\n switch (frame.cmd) {\n case 0x40: // realtime\n if (p.length >= 11) {\n ctx.onLive?.({ jumps: u32be(p, 5), elapsedS: u16be(p, 3), battery: p[16] });\n }\n break;\n case 0x81: // time reply (battery in byte 1)\n if (p.length >= 2) ctx.onLive?.({ battery: p[1] });\n break;\n case 0xa0: // session summary\n case 0x84: // stored record\n if (p.length >= 11) {\n const unix = u32be(p, 0);\n const at = unix > 1_500_000_000 ? unix * 1000 : startedAt;\n const totalSeconds = u16be(p, 7);\n const reading: WorkoutReading = {\n kind: 'workout',\n id: readingId(link.id, 'workout', at),\n at,\n end: at + totalSeconds * 1000,\n deviceId: link.id,\n deviceName: link.name,\n workoutType: 'jump-rope',\n durationMin: totalSeconds / 60,\n jumps: u16be(p, 9),\n };\n if (reading.jumps) ctx.onReading(reading);\n }\n break;\n default:\n break;\n }\n };\n\n const unsub = link.monitor(RENPHO_ROPE.service, RENPHO_ROPE.notify, (bytes) => {\n for (const b of bytes) buf.push(b);\n for (;;) {\n if (buf.length < 5) return;\n const len = u16be(buf, 1);\n const total = len + 5;\n if (len > 512) {\n buf.shift();\n continue;\n }\n if (buf.length < total) return;\n const frame = parseRopeFrame(Uint8Array.from(buf.slice(0, total)));\n buf.splice(0, total);\n if (frame) handle(frame);\n }\n });\n\n // Set the clock, then ask for any stored sessions.\n const d = new Date(startedAt);\n void send(0x01, [d.getSeconds(), d.getMinutes(), d.getHours(), d.getDate(), d.getMonth() + 1, d.getFullYear() - 2000])\n .then(() => send(0x04, [0x00, 0x00]))\n .catch(() => undefined);\n\n return { stop: unsub };\n}\n"],"names":["_a","_b"],"mappings":"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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import {readingId}from'../readings.js';import {u16le,u32le,ringFrame,le32,le16}from'./frames.js';import {RENPHO_RING}from'./uuids.js';var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
3
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
4
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
5
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
6
|
+
var __spreadValues = (a, b) => {
|
|
7
|
+
for (var prop in b || (b = {}))
|
|
8
|
+
if (__hasOwnProp.call(b, prop))
|
|
9
|
+
__defNormalProp(a, prop, b[prop]);
|
|
10
|
+
if (__getOwnPropSymbols)
|
|
11
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
12
|
+
if (__propIsEnum.call(b, prop))
|
|
13
|
+
__defNormalProp(a, prop, b[prop]);
|
|
14
|
+
}
|
|
15
|
+
return a;
|
|
16
|
+
};
|
|
17
|
+
function encodeSetTime(now) {
|
|
18
|
+
const unix = Math.floor(now / 1e3);
|
|
19
|
+
const tzMinutes = -new Date(now).getTimezoneOffset();
|
|
20
|
+
return ringFrame([1, ...le32(unix), 0, 0, 0, 0, 1, ...le16(tzMinutes & 65535)]);
|
|
21
|
+
}
|
|
22
|
+
function encodeProfile(person) {
|
|
23
|
+
var _a, _b, _c;
|
|
24
|
+
const gender = person.sex === "male" ? 1 : 0;
|
|
25
|
+
const age = (_a = person.age) != null ? _a : 30;
|
|
26
|
+
const heightX10 = Math.round(((_b = person.heightCm) != null ? _b : 170) * 10);
|
|
27
|
+
const weightX100 = Math.round(((_c = person.weightKg) != null ? _c : 60) * 100);
|
|
28
|
+
return ringFrame([2, gender, age, ...le16(heightX10), ...le16(weightX100), 0]);
|
|
29
|
+
}
|
|
30
|
+
function startRenphoRing(link, ctx) {
|
|
31
|
+
var _a, _b;
|
|
32
|
+
const withoutResponse = (_b = (_a = link.canWriteWithoutResponse) == null ? void 0 : _a.call(link, RENPHO_RING.service, RENPHO_RING.write)) != null ? _b : false;
|
|
33
|
+
const send = (bytes) => link.write(RENPHO_RING.service, RENPHO_RING.write, bytes, {
|
|
34
|
+
withoutResponse
|
|
35
|
+
});
|
|
36
|
+
const emitSpo2 = (spo2, at) => {
|
|
37
|
+
if (spo2 <= 0 || spo2 > 100) return;
|
|
38
|
+
const r = {
|
|
39
|
+
kind: "spo2",
|
|
40
|
+
id: readingId(link.id, "spo2", at, spo2),
|
|
41
|
+
at,
|
|
42
|
+
deviceId: link.id,
|
|
43
|
+
deviceName: link.name,
|
|
44
|
+
spo2
|
|
45
|
+
};
|
|
46
|
+
ctx.onReading(r);
|
|
47
|
+
};
|
|
48
|
+
const unsub = link.monitor(RENPHO_RING.service, RENPHO_RING.notify, (p) => {
|
|
49
|
+
var _a2, _b2, _c;
|
|
50
|
+
const cmd = p[0];
|
|
51
|
+
const at = ctx.now();
|
|
52
|
+
if (cmd === 9 && p.length >= 25) {
|
|
53
|
+
const bpm = p[21] || void 0;
|
|
54
|
+
const spo2 = p[24] || void 0;
|
|
55
|
+
const skinTempC = p.length >= 24 ? 36 + (u16le(p, 22) >= 32768 ? u16le(p, 22) - 65536 : u16le(p, 22)) / 10 : void 0;
|
|
56
|
+
(_a2 = ctx.onLive) == null ? void 0 : _a2.call(ctx, __spreadValues(__spreadValues(__spreadValues({
|
|
57
|
+
steps: u32le(p, 1),
|
|
58
|
+
kcal: u32le(p, 5) / 100
|
|
59
|
+
}, bpm ? {
|
|
60
|
+
bpm
|
|
61
|
+
} : {}), spo2 ? {
|
|
62
|
+
spo2
|
|
63
|
+
} : {}), skinTempC ? {
|
|
64
|
+
skinTempC
|
|
65
|
+
} : {}));
|
|
66
|
+
if (spo2) emitSpo2(spo2, at);
|
|
67
|
+
} else if (cmd === 36 && p.length >= 8) {
|
|
68
|
+
const hr = p[4] || void 0;
|
|
69
|
+
(_b2 = ctx.onLive) == null ? void 0 : _b2.call(ctx, __spreadValues(__spreadValues({}, hr ? {
|
|
70
|
+
bpm: hr
|
|
71
|
+
} : {}), {}));
|
|
72
|
+
} else if (cmd === 19 && p.length >= 2) {
|
|
73
|
+
(_c = ctx.onLive) == null ? void 0 : _c.call(ctx, {
|
|
74
|
+
battery: p[1]
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
void send(encodeSetTime(ctx.now())).then(() => send(encodeProfile(ctx.person))).then(() => send(ringFrame([19, 152]))).catch(() => void 0);
|
|
79
|
+
return {
|
|
80
|
+
stop: unsub
|
|
81
|
+
};
|
|
82
|
+
}export{startRenphoRing};//# sourceMappingURL=renphoRing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"renphoRing.js","sources":["../../../../src/features/devices/brands/renphoRing.ts"],"sourcesContent":["/**\n * RENPHO smart ring (PROTOCOLS.md section 12). Service FFF0, write FFF6, notify\n * FFF7; every app command is 16 bytes (command padded, then an 8-bit sum).\n *\n * On connect Yantra sets the ring's clock and the wearer's profile and asks for\n * the battery, then shows the live vitals the ring reports (heart rate, blood\n * oxygen, skin temperature, steps) and keeps the blood-oxygen and temperature\n * point readings. Stored day history (sleep stages, per-minute series) is a\n * follow-up; the realtime path already reads the ring.\n */\nimport type { DriverContext, DriverHandle, GattLink, PersonFacts } from '../drivers';\nimport { readingId, type OxygenReading, type TemperatureReading } from '../readings';\nimport { le16, le32, ringFrame, u16le, u32le } from './frames';\nimport { RENPHO_RING } from './uuids';\n\nfunction encodeSetTime(now: number): number[] {\n const unix = Math.floor(now / 1000);\n const tzMinutes = -new Date(now).getTimezoneOffset();\n return ringFrame([0x01, ...le32(unix), 0, 0, 0, 0, 0x01, ...le16(tzMinutes & 0xffff)]);\n}\n\nfunction encodeProfile(person: PersonFacts): number[] {\n const gender = person.sex === 'male' ? 1 : 0;\n const age = person.age ?? 30;\n const heightX10 = Math.round((person.heightCm ?? 170) * 10);\n const weightX100 = Math.round((person.weightKg ?? 60) * 100);\n return ringFrame([0x02, gender, age, ...le16(heightX10), ...le16(weightX100), 0]);\n}\n\nexport function startRenphoRing(link: GattLink, ctx: DriverContext): DriverHandle {\n const withoutResponse = link.canWriteWithoutResponse?.(RENPHO_RING.service, RENPHO_RING.write) ?? false;\n const send = (bytes: number[]) => link.write(RENPHO_RING.service, RENPHO_RING.write, bytes, { withoutResponse });\n\n const emitSpo2 = (spo2: number, at: number) => {\n if (spo2 <= 0 || spo2 > 100) return;\n const r: OxygenReading = {\n kind: 'spo2',\n id: readingId(link.id, 'spo2', at, spo2),\n at,\n deviceId: link.id,\n deviceName: link.name,\n spo2,\n };\n ctx.onReading(r);\n };\n const emitTemp = (celsius: number, at: number) => {\n if (celsius < 30 || celsius > 45) return;\n const r: TemperatureReading = {\n kind: 'temperature',\n id: readingId(link.id, 'temperature', at, celsius.toFixed(1)),\n at,\n deviceId: link.id,\n deviceName: link.name,\n celsius,\n };\n ctx.onReading(r);\n };\n\n const unsub = link.monitor(RENPHO_RING.service, RENPHO_RING.notify, (p) => {\n const cmd = p[0];\n const at = ctx.now();\n if (cmd === 0x09 && p.length >= 25) {\n // Realtime sport + vitals.\n const bpm = p[21] || undefined;\n const spo2 = p[24] || undefined;\n const skinTempC = p.length >= 24 ? 36 + (u16le(p, 22) >= 0x8000 ? u16le(p, 22) - 0x10000 : u16le(p, 22)) / 10 : undefined;\n ctx.onLive?.({\n steps: u32le(p, 1),\n kcal: u32le(p, 5) / 100,\n ...(bpm ? { bpm } : {}),\n ...(spo2 ? { spo2 } : {}),\n ...(skinTempC ? { skinTempC } : {}),\n });\n if (spo2) emitSpo2(spo2, at);\n } else if (cmd === 0x24 && p.length >= 8) {\n // HRV / stress / blood pressure estimate.\n const hr = p[4] || undefined;\n const spo2 = undefined;\n ctx.onLive?.({ ...(hr ? { bpm: hr } : {}), ...(spo2 ? { spo2 } : {}) });\n } else if (cmd === 0x13 && p.length >= 2) {\n // Battery auto-report.\n ctx.onLive?.({ battery: p[1] });\n }\n });\n\n void send(encodeSetTime(ctx.now()))\n .then(() => send(encodeProfile(ctx.person)))\n .then(() => send(ringFrame([0x13, 0x98])))\n .catch(() => undefined);\n\n return { stop: unsub };\n}\n"],"names":["_a","_b"],"mappings":"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"}
|