Files
frontend/src/data/bluetooth.ts
J. Nick Koston e60f9e326b Show scanner-type-specific power cycle instructions in Bluetooth UI (#26847)
* Show scanner-type-specific power cycle instructions in Bluetooth UI

* preen
2025-09-03 10:03:03 +03:00

200 lines
5.3 KiB
TypeScript

import {
createCollection,
type Connection,
type UnsubscribeFunc,
} from "home-assistant-js-websocket";
import type { Store } from "home-assistant-js-websocket/dist/store";
import type { DataTableRowData } from "../components/data-table/ha-data-table";
export interface BluetoothDeviceData extends DataTableRowData {
address: string;
connectable: boolean;
manufacturer_data: Record<number, string>;
name: string;
rssi: number;
service_data: Record<string, string>;
service_uuids: string[];
source: string;
time: number;
tx_power: number;
}
export interface BluetoothConnectionData extends DataTableRowData {
address: string;
source: string;
}
export type HaScannerType = "usb" | "uart" | "remote" | "unknown";
export interface BluetoothScannerDetails {
source: string;
connectable: boolean;
name: string;
adapter: string;
scanner_type?: HaScannerType;
}
export type BluetoothScannersDetails = Record<string, BluetoothScannerDetails>;
interface BluetoothRemoveDeviceData {
address: string;
}
interface BluetoothAdvertisementSubscriptionMessage {
add?: BluetoothDeviceData[];
change?: BluetoothDeviceData[];
remove?: BluetoothRemoveDeviceData[];
}
interface BluetoothScannersDetailsSubscriptionMessage {
add?: BluetoothScannerDetails[];
remove?: BluetoothScannerDetails[];
}
export interface BluetoothAllocationsData {
source: string;
slots: number;
free: number;
allocated: string[];
}
export interface BluetoothScannerState {
source: string;
adapter: string;
current_mode: "active" | "passive" | null;
requested_mode: "active" | "passive" | null;
}
export const subscribeBluetoothScannersDetailsUpdates = (
conn: Connection,
store: Store<BluetoothScannersDetails>
): Promise<UnsubscribeFunc> =>
conn.subscribeMessage<BluetoothScannersDetailsSubscriptionMessage>(
(event) => {
const data = { ...(store.state || {}) };
if (event.add) {
for (const device_data of event.add) {
data[device_data.source] = device_data;
}
}
if (event.remove) {
for (const device_data of event.remove) {
delete data[device_data.source];
}
}
store.setState(data, true);
},
{
type: `bluetooth/subscribe_scanner_details`,
}
);
export const subscribeBluetoothScannersDetails = (
conn: Connection,
callbackFunction: (bluetoothScannersDetails: BluetoothScannersDetails) => void
) =>
createCollection<BluetoothScannersDetails>(
"_bluetoothScannerDetails",
() => Promise.resolve<BluetoothScannersDetails>({}), // empty hash as initial state
subscribeBluetoothScannersDetailsUpdates,
conn,
callbackFunction
);
const subscribeBluetoothAdvertisementsUpdates = (
conn: Connection,
store: Store<BluetoothDeviceData[]>
): Promise<UnsubscribeFunc> =>
conn.subscribeMessage<BluetoothAdvertisementSubscriptionMessage>(
(event) => {
const data = [...(store.state || [])];
if (event.add) {
for (const device_data of event.add) {
const index = data.findIndex(
(d) => d.address === device_data.address
);
if (index === -1) {
data.push(device_data);
} else {
data[index] = device_data;
}
}
}
if (event.change) {
for (const device_data of event.change) {
const index = data.findIndex(
(d) => d.address === device_data.address
);
if (index !== -1) {
data[index] = device_data;
}
}
}
if (event.remove) {
for (const device_data of event.remove) {
const index = data.findIndex(
(d) => d.address === device_data.address
);
if (index !== -1) {
data.splice(index, 1);
}
}
}
store.setState(data, true);
},
{
type: `bluetooth/subscribe_advertisements`,
}
);
export const subscribeBluetoothAdvertisements = (
conn: Connection,
callbackFunction: (bluetoothDeviceData: BluetoothDeviceData[]) => void
) =>
createCollection<BluetoothDeviceData[]>(
"_bluetoothDeviceRows",
() => Promise.resolve<BluetoothDeviceData[]>([]), // empty array as initial state
subscribeBluetoothAdvertisementsUpdates,
conn,
callbackFunction
);
export const subscribeBluetoothConnectionAllocations = (
conn: Connection,
callbackFunction: (
bluetoothAllocationsData: BluetoothAllocationsData[]
) => void,
configEntryId?: string
): Promise<() => Promise<void>> => {
const params: { type: string; config_entry_id?: string } = {
type: "bluetooth/subscribe_connection_allocations",
};
if (configEntryId) {
params.config_entry_id = configEntryId;
}
return conn.subscribeMessage<BluetoothAllocationsData[]>(
(bluetoothAllocationsData) => callbackFunction(bluetoothAllocationsData),
params
);
};
export const subscribeBluetoothScannerState = (
conn: Connection,
callbackFunction: (scannerState: BluetoothScannerState) => void,
configEntryId?: string
): Promise<() => Promise<void>> => {
const params: { type: string; config_entry_id?: string } = {
type: "bluetooth/subscribe_scanner_state",
};
if (configEntryId) {
params.config_entry_id = configEntryId;
}
return conn.subscribeMessage<BluetoothScannerState>(
(scannerState) => callbackFunction(scannerState),
params
);
};