OpenHPS/openhps-rf

View on GitHub
src/data/BLEEddystoneTLM.ts

Summary

Maintainability
B
5 hrs
Test Coverage
import {
    SerializableMember,
    SerializableObject,
    Temperature,
    TemperatureUnit,
    TimeUnit,
    UnitValue,
} from '@openhps/core';
import { BLEBeaconBuilder } from './BLEBeaconObject';
import { BLEEddystone } from './BLEEddystone';
import { BLEService } from './BLEService';
import { BLEUUID } from './BLEUUID';

@SerializableObject()
export class BLEEddystoneTLM extends BLEEddystone {
    @SerializableMember()
    version: number;

    /**
     * Voltage in mV
     */
    @SerializableMember()
    voltage: number;

    @SerializableMember()
    temperature?: Temperature;

    @SerializableMember()
    advertiseCount: number;

    @SerializableMember()
    uptime: UnitValue<TimeUnit, number>;

    get encrypted(): boolean {
        return this.version === 0x01;
    }

    isValid(): boolean {
        return super.isValid() && this.frame === 0x20;
    }

    computeUID(): string {
        return this.address ? this.address.toString() : this.uid;
    }

    parseServiceData(uuid: BLEUUID, serviceData: Uint8Array): this {
        super.parseServiceData(uuid, serviceData);
        if (uuid === undefined && serviceData === undefined) {
            return this;
        }

        if (!this.service) {
            return this;
        }

        if (this.frame !== 0x20) {
            return this; // Do not attempt to parse
        }

        const view = new DataView(serviceData.buffer, 0);
        this.version = view.getUint8(1);

        if (this.version === 0x01) {
            // Encrypted
        } else {
            // Unencrypted
            this.voltage = view.getUint16(2);
            const temperatureRaw = view.getInt16(4);
            if (temperatureRaw !== 0x8000) {
                const temperatureSigned = (temperatureRaw & 0x8000) > 0 ? -1 : 1;
                const temperature = (temperatureSigned * temperatureRaw) / Math.pow(2, 8);
                this.temperature = new Temperature(Math.round(temperature * 100) / 100, TemperatureUnit.CELCIUS);
            }
            this.advertiseCount = view.getUint32(6);
            this.uptime = new UnitValue(view.getUint32(10) * 0.1, TimeUnit.SECOND);
        }

        if (this.uid === undefined) {
            this.uid = this.computeUID();
        }
        return this;
    }
}

/**
 * BLE Eddystone TLM builder
 */
export class BLEEddystoneTLMBuilder extends BLEBeaconBuilder<BLEEddystoneTLM> {
    protected constructor() {
        super();
        this.beacon = new BLEEddystoneTLM();
        this.beacon.frame = 0x20;
        this.beacon.version = 0x00;
    }

    static create(): BLEEddystoneTLMBuilder {
        return new BLEEddystoneTLMBuilder();
    }

    static fromBeacon(beacon: BLEEddystoneTLM): BLEEddystoneTLMBuilder {
        const builder = new BLEEddystoneTLMBuilder();
        builder.beacon = beacon;
        return builder;
    }

    calibratedRSSI(rssi: number): this {
        this.beacon.calibratedRSSI = rssi;
        return this;
    }

    voltage(voltage: number): this {
        this.beacon.voltage = voltage;
        return this;
    }

    temperature(temperature: number, unit: TemperatureUnit = TemperatureUnit.CELCIUS): this {
        this.beacon.temperature = new Temperature(temperature, unit);
        return this;
    }

    uptime(time: number, unit: TimeUnit = TimeUnit.SECOND): this {
        this.beacon.uptime = new UnitValue(time, unit);
        return this;
    }

    advertiseCount(count: number): this {
        this.beacon.advertiseCount = count;
        return this;
    }

    build(): Promise<BLEEddystoneTLM> {
        return new Promise((resolve) => {
            // Eddystone Service
            const serviceData = new DataView(new ArrayBuffer(14));
            serviceData.setUint8(0, 0x20); // Eddystone-TLM frame
            serviceData.setInt8(1, 0x00); // Version
            serviceData.setUint16(2, this.beacon.voltage);
            if (this.beacon.temperature) {
                serviceData.setInt16(4, Math.round(this.beacon.temperature.value * 256.0));
            } else {
                serviceData.setInt16(4, 0x8000);
            }

            serviceData.setUint32(6, this.beacon.advertiseCount);
            if (this.beacon.uptime) {
                serviceData.setUint32(10, this.beacon.uptime.to(TimeUnit.SECOND).valueOf() / 0.1);
            }
            this.beacon.addService(new BLEService(BLEUUID.fromString('FEAA'), new Uint8Array(serviceData.buffer)));
            this.beacon.uid = this.beacon.computeUID();
            resolve(this.beacon);
        });
    }
}