mirror of
https://github.com/home-assistant/frontend.git
synced 2025-04-19 10:57:19 +00:00
Implement new Z-Wave add device flow (#24667)
Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
This commit is contained in:
parent
c73a9fccb8
commit
933fb1327a
15
public/static/images/z-wave-add-node/long-range.svg
Normal file
15
public/static/images/z-wave-add-node/long-range.svg
Normal file
@ -0,0 +1,15 @@
|
||||
<svg width="94" height="72" viewBox="0 0 94 72" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M76.9105 39.4999C77.739 39.4999 78.4105 38.8283 78.4105 37.9999C78.4105 37.1715 77.739 36.4999 76.9105 36.4999V39.4999ZM37.5 39.4999L76.9105 39.4999V36.4999L37.5 36.4999L37.5 39.4999Z" fill="#00AFFF" fill-opacity="0.3"/>
|
||||
<path d="M30.8239 22.3365L38.8239 38.8365L30.3239 50.3365" stroke="black" stroke-opacity="0.12" stroke-width="3" stroke-linecap="round"/>
|
||||
<mask id="mask0_1110_23734" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="30" y="27" width="18" height="18">
|
||||
<path d="M45.75 42.075C45.75 42.4462 45.4462 42.75 45.075 42.75H32.925C32.5538 42.75 32.25 42.4462 32.25 42.075V36.675C32.25 36.3037 32.4649 35.7851 32.7276 35.5224L38.5224 29.7275C38.7851 29.4649 39.2143 29.4649 39.477 29.7275L45.2724 35.523C45.5351 35.7857 45.75 36.3043 45.75 36.6755V42.075Z" fill="black"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_1110_23734)">
|
||||
<rect x="30" y="27" width="18" height="18" fill="#212121"/>
|
||||
</g>
|
||||
<path d="M82 37.9999C82 36.343 83.3431 34.9999 85 34.9999C86.6569 34.9999 88 36.343 88 37.9999C88 39.6567 86.6569 40.9999 85 40.9999C83.3431 40.9999 82 39.6567 82 37.9999Z" stroke="#00AFFF" stroke-width="2"/>
|
||||
<rect x="23" y="11" width="8" height="8" rx="4" fill="black" fill-opacity="0.32"/>
|
||||
<rect x="22" y="52" width="8" height="8" rx="4" fill="black" fill-opacity="0.32"/>
|
||||
<path d="M21.5715 19.5C17.4983 23.801 15 29.6087 15 36C15 41.9085 17.1351 47.3183 20.6759 51.5" stroke="black" stroke-opacity="0.12" stroke-width="3" stroke-linecap="round"/>
|
||||
<circle cx="39" cy="36" r="33.5" stroke="black" stroke-opacity="0.12"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
15
public/static/images/z-wave-add-node/long-range_dark.svg
Normal file
15
public/static/images/z-wave-add-node/long-range_dark.svg
Normal file
@ -0,0 +1,15 @@
|
||||
<svg width="94" height="72" viewBox="0 0 94 72" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M30.824 22.3365L38.824 38.8365L30.324 50.3365" stroke="white" stroke-opacity="0.24" stroke-width="3" stroke-linecap="round"/>
|
||||
<mask id="mask0_1180_4955" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="30" y="27" width="18" height="18">
|
||||
<path d="M45.75 42.075C45.75 42.4462 45.4462 42.75 45.075 42.75H32.925C32.5538 42.75 32.25 42.4462 32.25 42.075V36.675C32.25 36.3037 32.4649 35.7851 32.7276 35.5224L38.5224 29.7275C38.7851 29.4649 39.2143 29.4649 39.477 29.7275L45.2724 35.523C45.5351 35.7857 45.75 36.3043 45.75 36.6755V42.075Z" fill="black"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_1180_4955)">
|
||||
<rect x="30" y="27" width="18" height="18" fill="#00AFFF"/>
|
||||
</g>
|
||||
<path d="M76.9105 39.4999C77.739 39.4999 78.4105 38.8283 78.4105 37.9999C78.4105 37.1715 77.739 36.4999 76.9105 36.4999V39.4999ZM37.5 39.4999L76.9105 39.4999V36.4999L37.5 36.4999L37.5 39.4999Z" fill="#00AFFF" fill-opacity="0.3"/>
|
||||
<path d="M82 37.9999C82 36.343 83.3431 34.9999 85 34.9999C86.6569 34.9999 88 36.343 88 37.9999C88 39.6567 86.6569 40.9999 85 40.9999C83.3431 40.9999 82 39.6567 82 37.9999Z" stroke="#00AFFF" stroke-width="2"/>
|
||||
<rect x="23" y="11" width="8" height="8" rx="4" fill="white" fill-opacity="0.48"/>
|
||||
<rect x="22" y="52" width="8" height="8" rx="4" fill="white" fill-opacity="0.48"/>
|
||||
<path d="M21.5715 19.5C17.4983 23.801 15 29.6087 15 36C15 41.9085 17.1351 47.3183 20.6759 51.5" stroke="white" stroke-opacity="0.24" stroke-width="3" stroke-linecap="round"/>
|
||||
<circle cx="39" cy="36" r="33.5" stroke="white" stroke-opacity="0.24"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
19
public/static/images/z-wave-add-node/mesh.svg
Normal file
19
public/static/images/z-wave-add-node/mesh.svg
Normal file
@ -0,0 +1,19 @@
|
||||
<svg width="94" height="72" viewBox="0 0 94 72" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M63.1358 38.5084C63.9608 38.4334 64.5688 37.7037 64.4938 36.8787C64.4188 36.0537 63.6892 35.4457 62.8642 35.5207L63.1358 38.5084ZM46.6358 40.0084L63.1358 38.5084L62.8642 35.5207L46.3642 37.0207L46.6358 40.0084Z" fill="#00AFFF" fill-opacity="0.3"/>
|
||||
<path d="M38.5 22L45.9722 37.4115C46.2967 38.0807 46.223 38.8747 45.781 39.4728L38 50" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<circle cx="47" cy="36" r="34" fill="white"/>
|
||||
<circle cx="47" cy="36" r="33.5" stroke="black" stroke-opacity="0.12"/>
|
||||
<path d="M41.8777 12.5216C43.4905 12.1798 45.1631 12 46.8777 12C58.2401 12 67.7582 19.8959 70.2445 30.5M40 59C42.1788 59.6506 44.4874 60 46.8777 60C56.9498 60 65.5728 53.7955 69.1332 45" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<path d="M38.5 22L45.9722 37.4115C46.2967 38.0807 46.223 38.8747 45.781 39.4728L38 50" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<mask id="mask0_1110_23775" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="38" y="27" width="18" height="18">
|
||||
<path d="M53.75 42.075C53.75 42.4462 53.4462 42.75 53.075 42.75H40.925C40.5538 42.75 40.25 42.4462 40.25 42.075V36.675C40.25 36.3037 40.4649 35.7851 40.7276 35.5224L46.5224 29.7275C46.7851 29.4649 47.2143 29.4649 47.477 29.7275L53.2724 35.523C53.5351 35.7857 53.75 36.3043 53.75 36.6755V42.075Z" fill="black"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_1110_23775)">
|
||||
<rect x="38" y="27" width="18" height="18" fill="#212121"/>
|
||||
</g>
|
||||
<path d="M63.5 39.4999C64.3284 39.4999 65 38.8283 65 37.9999C65 37.1715 64.3284 36.4999 63.5 36.4999L63.5 39.4999ZM49.5 39.4999L63.5 39.4999L63.5 36.4999L49.5 36.4999L49.5 39.4999Z" fill="#00AFFF" fill-opacity="0.3"/>
|
||||
<rect x="31" y="11" width="8" height="8" rx="4" fill="#00AFFF" fill-opacity="0.6"/>
|
||||
<rect x="30" y="52" width="8" height="8" rx="4" fill="#00AFFF" fill-opacity="0.6"/>
|
||||
<path d="M29.5715 19.5C25.4983 23.801 23 29.6087 23 36C23 41.9085 25.1351 47.3183 28.6759 51.5" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<path d="M68 37.9999C68 36.343 69.3431 34.9999 71 34.9999C72.6569 34.9999 74 36.343 74 37.9999C74 39.6567 72.6569 40.9999 71 40.9999C69.3431 40.9999 68 39.6567 68 37.9999Z" stroke="#00AFFF" stroke-width="2"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
19
public/static/images/z-wave-add-node/mesh_dark.svg
Normal file
19
public/static/images/z-wave-add-node/mesh_dark.svg
Normal file
@ -0,0 +1,19 @@
|
||||
<svg width="94" height="72" viewBox="0 0 94 72" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M63.1358 38.5084C63.9608 38.4334 64.5688 37.7037 64.4938 36.8787C64.4188 36.0537 63.6892 35.4457 62.8642 35.5207L63.1358 38.5084ZM46.6358 40.0084L63.1358 38.5084L62.8642 35.5207L46.3642 37.0207L46.6358 40.0084Z" fill="#00AFFF" fill-opacity="0.3"/>
|
||||
<path d="M38.5 22L45.9722 37.4115C46.2967 38.0807 46.223 38.8747 45.781 39.4728L38 50" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<circle cx="47" cy="36" r="34" fill="#1C1C1C"/>
|
||||
<circle cx="47" cy="36" r="33.5" stroke="white" stroke-opacity="0.24"/>
|
||||
<path d="M41.8777 12.5216C43.4905 12.1798 45.1631 12 46.8777 12C58.2401 12 67.7582 19.8959 70.2445 30.5M40 59C42.1788 59.6506 44.4874 60 46.8777 60C56.9498 60 65.5728 53.7955 69.1332 45" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<path d="M38.5 22L45.9722 37.4115C46.2967 38.0807 46.223 38.8747 45.781 39.4728L38 50" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<mask id="mask0_1180_4965" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="38" y="27" width="18" height="18">
|
||||
<path d="M53.75 42.075C53.75 42.4462 53.4462 42.75 53.075 42.75H40.925C40.5538 42.75 40.25 42.4462 40.25 42.075V36.675C40.25 36.3037 40.4649 35.7851 40.7276 35.5224L46.5224 29.7275C46.7851 29.4649 47.2143 29.4649 47.477 29.7275L53.2724 35.523C53.5351 35.7857 53.75 36.3043 53.75 36.6755V42.075Z" fill="black"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_1180_4965)">
|
||||
<rect x="38" y="27" width="18" height="18" fill="#00AFFF"/>
|
||||
</g>
|
||||
<path d="M63.5 39.4999C64.3284 39.4999 65 38.8283 65 37.9999C65 37.1715 64.3284 36.4999 63.5 36.4999L63.5 39.4999ZM49.5 39.4999L63.5 39.4999L63.5 36.4999L49.5 36.4999L49.5 39.4999Z" fill="#00AFFF" fill-opacity="0.3"/>
|
||||
<rect x="31" y="11" width="8" height="8" rx="4" fill="#00AFFF" fill-opacity="0.6"/>
|
||||
<rect x="30" y="52" width="8" height="8" rx="4" fill="#00AFFF" fill-opacity="0.6"/>
|
||||
<path d="M29.5715 19.5C25.4983 23.801 23 29.6087 23 36C23 41.9085 25.1351 47.3183 28.6759 51.5" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<path d="M68 37.9999C68 36.343 69.3431 34.9999 71 34.9999C72.6569 34.9999 74 36.343 74 37.9999C74 39.6567 72.6569 40.9999 71 40.9999C69.3431 40.9999 68 39.6567 68 37.9999Z" stroke="#00AFFF" stroke-width="2"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
@ -5,7 +5,7 @@ import { getIntegrationDescriptions } from "../../data/integrations";
|
||||
import { showConfigFlowDialog } from "../../dialogs/config-flow/show-dialog-config-flow";
|
||||
import { showConfirmationDialog } from "../../dialogs/generic/show-dialog-box";
|
||||
import { showMatterAddDeviceDialog } from "../../panels/config/integrations/integration-panels/matter/show-dialog-add-matter-device";
|
||||
import { showZWaveJSAddNodeDialog } from "../../panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-add-node";
|
||||
import { showZWaveJSAddNodeDialog } from "../../panels/config/integrations/integration-panels/zwave_js/add-node/show-dialog-zwave_js-add-node";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { documentationUrl } from "../../util/documentation-url";
|
||||
import { isComponentLoaded } from "../config/is_component_loaded";
|
||||
|
@ -1,7 +1,5 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import { mdiCamera } from "@mdi/js";
|
||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
// The BarcodeDetector Web API is not yet supported in all browsers,
|
||||
@ -12,12 +10,13 @@ import { prepareZXingModule } from "barcode-detector";
|
||||
import type QrScanner from "qr-scanner";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||
import type { LocalizeFunc } from "../common/translations/localize";
|
||||
import { addExternalBarCodeListener } from "../external_app/external_app_entrypoint";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-alert";
|
||||
import "./ha-button";
|
||||
import "./ha-button-menu";
|
||||
import "./ha-list-item";
|
||||
import "./ha-spinner";
|
||||
import "./ha-textfield";
|
||||
import type { HaTextField } from "./ha-textfield";
|
||||
|
||||
@ -36,18 +35,22 @@ prepareZXingModule({
|
||||
class HaQrScanner extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public localize!: LocalizeFunc;
|
||||
|
||||
@property() public description?: string;
|
||||
|
||||
@property({ attribute: "alternative_option_label" })
|
||||
public alternativeOptionLabel?: string;
|
||||
|
||||
@property() public error?: string;
|
||||
@property({ attribute: false }) public validate?: (
|
||||
value: string
|
||||
) => string | undefined;
|
||||
|
||||
@state() private _cameras?: QrScanner.Camera[];
|
||||
|
||||
@state() private _manual = false;
|
||||
@state() private _loading = true;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
@state() private _warning?: string;
|
||||
|
||||
private _qrScanner?: QrScanner;
|
||||
|
||||
@ -88,29 +91,40 @@ class HaQrScanner extends LitElement {
|
||||
this._loadQrScanner();
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
if (changedProps.has("error") && this.error) {
|
||||
alert(`error: ${this.error}`);
|
||||
this._notifyExternalScanner(this.error);
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (this._nativeBarcodeScanner && !this._manual) {
|
||||
if (this._nativeBarcodeScanner) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`${this.error
|
||||
? html`<ha-alert alert-type="error">${this.error}</ha-alert>`
|
||||
: ""}
|
||||
${navigator.mediaDevices && !this._manual
|
||||
return html`${this._error || this._warning
|
||||
? html`<ha-alert
|
||||
.alertType=${this._error ? "error" : "warning"}
|
||||
class=${this._error ? "" : "warning"}
|
||||
>
|
||||
${this._error || this._warning}
|
||||
${this._error
|
||||
? html` <ha-button @click=${this._retry} slot="action">
|
||||
${this.hass.localize("ui.components.qr-scanner.retry")}
|
||||
</ha-button>`
|
||||
: nothing}
|
||||
</ha-alert>`
|
||||
: nothing}
|
||||
${navigator.mediaDevices
|
||||
? html`<video></video>
|
||||
<div id="canvas-container">
|
||||
${this._cameras && this._cameras.length > 1
|
||||
${this._loading
|
||||
? html`<div class="loading">
|
||||
<ha-spinner active></ha-spinner>
|
||||
</div>`
|
||||
: nothing}
|
||||
${!this._loading &&
|
||||
!this._error &&
|
||||
this._cameras &&
|
||||
this._cameras.length > 1
|
||||
? html`<ha-button-menu fixed @closed=${stopPropagation}>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.localize(
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.qr-scanner.select_camera"
|
||||
)}
|
||||
.path=${mdiCamera}
|
||||
@ -128,25 +142,25 @@ class HaQrScanner extends LitElement {
|
||||
</ha-button-menu>`
|
||||
: nothing}
|
||||
</div>`
|
||||
: html`${this._manual
|
||||
? nothing
|
||||
: html`<ha-alert alert-type="warning">
|
||||
${!window.isSecureContext
|
||||
? this.localize(
|
||||
? this.hass.localize(
|
||||
"ui.components.qr-scanner.only_https_supported"
|
||||
)
|
||||
: this.localize("ui.components.qr-scanner.not_supported")}
|
||||
</ha-alert>`}
|
||||
<p>${this.localize("ui.components.qr-scanner.manual_input")}</p>
|
||||
: this.hass.localize("ui.components.qr-scanner.not_supported")}
|
||||
</ha-alert>
|
||||
<p>${this.hass.localize("ui.components.qr-scanner.manual_input")}</p>
|
||||
<div class="row">
|
||||
<ha-textfield
|
||||
.label=${this.localize("ui.components.qr-scanner.enter_qr_code")}
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.qr-scanner.enter_qr_code"
|
||||
)}
|
||||
@keyup=${this._manualKeyup}
|
||||
@paste=${this._manualPaste}
|
||||
></ha-textfield>
|
||||
<mwc-button @click=${this._manualSubmit}>
|
||||
${this.localize("ui.common.submit")}
|
||||
</mwc-button>
|
||||
<ha-button @click=${this._manualSubmit}>
|
||||
${this.hass.localize("ui.common.submit")}
|
||||
</ha-button>
|
||||
</div>`}`;
|
||||
}
|
||||
|
||||
@ -165,7 +179,9 @@ class HaQrScanner extends LitElement {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const QrScanner = (await import("qr-scanner")).default;
|
||||
if (!(await QrScanner.hasCamera())) {
|
||||
this._reportError("No camera found");
|
||||
this._reportError(
|
||||
this.hass.localize("ui.components.qr-scanner.no_camera_found")
|
||||
);
|
||||
return;
|
||||
}
|
||||
QrScanner.WORKER_PATH = "/static/js/qr-scanner-worker.min.js";
|
||||
@ -181,6 +197,7 @@ class HaQrScanner extends LitElement {
|
||||
canvas.style.display = "block";
|
||||
try {
|
||||
await this._qrScanner.start();
|
||||
this._loading = false;
|
||||
} catch (err: any) {
|
||||
this._reportError(err);
|
||||
}
|
||||
@ -193,8 +210,8 @@ class HaQrScanner extends LitElement {
|
||||
private _qrCodeError = (err: any) => {
|
||||
if (err.endsWith("No QR code found")) {
|
||||
this._qrNotFoundCount++;
|
||||
if (this._qrNotFoundCount === 250) {
|
||||
this._reportError(err);
|
||||
if (this._qrNotFoundCount >= 250) {
|
||||
this._reportWarning(err);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -204,7 +221,17 @@ class HaQrScanner extends LitElement {
|
||||
};
|
||||
|
||||
private _qrCodeScanned = (qrCodeString: string): void => {
|
||||
this._warning = undefined;
|
||||
this._qrNotFoundCount = 0;
|
||||
if (this.validate) {
|
||||
const validationMessage = this.validate(qrCodeString);
|
||||
|
||||
if (validationMessage) {
|
||||
this._reportWarning(validationMessage);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fireEvent(this, "qr-code-scanned", { value: qrCodeString });
|
||||
};
|
||||
|
||||
@ -234,7 +261,10 @@ class HaQrScanner extends LitElement {
|
||||
if (msg.command === "bar_code/scan_result") {
|
||||
if (msg.payload.format !== "qr_code") {
|
||||
this._notifyExternalScanner(
|
||||
`Wrong barcode scanned! ${msg.payload.format}: ${msg.payload.rawValue}, we need a QR code.`
|
||||
this.hass.localize("ui.components.qr-scanner.wrong_code", {
|
||||
format: msg.payload.format,
|
||||
rawValue: msg.payload.rawValue,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
this._qrCodeScanned(msg.payload.rawValue);
|
||||
@ -244,7 +274,7 @@ class HaQrScanner extends LitElement {
|
||||
if (msg.payload.reason === "canceled") {
|
||||
fireEvent(this, "qr-code-closed");
|
||||
} else {
|
||||
this._manual = true;
|
||||
fireEvent(this, "qr-code-more-options");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@ -252,10 +282,17 @@ class HaQrScanner extends LitElement {
|
||||
this.hass.auth.external!.fireMessage({
|
||||
type: "bar_code/scan",
|
||||
payload: {
|
||||
title: this.title || "Scan QR code",
|
||||
description: this.description || "Scan a barcode.",
|
||||
title:
|
||||
this.title ||
|
||||
this.hass.localize("ui.components.qr-scanner.app.title"),
|
||||
description:
|
||||
this.description ||
|
||||
this.hass.localize("ui.components.qr-scanner.app.description"),
|
||||
alternative_option_label:
|
||||
this.alternativeOptionLabel || "Click to manually enter the barcode",
|
||||
this.alternativeOptionLabel ||
|
||||
this.hass.localize(
|
||||
"ui.components.qr-scanner.app.alternativeOptionLabel"
|
||||
),
|
||||
},
|
||||
});
|
||||
}
|
||||
@ -269,25 +306,55 @@ class HaQrScanner extends LitElement {
|
||||
}
|
||||
|
||||
private _notifyExternalScanner(message: string) {
|
||||
if (!this.hass.auth.external) {
|
||||
if (!this._nativeBarcodeScanner) {
|
||||
return;
|
||||
}
|
||||
this.hass.auth.external.fireMessage({
|
||||
this.hass.auth.external!.fireMessage({
|
||||
type: "bar_code/notify",
|
||||
payload: {
|
||||
message,
|
||||
},
|
||||
});
|
||||
this.error = undefined;
|
||||
this._warning = undefined;
|
||||
this._error = undefined;
|
||||
}
|
||||
|
||||
private _reportError(message: string) {
|
||||
fireEvent(this, "qr-code-error", { message });
|
||||
const canvas = this._qrScanner?.$canvas;
|
||||
if (canvas) {
|
||||
canvas.style.display = "none";
|
||||
}
|
||||
this._error = message;
|
||||
}
|
||||
|
||||
private _reportWarning(message: string) {
|
||||
if (this._nativeBarcodeScanner) {
|
||||
this._notifyExternalScanner(message);
|
||||
} else {
|
||||
this._warning = message;
|
||||
}
|
||||
}
|
||||
|
||||
private async _retry() {
|
||||
if (this._qrScanner) {
|
||||
this._loading = true;
|
||||
this._error = undefined;
|
||||
this._warning = undefined;
|
||||
const canvas = this._qrScanner.$canvas;
|
||||
canvas.style.display = "block";
|
||||
this._qrNotFoundCount = 0;
|
||||
await this._qrScanner.start();
|
||||
this._loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:root {
|
||||
position: relative;
|
||||
}
|
||||
canvas {
|
||||
width: 100%;
|
||||
border-radius: 16px;
|
||||
}
|
||||
#canvas-container {
|
||||
position: relative;
|
||||
@ -312,6 +379,24 @@ class HaQrScanner extends LitElement {
|
||||
margin-inline-end: 8px;
|
||||
margin-inline-start: initial;
|
||||
}
|
||||
.loading {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
ha-alert {
|
||||
display: block;
|
||||
}
|
||||
ha-alert.warning {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
background-color: var(--primary-background-color);
|
||||
top: 0;
|
||||
width: calc(100% - 48px);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@ -319,8 +404,8 @@ declare global {
|
||||
// for fire event
|
||||
interface HASSDomEvents {
|
||||
"qr-code-scanned": { value: string };
|
||||
"qr-code-error": { message: string };
|
||||
"qr-code-closed": undefined;
|
||||
"qr-code-more-options": undefined;
|
||||
}
|
||||
|
||||
interface HTMLElementTagNameMap {
|
||||
|
@ -80,7 +80,7 @@ enum QRCodeVersion {
|
||||
SmartStart = 1,
|
||||
}
|
||||
|
||||
enum Protocols {
|
||||
export enum Protocols {
|
||||
ZWave = 0,
|
||||
ZWaveLongRange = 1,
|
||||
}
|
||||
@ -151,12 +151,35 @@ export interface QRProvisioningInformation {
|
||||
maxInclusionRequestInterval?: number | undefined;
|
||||
uuid?: string | undefined;
|
||||
supportedProtocols?: Protocols[] | undefined;
|
||||
status?: ProvisioningEntryStatus;
|
||||
}
|
||||
|
||||
export interface PlannedProvisioningEntry {
|
||||
/** The device specific key (DSK) in the form aaaaa-bbbbb-ccccc-ddddd-eeeee-fffff-11111-22222 */
|
||||
dsk: string;
|
||||
securityClasses: SecurityClass[];
|
||||
status?: ProvisioningEntryStatus;
|
||||
}
|
||||
|
||||
export enum ProvisioningEntryStatus {
|
||||
Active = 0,
|
||||
Inactive = 1,
|
||||
}
|
||||
|
||||
export interface DeviceConfig {
|
||||
filename: string;
|
||||
manufacturer: string;
|
||||
manufacturerId: number;
|
||||
label: string;
|
||||
description: string;
|
||||
devices: {
|
||||
productType: number;
|
||||
productId: number;
|
||||
}[];
|
||||
firmwareVersion: {
|
||||
min: string;
|
||||
max: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const MINIMUM_QR_STRING_LENGTH = 52;
|
||||
@ -195,6 +218,7 @@ export interface ZWaveJSController {
|
||||
is_rebuilding_routes: boolean;
|
||||
inclusion_state: InclusionState;
|
||||
nodes: ZWaveJSNodeStatus[];
|
||||
supports_long_range: boolean;
|
||||
}
|
||||
|
||||
export interface ZWaveJSNodeStatus {
|
||||
@ -555,7 +579,7 @@ export const zwaveTryParseDskFromQrCode = (
|
||||
export const zwaveValidateDskAndEnterPin = (
|
||||
hass: HomeAssistant,
|
||||
entry_id: string,
|
||||
pin: string
|
||||
pin: string | false
|
||||
) =>
|
||||
hass.callWS({
|
||||
type: "zwave_js/validate_dsk_and_enter_pin",
|
||||
@ -585,19 +609,38 @@ export const zwaveParseQrCode = (
|
||||
qr_code_string,
|
||||
});
|
||||
|
||||
export const lookupZwaveDevice = (
|
||||
hass: HomeAssistant,
|
||||
entry_id: string,
|
||||
manufacturerId: number,
|
||||
productType: number,
|
||||
productId: number,
|
||||
applicationVersion?: string
|
||||
): Promise<DeviceConfig> =>
|
||||
hass.callWS({
|
||||
type: "zwave_js/lookup_device",
|
||||
entry_id,
|
||||
manufacturerId,
|
||||
productType,
|
||||
productId,
|
||||
applicationVersion,
|
||||
});
|
||||
|
||||
export const provisionZwaveSmartStartNode = (
|
||||
hass: HomeAssistant,
|
||||
entry_id: string,
|
||||
qr_provisioning_information?: QRProvisioningInformation,
|
||||
qr_code_string?: string,
|
||||
planned_provisioning_entry?: PlannedProvisioningEntry
|
||||
): Promise<QRProvisioningInformation> =>
|
||||
protocol?: Protocols,
|
||||
device_name?: string,
|
||||
area_id?: string
|
||||
): Promise<string> =>
|
||||
hass.callWS({
|
||||
type: "zwave_js/provision_smart_start_node",
|
||||
entry_id,
|
||||
qr_code_string,
|
||||
qr_provisioning_information,
|
||||
planned_provisioning_entry,
|
||||
protocol,
|
||||
device_name,
|
||||
area_id,
|
||||
});
|
||||
|
||||
export const unprovisionZwaveSmartStartNode = (
|
||||
@ -613,6 +656,16 @@ export const unprovisionZwaveSmartStartNode = (
|
||||
node_id,
|
||||
});
|
||||
|
||||
export const subscribeNewDevices = (
|
||||
hass: HomeAssistant,
|
||||
entry_id: string,
|
||||
callbackFunction: (message: any) => void
|
||||
): Promise<UnsubscribeFunc> =>
|
||||
hass.connection.subscribeMessage((message) => callbackFunction(message), {
|
||||
type: "zwave_js/subscribe_new_devices",
|
||||
entry_id: entry_id,
|
||||
});
|
||||
|
||||
export const fetchZwaveNodeStatus = (
|
||||
hass: HomeAssistant,
|
||||
device_id: string
|
||||
|
@ -0,0 +1,54 @@
|
||||
import type { QRProvisioningInformation } from "../../../../../../data/zwave_js";
|
||||
|
||||
export const backButtonStages: Partial<ZWaveJSAddNodeStage>[] = [
|
||||
"qr_scan",
|
||||
"select_other_method",
|
||||
"qr_code_input",
|
||||
"choose_security_strategy",
|
||||
"configure_device",
|
||||
];
|
||||
|
||||
export const closeButtonStages: Partial<ZWaveJSAddNodeStage>[] = [
|
||||
"select_method",
|
||||
"search_devices",
|
||||
"search_smart_start_device",
|
||||
"search_s2_device",
|
||||
"failed",
|
||||
"interviewing",
|
||||
"validate_dsk_enter_pin",
|
||||
"added_insecure",
|
||||
"grant_security_classes",
|
||||
"rename_device",
|
||||
];
|
||||
|
||||
export type ZWaveJSAddNodeStage =
|
||||
| "loading"
|
||||
| "qr_scan"
|
||||
| "select_method"
|
||||
| "select_other_method"
|
||||
| "qr_code_input"
|
||||
| "search_devices"
|
||||
| "search_smart_start_device"
|
||||
| "search_s2_device"
|
||||
| "choose_security_strategy"
|
||||
| "configure_device"
|
||||
| "interviewing"
|
||||
| "failed"
|
||||
| "timed_out"
|
||||
| "added_insecure"
|
||||
| "validate_dsk_enter_pin"
|
||||
| "grant_security_classes"
|
||||
| "waiting_for_device"
|
||||
| "rename_device";
|
||||
|
||||
export interface ZWaveJSAddNodeSmartStartOptions {
|
||||
name: string;
|
||||
area?: string;
|
||||
network_type?: string;
|
||||
}
|
||||
|
||||
export interface ZWaveJSAddNodeDevice {
|
||||
id?: string;
|
||||
name: string;
|
||||
provisioningInfo?: QRProvisioningInformation;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,9 @@
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { fireEvent } from "../../../../../../common/dom/fire_event";
|
||||
|
||||
export interface ZWaveJSAddNodeDialogParams {
|
||||
entry_id: string;
|
||||
longRangeSupported?: boolean;
|
||||
inclusionOngoing?: boolean;
|
||||
dsk?: string;
|
||||
onStop?: () => void;
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
import { mdiCheckCircleOutline } from "@mdi/js";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "@shoelace-style/shoelace/dist/components/animation/animation";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import type { HomeAssistant } from "../../../../../../types";
|
||||
|
||||
import "../../../../../../components/ha-svg-icon";
|
||||
import "../../../../../../components/ha-alert";
|
||||
|
||||
@customElement("zwave-js-add-node-added-insecure")
|
||||
export class ZWaveJsAddNodeFinished extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: "device-name" }) public deviceName?: string;
|
||||
|
||||
@property() public reason?;
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<sl-animation name="zoomIn" .iterations=${1} play>
|
||||
<ha-svg-icon .path=${mdiCheckCircleOutline}></ha-svg-icon>
|
||||
</sl-animation>
|
||||
<ha-alert alert-type="warning">
|
||||
${this.reason
|
||||
? this.hass.localize(
|
||||
`ui.panel.config.zwave_js.add_node.added_insecure.low_security_reason.${this.reason}`
|
||||
)
|
||||
: ""}
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.added_insecure.added_insecurely_text",
|
||||
{
|
||||
deviceName: html`<b>${this.deviceName}</b>`,
|
||||
}
|
||||
)}
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.zwave_js.add_node.added_insecure.try_again_text`
|
||||
)}
|
||||
</p>
|
||||
</ha-alert>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
ha-svg-icon {
|
||||
--mdc-icon-size: 96px;
|
||||
color: var(--warning-color);
|
||||
}
|
||||
ha-alert {
|
||||
margin-top: 16px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"zwave-js-add-node-added-insecure": ZWaveJsAddNodeFinished;
|
||||
}
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
|
||||
import { fireEvent } from "../../../../../../common/dom/fire_event";
|
||||
import type { HaTextField } from "../../../../../../components/ha-textfield";
|
||||
|
||||
import "../../../../../../components/ha-textfield";
|
||||
import "../../../../../../components/ha-alert";
|
||||
|
||||
@customElement("zwave-js-add-node-code-input")
|
||||
export class ZWaveJsAddNodeCodeInput extends LitElement {
|
||||
@property() public value = "";
|
||||
|
||||
@property() public description = "";
|
||||
|
||||
@property() public placeholder = "";
|
||||
|
||||
@property({ attribute: "reference-key" }) public referenceKey = "";
|
||||
|
||||
@property() public error?: string;
|
||||
|
||||
@property({ type: Boolean }) public numeric = false;
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<p>${this.description}</p>
|
||||
${this.error
|
||||
? html`<ha-alert alert-type="error">${this.error}</ha-alert>`
|
||||
: nothing}
|
||||
<ha-textfield
|
||||
.placeholder=${this.placeholder}
|
||||
.value=${this.value}
|
||||
@input=${this._handleChange}
|
||||
@keyup=${this._handleKeyup}
|
||||
required
|
||||
autofocus
|
||||
></ha-textfield>
|
||||
${this.referenceKey
|
||||
? html`<div>
|
||||
<span>${this.value.padEnd(5, "·")}</span>${this.referenceKey}
|
||||
</div> `
|
||||
: nothing}
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleKeyup(ev: KeyboardEvent): void {
|
||||
if (ev.key === "Enter" && this.value) {
|
||||
fireEvent(this, "z-wave-submit");
|
||||
}
|
||||
}
|
||||
|
||||
private _handleChange(ev: InputEvent): void {
|
||||
const inputElement = ev.target as HaTextField;
|
||||
if (
|
||||
this.numeric &&
|
||||
(isNaN(Number(inputElement.value)) || inputElement.value.length > 5)
|
||||
) {
|
||||
inputElement.value = this.value;
|
||||
return;
|
||||
}
|
||||
|
||||
this.value = (ev.target as HaTextField).value;
|
||||
|
||||
fireEvent(this, "value-changed", {
|
||||
value: (ev.target as HaTextField).value,
|
||||
});
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-textfield {
|
||||
width: 100%;
|
||||
}
|
||||
ha-alert {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
p {
|
||||
color: var(--secondary-text-color);
|
||||
margin-top: 0;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
div {
|
||||
font-family: "Roboto Mono", "Consolas", "Menlo", monospace;
|
||||
margin-top: 16px;
|
||||
}
|
||||
div span {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"zwave-js-add-node-code-input": ZWaveJsAddNodeCodeInput;
|
||||
}
|
||||
interface HASSDomEvents {
|
||||
"z-wave-submit";
|
||||
}
|
||||
}
|
@ -0,0 +1,152 @@
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { html, LitElement, type PropertyValues } from "lit";
|
||||
import memoizeOne from "memoize-one";
|
||||
|
||||
import type { HomeAssistant } from "../../../../../../types";
|
||||
import type { LocalizeFunc } from "../../../../../../common/translations/localize";
|
||||
import type { HaFormSchema } from "../../../../../../components/ha-form/types";
|
||||
import { fireEvent } from "../../../../../../common/dom/fire_event";
|
||||
import { Protocols } from "../../../../../../data/zwave_js";
|
||||
import type { ZWaveJSAddNodeSmartStartOptions } from "./data";
|
||||
|
||||
import "../../../../../../components/ha-form/ha-form";
|
||||
|
||||
@customElement("zwave-js-add-node-configure-device")
|
||||
export class ZWaveJsAddNodeConfigureDevice extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: "device-name" }) public deviceName = "";
|
||||
|
||||
@property({ type: Boolean, attribute: "lr-supported" })
|
||||
public longRangeSupported = false;
|
||||
|
||||
@state() private _options?: ZWaveJSAddNodeSmartStartOptions;
|
||||
|
||||
protected willUpdate(changedProps: PropertyValues): void {
|
||||
super.willUpdate(changedProps);
|
||||
|
||||
if (!this.hasUpdated) {
|
||||
this._options = {
|
||||
name: this.deviceName,
|
||||
};
|
||||
|
||||
if (this.longRangeSupported) {
|
||||
this._options.network_type = Protocols.ZWaveLongRange.toString();
|
||||
}
|
||||
|
||||
fireEvent(this, "value-changed", { value: this._options });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.schema=${this._getSchema(this.hass.localize, this.longRangeSupported)}
|
||||
.data=${this._options!}
|
||||
@value-changed=${this._setOptions}
|
||||
.computeLabel=${this._computeLabel}
|
||||
>
|
||||
</ha-form>
|
||||
`;
|
||||
}
|
||||
|
||||
private _getSchema = memoizeOne(
|
||||
(localize: LocalizeFunc, longRangeSupported: boolean): HaFormSchema[] => {
|
||||
const schema: HaFormSchema[] = [
|
||||
{
|
||||
name: "name",
|
||||
required: true,
|
||||
default: this.deviceName,
|
||||
type: "string",
|
||||
autofocus: true,
|
||||
},
|
||||
{
|
||||
name: "area",
|
||||
selector: {
|
||||
area: {},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
if (longRangeSupported) {
|
||||
schema.push({
|
||||
name: "network_type",
|
||||
required: true,
|
||||
selector: {
|
||||
select: {
|
||||
box_max_columns: 1,
|
||||
mode: "box",
|
||||
options: [
|
||||
{
|
||||
value: Protocols.ZWaveLongRange.toString(),
|
||||
label: localize(
|
||||
"ui.panel.config.zwave_js.add_node.configure_device.long_range_label"
|
||||
),
|
||||
description: localize(
|
||||
"ui.panel.config.zwave_js.add_node.configure_device.long_range_description"
|
||||
),
|
||||
image: {
|
||||
src: "/static/images/z-wave-add-node/long-range.svg",
|
||||
src_dark:
|
||||
"/static/images/z-wave-add-node/long-range_dark.svg",
|
||||
flip_rtl: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
value: Protocols.ZWave.toString(),
|
||||
label: localize(
|
||||
"ui.panel.config.zwave_js.add_node.configure_device.mesh_label"
|
||||
),
|
||||
description: localize(
|
||||
"ui.panel.config.zwave_js.add_node.configure_device.mesh_description"
|
||||
),
|
||||
image: {
|
||||
src: "/static/images/z-wave-add-node/mesh.svg",
|
||||
src_dark: "/static/images/z-wave-add-node/mesh_dark.svg",
|
||||
flip_rtl: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
return schema;
|
||||
}
|
||||
);
|
||||
|
||||
private _computeLabel = (schema: HaFormSchema): string | undefined => {
|
||||
if (schema.name === "network_type") {
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.configure_device.choose_network_type"
|
||||
);
|
||||
}
|
||||
if (schema.name === "name") {
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.configure_device.device_name"
|
||||
);
|
||||
}
|
||||
if (schema.name === "area") {
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.configure_device.device_area"
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
private _setOptions(event: any) {
|
||||
this._options = {
|
||||
...this._options!,
|
||||
...event.detail.value,
|
||||
};
|
||||
|
||||
fireEvent(this, "value-changed", { value: this._options });
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"zwave-js-add-node-configure-device": ZWaveJsAddNodeConfigureDevice;
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import type { HomeAssistant } from "../../../../../../types";
|
||||
import type { ZWaveJSAddNodeDevice } from "./data";
|
||||
|
||||
import "../../../../../../components/ha-alert";
|
||||
import "../../../../../../components/ha-button";
|
||||
|
||||
@customElement("zwave-js-add-node-failed")
|
||||
export class ZWaveJsAddNodeFailed extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public error?: string;
|
||||
|
||||
@property({ attribute: false }) public device?: ZWaveJSAddNodeDevice;
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<ha-alert
|
||||
alert-type="error"
|
||||
.title=${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.inclusion_failed"
|
||||
)}
|
||||
>
|
||||
${this.error ||
|
||||
this.hass.localize("ui.panel.config.zwave_js.add_node.check_logs")}
|
||||
</ha-alert>
|
||||
${this.error
|
||||
? html`<div class="note">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.check_logs"
|
||||
)}
|
||||
</div>`
|
||||
: nothing}
|
||||
${this.device?.id
|
||||
? html`<a href=${`/config/devices/device/${this.device.id}`}>
|
||||
<ha-button>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.view_device"
|
||||
)}
|
||||
</ha-button>
|
||||
</a>`
|
||||
: nothing}
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
padding: 16px;
|
||||
}
|
||||
div.note {
|
||||
text-align: center;
|
||||
margin-top: 16px;
|
||||
font-size: 12px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
ha-button {
|
||||
margin-top: 32px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"zwave-js-add-node-failed": ZWaveJsAddNodeFailed;
|
||||
}
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "@shoelace-style/shoelace/dist/components/animation/animation";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import type { HomeAssistant } from "../../../../../../types";
|
||||
import { SecurityClass } from "../../../../../../data/zwave_js";
|
||||
import type { HaCheckbox } from "../../../../../../components/ha-checkbox";
|
||||
import { fireEvent } from "../../../../../../common/dom/fire_event";
|
||||
|
||||
import "../../../../../../components/ha-alert";
|
||||
import "../../../../../../components/ha-formfield";
|
||||
import "../../../../../../components/ha-checkbox";
|
||||
|
||||
@customElement("zwave-js-add-node-grant-security-classes")
|
||||
export class ZWaveJsAddNodeGrantSecurityClasses extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public error?: string;
|
||||
|
||||
@property({ attribute: false }) public securityClassOptions!: SecurityClass[];
|
||||
|
||||
@property({ attribute: false })
|
||||
public selectedSecurityClasses: SecurityClass[] = [];
|
||||
|
||||
render() {
|
||||
return html`
|
||||
${this.error
|
||||
? html`<ha-alert alert-type="error"> ${this.error} </ha-alert>`
|
||||
: nothing}
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.grant_security_classes.description"
|
||||
)}
|
||||
</p>
|
||||
<div class="flex-column">
|
||||
${this.securityClassOptions
|
||||
.sort((a, b) => {
|
||||
// Put highest security classes at the top, S0 at the bottom
|
||||
if (a === SecurityClass.S0_Legacy) return 1;
|
||||
if (b === SecurityClass.S0_Legacy) return -1;
|
||||
return b - a;
|
||||
})
|
||||
.map(
|
||||
(securityClass) =>
|
||||
html`<ha-formfield
|
||||
.label=${html`<b
|
||||
>${this.hass.localize(
|
||||
`ui.panel.config.zwave_js.security_classes.${SecurityClass[securityClass]}.title`
|
||||
)}</b
|
||||
>
|
||||
<div class="secondary">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.zwave_js.security_classes.${SecurityClass[securityClass]}.description`
|
||||
)}
|
||||
</div>`}
|
||||
>
|
||||
<ha-checkbox
|
||||
@change=${this._handleSecurityClassChange}
|
||||
.value=${securityClass.toString()}
|
||||
.checked=${this.selectedSecurityClasses.includes(
|
||||
securityClass
|
||||
)}
|
||||
>
|
||||
</ha-checkbox>
|
||||
</ha-formfield>`
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleSecurityClassChange(ev: CustomEvent) {
|
||||
const checkbox = ev.currentTarget as HaCheckbox;
|
||||
const securityClass = Number(checkbox.value);
|
||||
if (
|
||||
checkbox.checked &&
|
||||
!this.selectedSecurityClasses.includes(securityClass)
|
||||
) {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: [...this.selectedSecurityClasses, securityClass],
|
||||
});
|
||||
} else if (!checkbox.checked) {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: this.selectedSecurityClasses.filter(
|
||||
(val) => val !== securityClass
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-alert {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.flex-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.secondary {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"zwave-js-add-node-grant-security-classes": ZWaveJsAddNodeGrantSecurityClasses;
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
|
||||
import "../../../../../../components/ha-fade-in";
|
||||
import "../../../../../../components/ha-spinner";
|
||||
|
||||
@customElement("zwave-js-add-node-loading")
|
||||
export class ZWaveJsAddNodeLoading extends LitElement {
|
||||
@property() public description?: string;
|
||||
|
||||
@property({ type: Number }) public delay = 0;
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<ha-fade-in .delay=${this.delay}>
|
||||
<div class="loading">
|
||||
<ha-spinner size="large"></ha-spinner>
|
||||
</div>
|
||||
${this.description ? html`<p>${this.description}</p>` : nothing}
|
||||
</ha-fade-in>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-fade-in {
|
||||
display: block;
|
||||
}
|
||||
.loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 200px;
|
||||
}
|
||||
p {
|
||||
margin-top: 16px;
|
||||
color: var(--secondary-text-color);
|
||||
text-align: center;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"zwave-js-add-node-loading": ZWaveJsAddNodeLoading;
|
||||
}
|
||||
}
|
@ -0,0 +1,164 @@
|
||||
import "@shoelace-style/shoelace/dist/components/animation/animation";
|
||||
import { mdiRestart } from "@mdi/js";
|
||||
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import type { HomeAssistant } from "../../../../../../types";
|
||||
import { fireEvent } from "../../../../../../common/dom/fire_event";
|
||||
import { InclusionStrategy } from "../../../../../../data/zwave_js";
|
||||
|
||||
import "../../../../../../components/ha-spinner";
|
||||
import "../../../../../../components/ha-button";
|
||||
import "../../../../../../components/ha-alert";
|
||||
|
||||
@customElement("zwave-js-add-node-searching-devices")
|
||||
export class ZWaveJsAddNodeSearchingDevices extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean, attribute: "smart-start" })
|
||||
public smartStart = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "show-security-options" })
|
||||
public showSecurityOptions = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "show-add-another-device" })
|
||||
public showAddAnotherDevice = false;
|
||||
|
||||
@property({ attribute: false }) public inclusionStrategy?: InclusionStrategy;
|
||||
|
||||
render() {
|
||||
let inclusionStrategyTranslationKey = "";
|
||||
if (this.inclusionStrategy !== undefined) {
|
||||
switch (this.inclusionStrategy) {
|
||||
case InclusionStrategy.Security_S0:
|
||||
inclusionStrategyTranslationKey = "s0";
|
||||
break;
|
||||
case InclusionStrategy.Insecure:
|
||||
inclusionStrategyTranslationKey = "insecure";
|
||||
break;
|
||||
default:
|
||||
inclusionStrategyTranslationKey = "default";
|
||||
}
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="searching-devices">
|
||||
<div class="searching-spinner">
|
||||
<div class="spinner">
|
||||
<ha-spinner></ha-spinner>
|
||||
</div>
|
||||
<sl-animation name="pulse" easing="linear" .duration=${2000} play>
|
||||
<div class="circle"></div>
|
||||
</sl-animation>
|
||||
</div>
|
||||
${this.smartStart
|
||||
? html`<ha-alert
|
||||
.title=${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.specific_device.turn_on_device"
|
||||
)}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiRestart}></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.specific_device.turn_on_device_description"
|
||||
)}
|
||||
</ha-alert>
|
||||
<p class="note">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.specific_device.close_description"
|
||||
)}
|
||||
</p>`
|
||||
: html`
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.follow_device_instructions"
|
||||
)}
|
||||
</p>
|
||||
`}
|
||||
${this.showSecurityOptions && !inclusionStrategyTranslationKey
|
||||
? html`<ha-button @click=${this._handleSecurityOptions}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.security_options"
|
||||
)}
|
||||
</ha-button>`
|
||||
: inclusionStrategyTranslationKey
|
||||
? html`<span class="note">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.select_strategy.inclusion_strategy",
|
||||
{
|
||||
strategy: this.hass.localize(
|
||||
`ui.panel.config.zwave_js.add_node.select_strategy.${inclusionStrategyTranslationKey}_label`
|
||||
),
|
||||
}
|
||||
)}
|
||||
</span>`
|
||||
: nothing}
|
||||
${this.showAddAnotherDevice
|
||||
? html`<ha-button @click=${this._handleAddAnotherDevice}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.specific_device.add_another_z_wave_device"
|
||||
)}
|
||||
</ha-button>`
|
||||
: nothing}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleSecurityOptions() {
|
||||
fireEvent(this, "show-z-wave-security-options");
|
||||
}
|
||||
|
||||
private _handleAddAnotherDevice() {
|
||||
fireEvent(this, "add-another-z-wave-device");
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
text-align: center;
|
||||
display: block;
|
||||
}
|
||||
ha-alert {
|
||||
margin-top: 32px;
|
||||
display: block;
|
||||
}
|
||||
.note {
|
||||
font-size: 12px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.searching-spinner {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
position: relative;
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
}
|
||||
.searching-spinner .circle {
|
||||
border-radius: 50%;
|
||||
background-color: var(--light-primary-color);
|
||||
position: absolute;
|
||||
width: calc(100% - 32px);
|
||||
height: calc(100% - 32px);
|
||||
margin: 16px;
|
||||
}
|
||||
.searching-spinner .spinner {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
--ha-spinner-divider-color: var(--light-primary-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"zwave-js-add-node-searching-devices": ZWaveJsAddNodeSearchingDevices;
|
||||
}
|
||||
|
||||
interface HASSDomEvents {
|
||||
"show-z-wave-security-options": undefined;
|
||||
"add-another-z-wave-device": undefined;
|
||||
}
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { fireEvent } from "../../../../../../common/dom/fire_event";
|
||||
import type { HomeAssistant } from "../../../../../../types";
|
||||
|
||||
import "../../../../../../components/ha-md-list";
|
||||
import "../../../../../../components/ha-md-list-item";
|
||||
import "../../../../../../components/ha-alert";
|
||||
import "../../../../../../components/ha-icon-next";
|
||||
|
||||
@customElement("zwave-js-add-node-select-method")
|
||||
export class ZWaveJsAddNodeSelectMethod extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean, attribute: "hide-qr-webcam" })
|
||||
public hideQrWebcam = false;
|
||||
|
||||
render() {
|
||||
return html`
|
||||
${!this.hideQrWebcam && !window.isSecureContext
|
||||
? html`<ha-alert alert-type="warning">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.select_method.webcam_unsupported"
|
||||
)}</ha-alert
|
||||
>`
|
||||
: nothing}
|
||||
<ha-md-list>
|
||||
${!this.hideQrWebcam
|
||||
? html`<ha-md-list-item
|
||||
interactive
|
||||
type="button"
|
||||
@click=${this._selectMethod}
|
||||
.value=${"qr_code_webcam"}
|
||||
.disabled=${!window.isSecureContext}
|
||||
>
|
||||
<div slot="headline">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.zwave_js.add_node.select_method.qr_code_webcam`
|
||||
)}
|
||||
</div>
|
||||
<div slot="supporting-text">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.zwave_js.add_node.select_method.qr_code_webcam_description`
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-md-list-item>`
|
||||
: nothing}
|
||||
<ha-md-list-item
|
||||
interactive
|
||||
type="button"
|
||||
@click=${this._selectMethod}
|
||||
.value=${"qr_code_manual"}
|
||||
>
|
||||
<div slot="headline">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.zwave_js.add_node.select_method.qr_code_manual`
|
||||
)}
|
||||
</div>
|
||||
<div slot="supporting-text">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.zwave_js.add_node.select_method.qr_code_manual_description`
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item
|
||||
interactive
|
||||
type="button"
|
||||
@click=${this._selectMethod}
|
||||
.value=${"search_device"}
|
||||
>
|
||||
<div slot="headline">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.zwave_js.add_node.select_method.search_device`
|
||||
)}
|
||||
</div>
|
||||
<div slot="supporting-text">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.zwave_js.add_node.select_method.search_device_description`
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
`;
|
||||
}
|
||||
|
||||
private _selectMethod(event: any) {
|
||||
const method = event.currentTarget.value;
|
||||
if (method !== "qr_code_webcam" || window.isSecureContext) {
|
||||
fireEvent(this, "z-wave-method-selected", { method });
|
||||
}
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-md-list {
|
||||
padding: 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"zwave-js-add-node-select-method": ZWaveJsAddNodeSelectMethod;
|
||||
}
|
||||
interface HASSDomEvents {
|
||||
"z-wave-method-selected": {
|
||||
method: "qr_code_webcam" | "qr_code_manual" | "search_device";
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import memoizeOne from "memoize-one";
|
||||
|
||||
import { fireEvent } from "../../../../../../common/dom/fire_event";
|
||||
import type { HomeAssistant } from "../../../../../../types";
|
||||
import { InclusionStrategy } from "../../../../../../data/zwave_js";
|
||||
import type { LocalizeFunc } from "../../../../../../common/translations/localize";
|
||||
import type { HaFormSchema } from "../../../../../../components/ha-form/types";
|
||||
|
||||
import "../../../../../../components/ha-form/ha-form";
|
||||
|
||||
@customElement("zwave-js-add-node-select-security-strategy")
|
||||
export class ZWaveJsAddNodeSelectMethod extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() public _inclusionStrategy?: InclusionStrategy;
|
||||
|
||||
private _getSchema = memoizeOne((localize: LocalizeFunc): HaFormSchema[] => [
|
||||
{
|
||||
name: "strategy",
|
||||
required: true,
|
||||
selector: {
|
||||
select: {
|
||||
box_max_columns: 1,
|
||||
mode: "box",
|
||||
options: [
|
||||
{
|
||||
value: InclusionStrategy.Default.toString(),
|
||||
label: localize(
|
||||
"ui.panel.config.zwave_js.add_node.select_strategy.default_label"
|
||||
),
|
||||
description: localize(
|
||||
"ui.panel.config.zwave_js.add_node.select_strategy.default_description"
|
||||
),
|
||||
},
|
||||
{
|
||||
value: InclusionStrategy.Security_S0.toString(),
|
||||
label: localize(
|
||||
"ui.panel.config.zwave_js.add_node.select_strategy.s0_label"
|
||||
),
|
||||
description: localize(
|
||||
"ui.panel.config.zwave_js.add_node.select_strategy.s0_description"
|
||||
),
|
||||
},
|
||||
{
|
||||
value: InclusionStrategy.Insecure.toString(),
|
||||
label: localize(
|
||||
"ui.panel.config.zwave_js.add_node.select_strategy.insecure_label"
|
||||
),
|
||||
description: localize(
|
||||
"ui.panel.config.zwave_js.add_node.select_strategy.insecure_description"
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<ha-form
|
||||
.schema=${this._getSchema(this.hass.localize)}
|
||||
.data=${{ strategy: this._inclusionStrategy?.toString() }}
|
||||
@value-changed=${this._selectStrategy}
|
||||
.computeLabel=${this._computeLabel}
|
||||
>
|
||||
</ha-form>
|
||||
`;
|
||||
}
|
||||
|
||||
private _computeLabel = () =>
|
||||
this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.select_strategy.title"
|
||||
);
|
||||
|
||||
private _selectStrategy(event: any) {
|
||||
const selectedStrategy = Number(
|
||||
event.detail.value.strategy
|
||||
) as InclusionStrategy;
|
||||
fireEvent(this, "z-wave-strategy-selected", {
|
||||
strategy: selectedStrategy,
|
||||
});
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
padding: 16px;
|
||||
}
|
||||
ha-md-list {
|
||||
padding: 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"zwave-js-add-node-select-security-strategy": ZWaveJsAddNodeSelectMethod;
|
||||
}
|
||||
interface HASSDomEvents {
|
||||
"z-wave-strategy-selected": {
|
||||
strategy: InclusionStrategy;
|
||||
};
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/* eslint-disable lit/lifecycle-super */
|
||||
import { customElement } from "lit/decorators";
|
||||
import { navigate } from "../../../../../common/navigate";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import { navigate } from "../../../../../../common/navigate";
|
||||
import type { HomeAssistant } from "../../../../../../types";
|
||||
import { showZWaveJSAddNodeDialog } from "./show-dialog-zwave_js-add-node";
|
||||
|
||||
@customElement("zwave_js-add-node")
|
||||
@ -21,6 +21,7 @@ export class DialogZWaveJSAddNode extends HTMLElement {
|
||||
replace: true,
|
||||
}
|
||||
);
|
||||
|
||||
showZWaveJSAddNodeDialog(this, {
|
||||
entry_id: this.configEntryId,
|
||||
});
|
File diff suppressed because it is too large
Load Diff
@ -49,7 +49,7 @@ import "../../../../../layouts/hass-tabs-subpage";
|
||||
import { SubscribeMixin } from "../../../../../mixins/subscribe-mixin";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant, Route } from "../../../../../types";
|
||||
import { showZWaveJSAddNodeDialog } from "./show-dialog-zwave_js-add-node";
|
||||
import { showZWaveJSAddNodeDialog } from "./add-node/show-dialog-zwave_js-add-node";
|
||||
import { showZWaveJSRebuildNetworkRoutesDialog } from "./show-dialog-zwave_js-rebuild-network-routes";
|
||||
import { showZWaveJSRemoveNodeDialog } from "./show-dialog-zwave_js-remove-node";
|
||||
import { configTabs } from "./zwave_js-config-router";
|
||||
@ -101,7 +101,7 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
const inclusion_state = this._network?.controller.inclusion_state;
|
||||
// show dialog if inclusion/exclusion is already in progress
|
||||
if (inclusion_state === InclusionState.Including) {
|
||||
this._addNodeClicked();
|
||||
this._openInclusionDialog(undefined, true);
|
||||
} else if (inclusion_state === InclusionState.Excluding) {
|
||||
this._removeNodeClicked();
|
||||
}
|
||||
@ -743,7 +743,7 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
input.value = "";
|
||||
}
|
||||
|
||||
private _openInclusionDialog(dsk?: string) {
|
||||
private _openInclusionDialog(dsk?: string, inclusionOngoing = false) {
|
||||
if (!this._dialogOpen) {
|
||||
// Unsubscribe from S2 inclusion before opening dialog
|
||||
if (this._s2InclusionUnsubscribe) {
|
||||
@ -755,6 +755,8 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
entry_id: this.configEntryId!,
|
||||
dsk,
|
||||
onStop: this._handleInclusionDialogClosed,
|
||||
longRangeSupported: !!this._network?.controller?.supports_long_range,
|
||||
inclusionOngoing,
|
||||
});
|
||||
this._dialogOpen = true;
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ class ZWaveJSConfigRouter extends HassRouterPage {
|
||||
},
|
||||
add: {
|
||||
tag: "zwave_js-add-node",
|
||||
load: () => import("./zwave_js-add-node"),
|
||||
load: () => import("./add-node/zwave_js-add-node"),
|
||||
},
|
||||
node_config: {
|
||||
tag: "zwave_js-node-config",
|
||||
|
@ -1106,7 +1106,15 @@
|
||||
"only_https_supported": "You can only use your camera to scan a QR code when using HTTPS.",
|
||||
"not_supported": "Your browser doesn't support QR scanning.",
|
||||
"manual_input": "You can scan the QR code with another QR scanner and paste the code in the input below",
|
||||
"enter_qr_code": "Enter QR code value"
|
||||
"enter_qr_code": "Enter QR code value",
|
||||
"retry": "Retry",
|
||||
"wrong_code": "Wrong barcode scanned! {format}: {rawValue}, we need a QR code.",
|
||||
"no_camera_found": "No camera found",
|
||||
"app": {
|
||||
"title": "Scan QR code",
|
||||
"description": "Find the code on your device",
|
||||
"alternativeOptionLabel": "More options"
|
||||
}
|
||||
},
|
||||
"climate-control": {
|
||||
"temperature_up": "Increase temperature",
|
||||
@ -5876,23 +5884,76 @@
|
||||
},
|
||||
"add_node": {
|
||||
"title": "Add a Z-Wave device",
|
||||
"searching_device": "Searching for devices…",
|
||||
"follow_device_instructions": "Follow the directions that came with your device to trigger pairing on the device.",
|
||||
"searching_devices": "Searching for devices",
|
||||
"follow_device_instructions": "Follow the directions provided with your device to put it into pairing mode (sometimes called \"inclusion mode\")",
|
||||
"security_options": "Advanced security options",
|
||||
"choose_inclusion_strategy": "How do you want to add your device",
|
||||
"qr_code": "QR Code",
|
||||
"qr_code_paragraph": "If your device supports SmartStart you can scan the QR code for easy pairing.",
|
||||
"scan_qr_code": "Scan QR code",
|
||||
"add_device_failed": "Add device failed",
|
||||
"inclusion_failed": "The device could not be added.",
|
||||
"getting_device_information": "Getting device information",
|
||||
"saving_device": "Saving device",
|
||||
"check_logs": "Please check the logs for more information.",
|
||||
"inclusion_finished": "The device has been added.",
|
||||
"provisioning_finished": "The device has been added. Once you power it on, it will become available.",
|
||||
"view_device": "View Device",
|
||||
"interview_started": "The device is being interviewed. This may take some time.",
|
||||
"interview_failed": "The device interview failed. Additional information may be available in the logs.",
|
||||
"waiting_for_device": "Communicating with the device. Please wait.",
|
||||
"adding_insecurely": "The device is being added insecurely",
|
||||
"added_insecurely": "The device was added insecurely",
|
||||
"added_insecurely_text": "There was an error during secure inclusion. You can try again by excluding the device and adding it again.",
|
||||
"timeout_error": "No device found after {minutes} minutes. Please check the device and try again.",
|
||||
"select_method": {
|
||||
"webcam_unsupported": "You can only use your camera to scan a QR code when using a secure connection with the app or over HTTPS.",
|
||||
"qr_code_webcam": "Scan QR code using webcam",
|
||||
"qr_code_webcam_description": "Find and scan the code on your device.",
|
||||
"qr_code_manual": "Enter QR code manually",
|
||||
"qr_code_manual_description": "You can scan the QR code with another QR scanner and paste the code.",
|
||||
"search_device": "Search for device",
|
||||
"search_device_description": "Use this option to manually include a device that does not have a SmartStart QR code."
|
||||
},
|
||||
"qr": {
|
||||
"manual": {
|
||||
"title": "Enter QR code manually",
|
||||
"text": "You can scan the QR code with another QR scanner and paste the code here.",
|
||||
"placeholder": "Code"
|
||||
},
|
||||
"scan_code": "Scan QR code",
|
||||
"other_add_options": "Other add options",
|
||||
"invalid_code": "Invalid QR code ({code})",
|
||||
"unsupported_code": "This QR code is not supported \"{code}\""
|
||||
},
|
||||
"specific_device": {
|
||||
"title": "Searching for device",
|
||||
"turn_on_device": "Turn on the device",
|
||||
"turn_on_device_description": "If your device is already turned on, you might need to turn it off and on again. Consult your device manual if you are unsure.",
|
||||
"add_another_z_wave_device": "Add another Z-Wave device",
|
||||
"close_description": "Feel free to close this screen, as this process will continue in the background."
|
||||
},
|
||||
"select_strategy": {
|
||||
"title": "Choose security strategy",
|
||||
"default_label": "Secure if possible",
|
||||
"default_description": "Requires user interaction during inclusion. Fast and secure with S2 when supported. Allows manually selecting which security keys to grant. Fallback to legacy S0 or no encryption when necessary.",
|
||||
"s0_label": "Legacy Secure",
|
||||
"s0_description": "Uses the older S0 security that is secure, but slow due to a lot of overhead. Allows securely including S2 capable devices which fail to be included with S2.",
|
||||
"insecure_label": "Insecure",
|
||||
"insecure_description": "Do not use encryption.",
|
||||
"inclusion_strategy": "Inclusion strategy: {strategy}"
|
||||
},
|
||||
"configure_device": {
|
||||
"title": "Add device",
|
||||
"device_name": "Name",
|
||||
"device_area": "Area",
|
||||
"choose_network_type": "Choose network type",
|
||||
"long_range_label": "Direct long range",
|
||||
"long_range_description": "Direct connection to Home Assistant for extended coverage, without a mesh network.",
|
||||
"mesh_label": "Mesh network",
|
||||
"mesh_description": "Devices relay signals to each other, enhancing coverage and reliability.",
|
||||
"add_device": "Add device",
|
||||
"save_device_failed": "Saving new device info failed"
|
||||
},
|
||||
"validate_dsk_pin": {
|
||||
"title": "Verify key",
|
||||
"text": "Find the key on your device or manual and enter the 5-digit PIN.",
|
||||
"placeholder": "PIN"
|
||||
},
|
||||
"added_insecure": {
|
||||
"title": "Device added insecurely",
|
||||
"text": "The device {name} has been added to your Z-Wave network.",
|
||||
"added_insecurely_text": "{deviceName} has been added without secure inclusion. You can still use your device.",
|
||||
"try_again_text": "You can try again by removing the device and adding it again.",
|
||||
"view_device": "View device",
|
||||
"low_security_reason": {
|
||||
"0": "Security bootstrapping was canceled by the user.",
|
||||
"1": "The required security keys were not configured in the driver.",
|
||||
@ -5905,6 +5966,11 @@
|
||||
"8": "Unknown error occurred."
|
||||
}
|
||||
},
|
||||
"grant_security_classes": {
|
||||
"title": "Security classes",
|
||||
"description": "The device has requested the following security classes"
|
||||
}
|
||||
},
|
||||
"provisioned": {
|
||||
"dsk": "DSK",
|
||||
"security_classes": "Security classes",
|
||||
|
Loading…
x
Reference in New Issue
Block a user