Show scanner-type-specific power cycle instructions in Bluetooth UI (#26847)

* Show scanner-type-specific power cycle instructions in Bluetooth UI

* preen
pull/26849/head
J. Nick Koston 2025-09-03 02:03:03 -05:00 committed by GitHub
parent ba39d189e7
commit e60f9e326b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 87 additions and 34 deletions

View File

@ -24,11 +24,14 @@ export interface BluetoothConnectionData extends DataTableRowData {
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>;

View File

@ -14,16 +14,19 @@ import type { HomeAssistant } from "../../../../../types";
import {
subscribeBluetoothConnectionAllocations,
subscribeBluetoothScannerState,
subscribeBluetoothScannersDetails,
} from "../../../../../data/bluetooth";
import type {
BluetoothAllocationsData,
BluetoothScannerState,
BluetoothScannersDetails,
HaScannerType,
} from "../../../../../data/bluetooth";
import {
getValueInPercentage,
roundWithOneDecimal,
} from "../../../../../util/calculate";
import "../../../../../components/ha-metric";
import type {
BluetoothAllocationsData,
BluetoothScannerState,
} from "../../../../../data/bluetooth";
@customElement("bluetooth-config-dashboard")
export class BluetoothConfigDashboard extends LitElement {
@ -37,6 +40,8 @@ export class BluetoothConfigDashboard extends LitElement {
@state() private _scannerState?: BluetoothScannerState;
@state() private _scannerDetails?: BluetoothScannersDetails;
private _configEntry = new URLSearchParams(window.location.search).get(
"config_entry"
);
@ -45,11 +50,14 @@ export class BluetoothConfigDashboard extends LitElement {
private _unsubScannerState?: (() => Promise<void>) | undefined;
private _unsubScannerDetails?: (() => void) | undefined;
public connectedCallback(): void {
super.connectedCallback();
if (this.hass) {
this._subscribeBluetoothConnectionAllocations();
this._subscribeBluetoothScannerState();
this._subscribeScannerDetails();
}
}
@ -85,6 +93,18 @@ export class BluetoothConfigDashboard extends LitElement {
);
}
private _subscribeScannerDetails(): void {
if (this._unsubScannerDetails) {
return;
}
this._unsubScannerDetails = subscribeBluetoothScannersDetails(
this.hass.connection,
(details) => {
this._scannerDetails = details;
}
);
}
public disconnectedCallback() {
super.disconnectedCallback();
if (this._unsubConnectionAllocations) {
@ -95,6 +115,10 @@ export class BluetoothConfigDashboard extends LitElement {
this._unsubScannerState();
this._unsubScannerState = undefined;
}
if (this._unsubScannerDetails) {
this._unsubScannerDetails();
this._unsubScannerDetails = undefined;
}
}
protected render(): TemplateResult {
@ -171,6 +195,51 @@ export class BluetoothConfigDashboard extends LitElement {
private _getUsedAllocations = (used: number, total: number) =>
roundWithOneDecimal(getValueInPercentage(used, 0, total));
private _renderScannerMismatchWarning(
scannerState: BluetoothScannerState,
scannerType: HaScannerType,
formatMode: (mode: string | null) => string
) {
const instructions: string[] = [];
if (scannerType === "remote" || scannerType === "unknown") {
instructions.push(
this.hass.localize(
"ui.panel.config.bluetooth.scanner_mode_mismatch_remote"
)
);
}
if (scannerType === "usb" || scannerType === "unknown") {
instructions.push(
this.hass.localize(
"ui.panel.config.bluetooth.scanner_mode_mismatch_usb"
)
);
}
if (scannerType === "uart" || scannerType === "unknown") {
instructions.push(
this.hass.localize(
"ui.panel.config.bluetooth.scanner_mode_mismatch_uart"
)
);
}
return html`<ha-alert alert-type="warning">
<div>
${this.hass.localize(
"ui.panel.config.bluetooth.scanner_mode_mismatch",
{
requested: formatMode(scannerState.requested_mode),
current: formatMode(scannerState.current_mode),
}
)}
</div>
<ul>
${instructions.map((instruction) => html`<li>${instruction}</li>`)}
</ul>
</ha-alert>`;
}
private _renderScannerState() {
if (!this._configEntry || !this._scannerState) {
return html`<div>
@ -181,6 +250,10 @@ export class BluetoothConfigDashboard extends LitElement {
}
const scannerState = this._scannerState;
// Find the scanner details for this source
const scannerDetails = this._scannerDetails?.[scannerState.source];
const scannerType: HaScannerType =
scannerDetails?.scanner_type ?? "unknown";
const formatMode = (mode: string | null) => {
switch (mode) {
@ -224,34 +297,11 @@ export class BluetoothConfigDashboard extends LitElement {
>
</div>
${scannerState.current_mode !== scannerState.requested_mode
? html`<ha-alert alert-type="warning">
<div>
${this.hass.localize(
"ui.panel.config.bluetooth.scanner_mode_mismatch",
{
requested: formatMode(scannerState.requested_mode),
current: formatMode(scannerState.current_mode),
}
)}
</div>
<ul>
<li>
${this.hass.localize(
"ui.panel.config.bluetooth.scanner_mode_mismatch_proxy"
)}
</li>
<li>
${this.hass.localize(
"ui.panel.config.bluetooth.scanner_mode_mismatch_usb"
)}
</li>
<li>
${this.hass.localize(
"ui.panel.config.bluetooth.scanner_mode_mismatch_onboard"
)}
</li>
</ul>
</ha-alert>`
? this._renderScannerMismatchWarning(
scannerState,
scannerType,
formatMode
)
: nothing}
</div>
`;

View File

@ -5748,9 +5748,9 @@
"scanning_mode_active": "active",
"scanning_mode_passive": "passive",
"scanner_mode_mismatch": "Scanner requested {requested} mode but is operating in {current} mode. The scanner is in a bad state and needs to be power cycled.",
"scanner_mode_mismatch_proxy": "For proxies: reboot the device",
"scanner_mode_mismatch_remote": "For proxies: reboot the device",
"scanner_mode_mismatch_usb": "For USB adapters: unplug and plug back in",
"scanner_mode_mismatch_onboard": "For onboard adapters: power down the system completely and power it back up",
"scanner_mode_mismatch_uart": "For UART/onboard adapters: power down the system completely and power it back up",
"address": "Address",
"name": "Name",
"source": "Source",