From cc8869b9f9330a2c7ea0ef5a42970c4ddf301e96 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 28 Jan 2025 13:07:16 -1000 Subject: [PATCH] Show scanner name in the Bluetooth Advertisement Monitor (#23926) --- src/data/bluetooth.ts | 51 +++++++++++++++++++ .../bluetooth-advertisement-monitor.ts | 40 +++++++++++---- 2 files changed, 81 insertions(+), 10 deletions(-) diff --git a/src/data/bluetooth.ts b/src/data/bluetooth.ts index 49d11999d1..59a6e99752 100644 --- a/src/data/bluetooth.ts +++ b/src/data/bluetooth.ts @@ -19,6 +19,15 @@ export interface BluetoothDeviceData extends DataTableRowData { tx_power: number; } +export interface BluetoothScannerDetails { + source: string; + connectable: boolean; + name: string; + adapter: string; +} + +export type BluetoothScannersDetails = Record; + interface BluetoothRemoveDeviceData { address: string; } @@ -29,6 +38,11 @@ interface BluetoothAdvertisementSubscriptionMessage { remove?: BluetoothRemoveDeviceData[]; } +interface BluetoothScannersDetailsSubscriptionMessage { + add?: BluetoothScannerDetails[]; + remove?: BluetoothScannerDetails[]; +} + export interface BluetoothAllocationsData { source: string; slots: number; @@ -36,6 +50,43 @@ export interface BluetoothAllocationsData { allocated: string[]; } +export const subscribeBluetoothScannersDetailsUpdates = ( + conn: Connection, + store: Store +): Promise => + conn.subscribeMessage( + (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( + "_bluetoothScannerDetails", + () => Promise.resolve({}), // empty hash as initial state + + subscribeBluetoothScannersDetailsUpdates, + conn, + callbackFunction + ); + const subscribeBluetoothAdvertisementsUpdates = ( conn: Connection, store: Store diff --git a/src/panels/config/integrations/integration-panels/bluetooth/bluetooth-advertisement-monitor.ts b/src/panels/config/integrations/integration-panels/bluetooth/bluetooth-advertisement-monitor.ts index f874bf0fab..e3d9122a4b 100644 --- a/src/panels/config/integrations/integration-panels/bluetooth/bluetooth-advertisement-monitor.ts +++ b/src/panels/config/integrations/integration-panels/bluetooth/bluetooth-advertisement-monitor.ts @@ -14,9 +14,14 @@ import "../../../../../components/ha-icon-button"; import "../../../../../layouts/hass-tabs-subpage-data-table"; import { haStyle } from "../../../../../resources/styles"; import type { HomeAssistant, Route } from "../../../../../types"; -import type { BluetoothDeviceData } from "../../../../../data/bluetooth"; - -import { subscribeBluetoothAdvertisements } from "../../../../../data/bluetooth"; +import type { + BluetoothDeviceData, + BluetoothScannersDetails, +} from "../../../../../data/bluetooth"; +import { + subscribeBluetoothAdvertisements, + subscribeBluetoothScannersDetails, +} from "../../../../../data/bluetooth"; import { showBluetoothDeviceInfoDialog } from "./show-dialog-bluetooth-device-info"; @customElement("bluetooth-advertisement-monitor") @@ -31,25 +36,39 @@ export class BluetoothAdvertisementMonitorPanel extends LitElement { @state() private _data: BluetoothDeviceData[] = []; - private _unsub?: UnsubscribeFunc; + @state() private _scanners: BluetoothScannersDetails = {}; + + private _unsub_advertisements?: UnsubscribeFunc; + + private _unsub_scanners?: UnsubscribeFunc; public connectedCallback(): void { super.connectedCallback(); if (this.hass) { - this._unsub = subscribeBluetoothAdvertisements( + this._unsub_advertisements = subscribeBluetoothAdvertisements( this.hass.connection, (data) => { this._data = data; } ); + this._unsub_scanners = subscribeBluetoothScannersDetails( + this.hass.connection, + (scanners) => { + this._scanners = scanners; + } + ); } } public disconnectedCallback() { super.disconnectedCallback(); - if (this._unsub) { - this._unsub(); - this._unsub = undefined; + if (this._unsub_advertisements) { + this._unsub_advertisements(); + this._unsub_advertisements = undefined; + } + if (this._unsub_scanners) { + this._unsub_scanners(); + this._unsub_scanners = undefined; } } @@ -88,10 +107,11 @@ export class BluetoothAdvertisementMonitorPanel extends LitElement { } ); - private _dataWithIds = memoizeOne((data) => + private _dataWithNamedSourceAndIds = memoizeOne((data) => data.map((row) => ({ ...row, id: row.address, + source: this._scanners[row.source]?.name || row.source, })) ); @@ -102,7 +122,7 @@ export class BluetoothAdvertisementMonitorPanel extends LitElement { .narrow=${this.narrow} .route=${this.route} .columns=${this._columns(this.hass.localize)} - .data=${this._dataWithIds(this._data)} + .data=${this._dataWithNamedSourceAndIds(this._data)} @row-click=${this._handleRowClicked} clickable >