From 50219a2b46287c1a373967ac5ff02f9d7af37492 Mon Sep 17 00:00:00 2001 From: Lars Saalbach Date: Tue, 12 Nov 2024 22:16:36 +0100 Subject: [PATCH] #839 - Added first Varia AKU implementation --- src/classes/devices/index.ts | 4 + src/classes/devices/variaAku.ts | 128 ++++++++++++++++++ .../coffee-bluetooth-devices.service.ts | 8 ++ 3 files changed, 140 insertions(+) create mode 100644 src/classes/devices/variaAku.ts diff --git a/src/classes/devices/index.ts b/src/classes/devices/index.ts index 6384c76a..1621aafb 100644 --- a/src/classes/devices/index.ts +++ b/src/classes/devices/index.ts @@ -27,6 +27,7 @@ import { MeaterThermometer } from './meaterThermometer'; import { CombustionThermometer } from './combustionThermometer'; import { ArgosThermometer } from './argosThermometer'; import { TimemoreScale } from './timemoreScale'; +import { VariaAkuScale } from './variaAku'; export { BluetoothScale, SCALE_TIMER_COMMAND } from './bluetoothDevice'; export * from './common'; @@ -52,6 +53,7 @@ export enum ScaleType { DIYRUSTCOFFEESCALE = 'DIYRUSTCOFFEESCALE', BOKOOSCALE = 'BOOKOOSCALE', TIMEMORESCALE = 'TIMEMORESCALE', + VARIA_AKU = 'VARIA_AKU', } export enum PressureType { @@ -106,6 +108,8 @@ export function makeDevice( return new BookooScale(data, type); case ScaleType.TIMEMORESCALE: return new TimemoreScale(data, type); + case ScaleType.VARIA_AKU: + return new VariaAkuScale(data, type); default: return null; } diff --git a/src/classes/devices/variaAku.ts b/src/classes/devices/variaAku.ts new file mode 100644 index 00000000..9e3c1e60 --- /dev/null +++ b/src/classes/devices/variaAku.ts @@ -0,0 +1,128 @@ +import { PeripheralData } from './ble.types'; +import { BluetoothScale, SCALE_TIMER_COMMAND, Weight } from './bluetoothDevice'; +import { Logger } from './common/logger'; +import { ScaleType } from './index'; + +declare var ble: any; +export class VariaAkuScale extends BluetoothScale { + public static DEVICE_NAME = 'varia aku'; + public static SERVICE_UUID = 'FFF0'; + public static CHAR_UUID = 'FFF1'; + public static CMD_UUID = 'FFF2'; + + protected override weight: Weight = { + actual: 0, + old: 0, + smoothed: 0, + oldSmoothed: 0, + notMutatedWeight: 0, + }; + + private logger: Logger; + + constructor(data: PeripheralData, type: ScaleType) { + super(data, type); + this.logger = new Logger('Varia Aku'); + this.connect(); + } + + public static test(device: any): boolean { + return ( + device && + device.name && + device.name.toLowerCase().includes(this.DEVICE_NAME) + ); + } + + public override async connect() { + this.logger.log('connecting...'); + await this.attachNotification(); + } + + private getXOR(_bytes: any) { + return _bytes[0] ^ _bytes[1] ^ _bytes[2]; + } + + public override async tare() { + this.weight.smoothed = 0; + this.weight.actual = 0; + this.weight.oldSmoothed = 0; + this.weight.old = 0; + this.setWeight(0); + + await this.write([0xfa, 0x82, 0x01, 0x01, this.getXOR([0x82, 0x01, 0x01])]); + } + + public override disconnectTriggered(): void { + this.logger.log('Disconnecting...'); + this.deattachNotification(); + } + + public override async setTimer(_timer: SCALE_TIMER_COMMAND) { + this.logger.log('Setting Timer command ' + _timer + '...'); + if (_timer === SCALE_TIMER_COMMAND.START) { + this.write([0xfa, 0x88, 0x01, 0x01, this.getXOR([0x88, 0x01, 0x01])]); + } else if (_timer === SCALE_TIMER_COMMAND.STOP) { + this.write([0xfa, 0x89, 0x01, 0x01, this.getXOR([0x89, 0x01, 0x01])]); + } else if (_timer === SCALE_TIMER_COMMAND.RESET) { + this.write([0xfa, 0x8a, 0x01, 0x01, this.getXOR([0x8a, 0x01, 0x01])]); + } + } + + public override getWeight() { + return this.weight.actual; + } + + public override getSmoothedWeight() { + return this.weight.smoothed; + } + + public override getOldSmoothedWeight() { + return this.weight.old; + } + + private write(_bytes: number[]) { + ble.writeWithoutResponse( + this.device_id, + VariaAkuScale.SERVICE_UUID, + VariaAkuScale.CMD_UUID, + new Uint8Array(_bytes).buffer, + (e: any) => {}, + (e: any) => {} + ); + } + + private async attachNotification() { + ble.startNotification( + this.device_id, + VariaAkuScale.SERVICE_UUID, + VariaAkuScale.CHAR_UUID, + async (_data: any) => { + this.parseStatusUpdate(new Int8Array(_data)); + }, + (_data: any) => {} + ); + } + + private async parseStatusUpdate(rawStatus: Int8Array) { + if (rawStatus[1] === 0x01) { + const sign: number = (rawStatus[3] & 0x10) === 0 ? 1 : -1; + const actualData = + sign * + (((rawStatus[3] & 0x0f) << 16) + (rawStatus[4] << 8) + rawStatus[5]); + if (!isNaN(actualData)) { + this.setWeight(actualData / 100); + } + } + } + + private async deattachNotification() { + ble.stopNotification( + this.device_id, + VariaAkuScale.SERVICE_UUID, + VariaAkuScale.CHAR_UUID, + (e: any) => {}, + (e: any) => {} + ); + } +} diff --git a/src/services/coffeeBluetoothDevices/coffee-bluetooth-devices.service.ts b/src/services/coffeeBluetoothDevices/coffee-bluetooth-devices.service.ts index 0e882ce0..da756b21 100644 --- a/src/services/coffeeBluetoothDevices/coffee-bluetooth-devices.service.ts +++ b/src/services/coffeeBluetoothDevices/coffee-bluetooth-devices.service.ts @@ -45,6 +45,7 @@ import { MeaterThermometer } from 'src/classes/devices/meaterThermometer'; import { CombustionThermometer } from '../../classes/devices/combustionThermometer'; import { ArgosThermometer } from '../../classes/devices/argosThermometer'; import { TimemoreScale } from 'src/classes/devices/timemoreScale'; +import { VariaAkuScale } from '../../classes/devices/variaAku'; declare var ble: any; declare var cordova: any; @@ -720,6 +721,13 @@ export class CoffeeBluetoothDevicesService { type: ScaleType.TIMEMORESCALE, }; } + if (VariaAkuScale.test(deviceScale)) { + this.logger.log('BleManager - We found a Varia AKU scale'); + return { + id: deviceScale.id, + type: ScaleType.VARIA_AKU, + }; + } return undefined; }