From 59237e34fbef5683d88c60fb13cd443d814a3680 Mon Sep 17 00:00:00 2001 From: 0x7061 <1425202+0x7061@users.noreply.github.com> Date: Wed, 11 Dec 2024 10:13:09 +0100 Subject: [PATCH] feat: add support for allowDuplicates; introduce DisengageStatusType enum --- README.md | 110 ++++++++++++++++-- .../plugins/capacitor/AbrevvaPluginBLE.kt | 3 +- ios/Plugin/ble/AbrevvaPluginBLE.swift | 5 +- src/plugins/ble/client.spec.ts | 6 +- src/plugins/ble/client.ts | 38 ++++-- src/plugins/ble/definitions.ts | 27 +++++ 6 files changed, 160 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 3800eba..998845c 100644 --- a/README.md +++ b/README.md @@ -54,40 +54,125 @@ npx cap sync import { AbrevvaBLEClient, ScanResult } from "@evva/abrevva-capacitor"; class ExampleClass { - private results: ScanResult[]; + private devices: BleDevice[]; async startScan(event: any) { - this.results = []; - - await AbrevvaBLEClient.requestLEScan({ timeout: 5_000 }, (result: ScanResult) => { - this.results.push(result); + this.devices = []; + + await AbrevvaBLEClient.initialize() + await AbrevvaBLEClient.startScan({ timeout: 5_000 }, (device: BleDevice) => { + this.devices.push(device); + }, (success: boolean) => { + console.log(`Scan started, success: ${success}`); + }, (success: boolean) => { + console.log(`Scan stopped, success: ${success}`); }); } } ``` +### Read EVVA component advertisement + +Get the EVVA advertisement data from a scanned EVVA component. + +```typescript +const ad = device.advertisementData +console.log(ad?.rssi) +console.log(ad?.isConnectable) + +const md = ad?.manufacturerData +console.log(md?.batteryStatus) +console.log(md?.isOnline) +console.log(md?.officeModeEnabled) +console.log(md?.officeModeActive) +// ... +``` + +There are several properties that can be accessed from the advertisement. + +```typescript +export interface BleDeviceAdvertisementData { + rssi?: number; + isConnectable?: boolean; + manufacturerData?: BleDeviceManufacturerData; +} + +export interface BleDeviceManufacturerData { + companyIdentifier?: string; + version?: number; + componentType?: "handle" | "escutcheon" | "cylinder" | "wallreader" | "emzy" | "iobox" | "unknown"; + mainFirmwareVersionMajor?: number; + mainFirmwareVersionMinor?: number; + mainFirmwareVersionPatch?: number; + componentHAL?: string; + batteryStatus?: "battery-full" | "battery-empty"; + mainConstructionMode?: boolean; + subConstructionMode?: boolean; + isOnline?: boolean; + officeModeEnabled?: boolean; + twoFactorRequired?: boolean; + officeModeActive?: boolean; + identifier?: string; + subFirmwareVersionMajor?: number; + subFirmwareVersionMinor?: number; + subFirmwareVersionPatch?: number; + subComponentIdentifier?: string; +} +``` + ### Localize EVVA component -With the signalize method you can localize EVVA components. On a successful signalization the component will emit a melody indicating its location. +With the signalize method you can localize scanned EVVA components. On a successful signalization the component will emit a melody indicating its location. ```typescript const success = await AbrevvaBLEClient.signalize('deviceId'); ``` -### Perform disengage on EVVA components +### Disengage EVVA components For the component disengage you have to provide access credentials to the EVVA component. Those are generally acquired in the form of access media metadata from the Xesar software. ```typescript const status = await AbrevvaBLEClient.disengage( + 'deviceId', 'mobileId', 'mobileDeviceKey', 'mobileGroupId', - 'mobileAccessData', + 'mediumAccessData', false, ); ``` +There are several access status types upon attempting the component disengage. + +```typescript +export enum DisengageStatusType { + /// Component + Authorized = "AUTHORIZED", + AuthorizedPermanentEngage = "AUTHORIZED_PERMANENT_ENGAGE", + AuthorizedPermanentDisengage = "AUTHORIZED_PERMANENT_DISENGAGE", + AuthorizedBatteryLow = "AUTHORIZED_BATTERY_LOW", + AuthorizedOffline = "AUTHORIZED_OFFLINE", + Unauthorized = "UNAUTHORIZED", + UnauthorizedOffline = "UNAUTHORIZED_OFFLINE", + SignalLocalization = "SIGNAL_LOCALIZATION", + MediumDefectOnline = "MEDIUM_DEFECT_ONLINE", + MediumBlacklisted = "MEDIUM_BLACKLISTED", + Error = "ERROR", + + /// Interface + UnableToConnect = "UNABLE_TO_CONNECT", + UnableToSetNotifications = "UNABLE_TO_SET_NOTIFICATIONS", + UnableToReadChallenge = "UNABLE_TO_READ_CHALLENGE", + UnableToWriteMDF = "UNABLE_TO_WRITE_MDF", + AccessCipherError = "ACCESS_CIPHER_ERROR", + BleAdapterDisabled = "BLE_ADAPTER_DISABLED", + UnknownDevice = "UNKNOWN_DEVICE", + UnknownStatusCode = "UNKNOWN_STATUS_CODE", + Timeout = "TIMEOUT", +} +``` + ## API @@ -147,10 +232,11 @@ const status = await AbrevvaBLEClient.disengage( #### BleScannerOptions -| Prop | Type | -| --------------- | ------------------- | -| **`macFilter`** | string | -| **`timeout`** | number | +| Prop | Type | +| --------------------- | -------------------- | +| **`macFilter`** | string | +| **`allowDuplicates`** | boolean | +| **`timeout`** | number | #### PluginListenerHandle diff --git a/android/src/main/java/com/evva/xesar/abrevva/plugins/capacitor/AbrevvaPluginBLE.kt b/android/src/main/java/com/evva/xesar/abrevva/plugins/capacitor/AbrevvaPluginBLE.kt index 4ff0b02..f022445 100644 --- a/android/src/main/java/com/evva/xesar/abrevva/plugins/capacitor/AbrevvaPluginBLE.kt +++ b/android/src/main/java/com/evva/xesar/abrevva/plugins/capacitor/AbrevvaPluginBLE.kt @@ -199,6 +199,7 @@ class AbrevvaPluginBLE : Plugin() { @PluginMethod fun startScan(call: PluginCall) { val macFilter = call.getString("macFilter", null) + val allowDuplicates = call.getBoolean("allowDuplicates", false) val timeout = call.getFloat("timeout", 15000.0F)!!.toLong() this.manager.startScan({ device -> @@ -219,7 +220,7 @@ class AbrevvaPluginBLE : Plugin() { data.put("value", success) notifyListeners("onScanStop", data) call.resolve() - }, macFilter, false, timeout) + }, macFilter, allowDuplicates, timeout) } @PluginMethod diff --git a/ios/Plugin/ble/AbrevvaPluginBLE.swift b/ios/Plugin/ble/AbrevvaPluginBLE.swift index 7cdbd32..2946dd2 100644 --- a/ios/Plugin/ble/AbrevvaPluginBLE.swift +++ b/ios/Plugin/ble/AbrevvaPluginBLE.swift @@ -82,13 +82,14 @@ public class AbrevvaPluginBLE: CAPPlugin { func startScan(_ call: CAPPluginCall) { guard let bleManager = self.getBleManager(call) else { return } let macFilter = call.getString("macFilter") + let allowDuplicates = call.getBool("allowDuplicates") ?? false let timeout = call.getDouble("timeout").map { Int($0) } ?? nil bleManager.startScan( { device in self.bleDeviceMap[device.getAddress()] = device let data = self.getAdvertismentData(device) - self.notifyListeners("onScanResult", data: data) + self.notifyListeners("onScanResult", data: data as [String: Any]) }, { error in self.notifyListeners("onScanStart", data: ["value": error == nil]) @@ -99,7 +100,7 @@ public class AbrevvaPluginBLE: CAPPlugin { call.resolve() }, macFilter, - false, + allowDuplicates, timeout ) } diff --git a/src/plugins/ble/client.spec.ts b/src/plugins/ble/client.spec.ts index 4cea663..9f0e416 100644 --- a/src/plugins/ble/client.spec.ts +++ b/src/plugins/ble/client.spec.ts @@ -5,7 +5,7 @@ import { Capacitor } from "@capacitor/core"; import type { AbrevvaBLEClientInterface } from "./client"; import { AbrevvaBLEClient } from "./client"; import { hexStringToDataView, numbersToDataView } from "./conversion"; -import type { BleDevice } from "./definitions"; +import { BleDevice, DisengageStatusType } from "./definitions"; import { AbrevvaBLE } from "./plugin"; interface AbrevvaBLEClientWithPrivate extends AbrevvaBLEClientInterface { @@ -274,7 +274,7 @@ describe("AbrevvaBLEClient", () => { expect(Capacitor.getPlatform()).toBe("android"); (AbrevvaBLE.disengage as jest.Mock).mockReturnValue({ - value: "ACCESS_STATUS_AUTHORIZED", + value: DisengageStatusType.Authorized, }); const result = await AbrevvaBLEClient.disengage(mockDevice.deviceId, "", "", "", "", false); expect(AbrevvaBLE.disengage).toHaveBeenCalledWith({ @@ -285,7 +285,7 @@ describe("AbrevvaBLEClient", () => { mediumAccessData: "", isPermanentRelease: false, }); - expect(result).toEqual("ACCESS_STATUS_AUTHORIZED"); + expect(result).toEqual(DisengageStatusType.Authorized); }); it("should run startNotifications", async () => { diff --git a/src/plugins/ble/client.ts b/src/plugins/ble/client.ts index 815172e..944114a 100644 --- a/src/plugins/ble/client.ts +++ b/src/plugins/ble/client.ts @@ -1,7 +1,15 @@ import type { PluginListenerHandle } from "@capacitor/core"; import { dataViewToHexString, hexStringToDataView } from "./conversion"; -import type { InitializeOptions, TimeoutOptions, ReadResult, BleScannerOptions, BleDevice, Data } from "./definitions"; +import { + BleDevice, + BleScannerOptions, + Data, + DisengageStatusType, + InitializeOptions, + ReadResult, + TimeoutOptions, +} from "./definitions"; import { AbrevvaBLE } from "./plugin"; import { getQueue } from "./queue"; import { validateUUID } from "./validators"; @@ -227,7 +235,7 @@ class AbrevvaBLEClientClass implements AbrevvaBLEClientInterface { isPermanentRelease: boolean, onConnect?: (address: string) => void, onDisconnect?: (address: string) => void, - ): Promise { + ): Promise { return await this.queue(async () => { if (onConnect) { await this.eventListeners.get(`connected|${deviceId}`)?.remove(); @@ -248,16 +256,24 @@ class AbrevvaBLEClientClass implements AbrevvaBLEClientInterface { ); } - const result = await AbrevvaBLE.disengage({ - deviceId, - mobileId, - mobileDeviceKey, - mobileGroupId, - mediumAccessData, - isPermanentRelease, - }); + const status = ( + await AbrevvaBLE.disengage({ + deviceId, + mobileId, + mobileDeviceKey, + mobileGroupId, + mediumAccessData, + isPermanentRelease, + }) + ).value; - return result.value; + let result: DisengageStatusType; + if (Object.values(DisengageStatusType).some((val: string) => val === status)) { + result = status; + } else { + result = DisengageStatusType.Error; + } + return result; }); } diff --git a/src/plugins/ble/definitions.ts b/src/plugins/ble/definitions.ts index 96d23d1..7e56377 100644 --- a/src/plugins/ble/definitions.ts +++ b/src/plugins/ble/definitions.ts @@ -24,6 +24,7 @@ export interface TimeoutOptions { export interface BleScannerOptions { macFilter?: string; + allowDuplicates?: boolean; timeout?: number; } @@ -91,6 +92,32 @@ export interface DisengageOptions { isPermanentRelease: boolean; } +export enum DisengageStatusType { + /// Component + Authorized = "AUTHORIZED", + AuthorizedPermanentEngage = "AUTHORIZED_PERMANENT_ENGAGE", + AuthorizedPermanentDisengage = "AUTHORIZED_PERMANENT_DISENGAGE", + AuthorizedBatteryLow = "AUTHORIZED_BATTERY_LOW", + AuthorizedOffline = "AUTHORIZED_OFFLINE", + Unauthorized = "UNAUTHORIZED", + UnauthorizedOffline = "UNAUTHORIZED_OFFLINE", + SignalLocalization = "SIGNAL_LOCALIZATION", + MediumDefectOnline = "MEDIUM_DEFECT_ONLINE", + MediumBlacklisted = "MEDIUM_BLACKLISTED", + Error = "ERROR", + + /// Interface + UnableToConnect = "UNABLE_TO_CONNECT", + UnableToSetNotifications = "UNABLE_TO_SET_NOTIFICATIONS", + UnableToReadChallenge = "UNABLE_TO_READ_CHALLENGE", + UnableToWriteMDF = "UNABLE_TO_WRITE_MDF", + AccessCipherError = "ACCESS_CIPHER_ERROR", + BleAdapterDisabled = "BLE_ADAPTER_DISABLED", + UnknownDevice = "UNKNOWN_DEVICE", + UnknownStatusCode = "UNKNOWN_STATUS_CODE", + Timeout = "TIMEOUT", +} + export interface AbrevvaBLEInterface { initialize(options?: InitializeOptions): Promise; isEnabled(): Promise;