mirror of
https://github.com/home-assistant/frontend.git
synced 2025-09-10 13:39:43 +00:00
Compare commits
1 Commits
20211201.0
...
ha-formfie
Author | SHA1 | Date | |
---|---|---|---|
![]() |
294967014d |
@@ -79,11 +79,6 @@ function copyFonts(staticDir) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyQrScannerWorker(staticDir) {
|
|
||||||
const staticPath = genStaticPath(staticDir);
|
|
||||||
copyFileDir(npmPath("qr-scanner/qr-scanner-worker.min.js"), staticPath("js"));
|
|
||||||
}
|
|
||||||
|
|
||||||
function copyMapPanel(staticDir) {
|
function copyMapPanel(staticDir) {
|
||||||
const staticPath = genStaticPath(staticDir);
|
const staticPath = genStaticPath(staticDir);
|
||||||
copyFileDir(
|
copyFileDir(
|
||||||
@@ -130,9 +125,6 @@ gulp.task("copy-static-app", async () => {
|
|||||||
|
|
||||||
// Panel assets
|
// Panel assets
|
||||||
copyMapPanel(staticDir);
|
copyMapPanel(staticDir);
|
||||||
|
|
||||||
// Qr Scanner assets
|
|
||||||
copyQrScannerWorker(staticDir);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task("copy-static-demo", async () => {
|
gulp.task("copy-static-demo", async () => {
|
||||||
|
@@ -82,9 +82,6 @@ export const mockEnergy = (hass: MockHomeAssistant) => {
|
|||||||
],
|
],
|
||||||
}));
|
}));
|
||||||
hass.mockWS("energy/info", () => ({ cost_sensors: [] }));
|
hass.mockWS("energy/info", () => ({ cost_sensors: [] }));
|
||||||
hass.mockWS("energy/fossil_energy_consumption", ({ period }) => ({
|
|
||||||
start: period === "month" ? 500 : period === "day" ? 20 : 5,
|
|
||||||
}));
|
|
||||||
const todayString = format(startOfToday(), "yyyy-MM-dd");
|
const todayString = format(startOfToday(), "yyyy-MM-dd");
|
||||||
const tomorrowString = format(startOfTomorrow(), "yyyy-MM-dd");
|
const tomorrowString = format(startOfTomorrow(), "yyyy-MM-dd");
|
||||||
hass.mockWS(
|
hass.mockWS(
|
||||||
|
@@ -1,10 +1,4 @@
|
|||||||
import {
|
import { addHours, differenceInHours, endOfDay } from "date-fns";
|
||||||
addDays,
|
|
||||||
addHours,
|
|
||||||
addMonths,
|
|
||||||
differenceInHours,
|
|
||||||
endOfDay,
|
|
||||||
} from "date-fns";
|
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { StatisticValue } from "../../../src/data/history";
|
import { StatisticValue } from "../../../src/data/history";
|
||||||
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||||
@@ -76,7 +70,6 @@ const generateMeanStatistics = (
|
|||||||
id: string,
|
id: string,
|
||||||
start: Date,
|
start: Date,
|
||||||
end: Date,
|
end: Date,
|
||||||
period: "5minute" | "hour" | "day" | "month" = "hour",
|
|
||||||
initValue: number,
|
initValue: number,
|
||||||
maxDiff: number
|
maxDiff: number
|
||||||
) => {
|
) => {
|
||||||
@@ -91,7 +84,6 @@ const generateMeanStatistics = (
|
|||||||
statistics.push({
|
statistics.push({
|
||||||
statistic_id: id,
|
statistic_id: id,
|
||||||
start: currentDate.toISOString(),
|
start: currentDate.toISOString(),
|
||||||
end: currentDate.toISOString(),
|
|
||||||
mean,
|
mean,
|
||||||
min: mean - Math.random() * maxDiff,
|
min: mean - Math.random() * maxDiff,
|
||||||
max: mean + Math.random() * maxDiff,
|
max: mean + Math.random() * maxDiff,
|
||||||
@@ -100,12 +92,7 @@ const generateMeanStatistics = (
|
|||||||
sum: null,
|
sum: null,
|
||||||
});
|
});
|
||||||
lastVal = mean;
|
lastVal = mean;
|
||||||
currentDate =
|
currentDate = addHours(currentDate, 1);
|
||||||
period === "day"
|
|
||||||
? addDays(currentDate, 1)
|
|
||||||
: period === "month"
|
|
||||||
? addMonths(currentDate, 1)
|
|
||||||
: addHours(currentDate, 1);
|
|
||||||
}
|
}
|
||||||
return statistics;
|
return statistics;
|
||||||
};
|
};
|
||||||
@@ -114,7 +101,6 @@ const generateSumStatistics = (
|
|||||||
id: string,
|
id: string,
|
||||||
start: Date,
|
start: Date,
|
||||||
end: Date,
|
end: Date,
|
||||||
period: "5minute" | "hour" | "day" | "month" = "hour",
|
|
||||||
initValue: number,
|
initValue: number,
|
||||||
maxDiff: number
|
maxDiff: number
|
||||||
) => {
|
) => {
|
||||||
@@ -129,7 +115,6 @@ const generateSumStatistics = (
|
|||||||
statistics.push({
|
statistics.push({
|
||||||
statistic_id: id,
|
statistic_id: id,
|
||||||
start: currentDate.toISOString(),
|
start: currentDate.toISOString(),
|
||||||
end: currentDate.toISOString(),
|
|
||||||
mean: null,
|
mean: null,
|
||||||
min: null,
|
min: null,
|
||||||
max: null,
|
max: null,
|
||||||
@@ -137,12 +122,7 @@ const generateSumStatistics = (
|
|||||||
state: initValue + sum,
|
state: initValue + sum,
|
||||||
sum,
|
sum,
|
||||||
});
|
});
|
||||||
currentDate =
|
currentDate = addHours(currentDate, 1);
|
||||||
period === "day"
|
|
||||||
? addDays(currentDate, 1)
|
|
||||||
: period === "month"
|
|
||||||
? addMonths(currentDate, 1)
|
|
||||||
: addHours(currentDate, 1);
|
|
||||||
}
|
}
|
||||||
return statistics;
|
return statistics;
|
||||||
};
|
};
|
||||||
@@ -151,7 +131,6 @@ const generateCurvedStatistics = (
|
|||||||
id: string,
|
id: string,
|
||||||
start: Date,
|
start: Date,
|
||||||
end: Date,
|
end: Date,
|
||||||
_period: "5minute" | "hour" | "day" | "month" = "hour",
|
|
||||||
initValue: number,
|
initValue: number,
|
||||||
maxDiff: number,
|
maxDiff: number,
|
||||||
metered: boolean
|
metered: boolean
|
||||||
@@ -170,7 +149,6 @@ const generateCurvedStatistics = (
|
|||||||
statistics.push({
|
statistics.push({
|
||||||
statistic_id: id,
|
statistic_id: id,
|
||||||
start: currentDate.toISOString(),
|
start: currentDate.toISOString(),
|
||||||
end: currentDate.toISOString(),
|
|
||||||
mean: null,
|
mean: null,
|
||||||
min: null,
|
min: null,
|
||||||
max: null,
|
max: null,
|
||||||
@@ -189,38 +167,11 @@ const generateCurvedStatistics = (
|
|||||||
|
|
||||||
const statisticsFunctions: Record<
|
const statisticsFunctions: Record<
|
||||||
string,
|
string,
|
||||||
(
|
(id: string, start: Date, end: Date) => StatisticValue[]
|
||||||
id: string,
|
|
||||||
start: Date,
|
|
||||||
end: Date,
|
|
||||||
period: "5minute" | "hour" | "day" | "month"
|
|
||||||
) => StatisticValue[]
|
|
||||||
> = {
|
> = {
|
||||||
"sensor.energy_consumption_tarif_1": (
|
"sensor.energy_consumption_tarif_1": (id: string, start: Date, end: Date) => {
|
||||||
id: string,
|
|
||||||
start: Date,
|
|
||||||
end: Date,
|
|
||||||
period = "hour"
|
|
||||||
) => {
|
|
||||||
if (period !== "hour") {
|
|
||||||
return generateSumStatistics(
|
|
||||||
id,
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
period,
|
|
||||||
0,
|
|
||||||
period === "day" ? 17 : 504
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const morningEnd = new Date(start.getTime() + 10 * 60 * 60 * 1000);
|
const morningEnd = new Date(start.getTime() + 10 * 60 * 60 * 1000);
|
||||||
const morningLow = generateSumStatistics(
|
const morningLow = generateSumStatistics(id, start, morningEnd, 0, 0.7);
|
||||||
id,
|
|
||||||
start,
|
|
||||||
morningEnd,
|
|
||||||
period,
|
|
||||||
0,
|
|
||||||
0.7
|
|
||||||
);
|
|
||||||
const eveningStart = new Date(start.getTime() + 20 * 60 * 60 * 1000);
|
const eveningStart = new Date(start.getTime() + 20 * 60 * 60 * 1000);
|
||||||
const morningFinalVal = morningLow.length
|
const morningFinalVal = morningLow.length
|
||||||
? morningLow[morningLow.length - 1].sum!
|
? morningLow[morningLow.length - 1].sum!
|
||||||
@@ -229,7 +180,6 @@ const statisticsFunctions: Record<
|
|||||||
id,
|
id,
|
||||||
morningEnd,
|
morningEnd,
|
||||||
eveningStart,
|
eveningStart,
|
||||||
period,
|
|
||||||
morningFinalVal,
|
morningFinalVal,
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
@@ -237,71 +187,39 @@ const statisticsFunctions: Record<
|
|||||||
id,
|
id,
|
||||||
eveningStart,
|
eveningStart,
|
||||||
end,
|
end,
|
||||||
period,
|
|
||||||
morningFinalVal,
|
morningFinalVal,
|
||||||
0.7
|
0.7
|
||||||
);
|
);
|
||||||
return [...morningLow, ...empty, ...eveningLow];
|
return [...morningLow, ...empty, ...eveningLow];
|
||||||
},
|
},
|
||||||
"sensor.energy_consumption_tarif_2": (
|
"sensor.energy_consumption_tarif_2": (id: string, start: Date, end: Date) => {
|
||||||
id: string,
|
|
||||||
start: Date,
|
|
||||||
end: Date,
|
|
||||||
period = "hour"
|
|
||||||
) => {
|
|
||||||
if (period !== "hour") {
|
|
||||||
return generateSumStatistics(
|
|
||||||
id,
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
period,
|
|
||||||
0,
|
|
||||||
period === "day" ? 17 : 504
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const morningEnd = new Date(start.getTime() + 9 * 60 * 60 * 1000);
|
const morningEnd = new Date(start.getTime() + 9 * 60 * 60 * 1000);
|
||||||
const eveningStart = new Date(start.getTime() + 20 * 60 * 60 * 1000);
|
const eveningStart = new Date(start.getTime() + 20 * 60 * 60 * 1000);
|
||||||
const highTarif = generateSumStatistics(
|
const highTarif = generateSumStatistics(
|
||||||
id,
|
id,
|
||||||
morningEnd,
|
morningEnd,
|
||||||
eveningStart,
|
eveningStart,
|
||||||
period,
|
|
||||||
0,
|
0,
|
||||||
0.3
|
0.3
|
||||||
);
|
);
|
||||||
const highTarifFinalVal = highTarif.length
|
const highTarifFinalVal = highTarif.length
|
||||||
? highTarif[highTarif.length - 1].sum!
|
? highTarif[highTarif.length - 1].sum!
|
||||||
: 0;
|
: 0;
|
||||||
const morning = generateSumStatistics(id, start, morningEnd, period, 0, 0);
|
const morning = generateSumStatistics(id, start, morningEnd, 0, 0);
|
||||||
const evening = generateSumStatistics(
|
const evening = generateSumStatistics(
|
||||||
id,
|
id,
|
||||||
eveningStart,
|
eveningStart,
|
||||||
end,
|
end,
|
||||||
period,
|
|
||||||
highTarifFinalVal,
|
highTarifFinalVal,
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
return [...morning, ...highTarif, ...evening];
|
return [...morning, ...highTarif, ...evening];
|
||||||
},
|
},
|
||||||
"sensor.energy_production_tarif_1": (id, start, end, period = "hour") =>
|
"sensor.energy_production_tarif_1": (id, start, end) =>
|
||||||
generateSumStatistics(id, start, end, period, 0, 0),
|
generateSumStatistics(id, start, end, 0, 0),
|
||||||
"sensor.energy_production_tarif_1_compensation": (
|
"sensor.energy_production_tarif_1_compensation": (id, start, end) =>
|
||||||
id,
|
generateSumStatistics(id, start, end, 0, 0),
|
||||||
start,
|
"sensor.energy_production_tarif_2": (id, start, end) => {
|
||||||
end,
|
|
||||||
period = "hour"
|
|
||||||
) => generateSumStatistics(id, start, end, period, 0, 0),
|
|
||||||
"sensor.energy_production_tarif_2": (id, start, end, period = "hour") => {
|
|
||||||
if (period !== "hour") {
|
|
||||||
return generateSumStatistics(
|
|
||||||
id,
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
period,
|
|
||||||
0,
|
|
||||||
period === "day" ? 17 : 504
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const productionStart = new Date(start.getTime() + 9 * 60 * 60 * 1000);
|
const productionStart = new Date(start.getTime() + 9 * 60 * 60 * 1000);
|
||||||
const productionEnd = new Date(start.getTime() + 21 * 60 * 60 * 1000);
|
const productionEnd = new Date(start.getTime() + 21 * 60 * 60 * 1000);
|
||||||
const dayEnd = new Date(endOfDay(productionEnd));
|
const dayEnd = new Date(endOfDay(productionEnd));
|
||||||
@@ -309,7 +227,6 @@ const statisticsFunctions: Record<
|
|||||||
id,
|
id,
|
||||||
productionStart,
|
productionStart,
|
||||||
productionEnd,
|
productionEnd,
|
||||||
period,
|
|
||||||
0,
|
0,
|
||||||
0.15,
|
0.15,
|
||||||
true
|
true
|
||||||
@@ -317,43 +234,18 @@ const statisticsFunctions: Record<
|
|||||||
const productionFinalVal = production.length
|
const productionFinalVal = production.length
|
||||||
? production[production.length - 1].sum!
|
? production[production.length - 1].sum!
|
||||||
: 0;
|
: 0;
|
||||||
const morning = generateSumStatistics(
|
const morning = generateSumStatistics(id, start, productionStart, 0, 0);
|
||||||
id,
|
|
||||||
start,
|
|
||||||
productionStart,
|
|
||||||
period,
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
const evening = generateSumStatistics(
|
const evening = generateSumStatistics(
|
||||||
id,
|
id,
|
||||||
productionEnd,
|
productionEnd,
|
||||||
dayEnd,
|
dayEnd,
|
||||||
period,
|
|
||||||
productionFinalVal,
|
productionFinalVal,
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
const rest = generateSumStatistics(
|
const rest = generateSumStatistics(id, dayEnd, end, productionFinalVal, 1);
|
||||||
id,
|
|
||||||
dayEnd,
|
|
||||||
end,
|
|
||||||
period,
|
|
||||||
productionFinalVal,
|
|
||||||
1
|
|
||||||
);
|
|
||||||
return [...morning, ...production, ...evening, ...rest];
|
return [...morning, ...production, ...evening, ...rest];
|
||||||
},
|
},
|
||||||
"sensor.solar_production": (id, start, end, period = "hour") => {
|
"sensor.solar_production": (id, start, end) => {
|
||||||
if (period !== "hour") {
|
|
||||||
return generateSumStatistics(
|
|
||||||
id,
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
period,
|
|
||||||
0,
|
|
||||||
period === "day" ? 17 : 504
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const productionStart = new Date(start.getTime() + 7 * 60 * 60 * 1000);
|
const productionStart = new Date(start.getTime() + 7 * 60 * 60 * 1000);
|
||||||
const productionEnd = new Date(start.getTime() + 23 * 60 * 60 * 1000);
|
const productionEnd = new Date(start.getTime() + 23 * 60 * 60 * 1000);
|
||||||
const dayEnd = new Date(endOfDay(productionEnd));
|
const dayEnd = new Date(endOfDay(productionEnd));
|
||||||
@@ -361,7 +253,6 @@ const statisticsFunctions: Record<
|
|||||||
id,
|
id,
|
||||||
productionStart,
|
productionStart,
|
||||||
productionEnd,
|
productionEnd,
|
||||||
period,
|
|
||||||
0,
|
0,
|
||||||
0.3,
|
0.3,
|
||||||
true
|
true
|
||||||
@@ -369,32 +260,19 @@ const statisticsFunctions: Record<
|
|||||||
const productionFinalVal = production.length
|
const productionFinalVal = production.length
|
||||||
? production[production.length - 1].sum!
|
? production[production.length - 1].sum!
|
||||||
: 0;
|
: 0;
|
||||||
const morning = generateSumStatistics(
|
const morning = generateSumStatistics(id, start, productionStart, 0, 0);
|
||||||
id,
|
|
||||||
start,
|
|
||||||
productionStart,
|
|
||||||
period,
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
const evening = generateSumStatistics(
|
const evening = generateSumStatistics(
|
||||||
id,
|
id,
|
||||||
productionEnd,
|
productionEnd,
|
||||||
dayEnd,
|
dayEnd,
|
||||||
period,
|
|
||||||
productionFinalVal,
|
productionFinalVal,
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
const rest = generateSumStatistics(
|
const rest = generateSumStatistics(id, dayEnd, end, productionFinalVal, 2);
|
||||||
id,
|
|
||||||
dayEnd,
|
|
||||||
end,
|
|
||||||
period,
|
|
||||||
productionFinalVal,
|
|
||||||
2
|
|
||||||
);
|
|
||||||
return [...morning, ...production, ...evening, ...rest];
|
return [...morning, ...production, ...evening, ...rest];
|
||||||
},
|
},
|
||||||
|
"sensor.grid_fossil_fuel_percentage": (id, start, end) =>
|
||||||
|
generateMeanStatistics(id, start, end, 35, 1.3),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mockHistory = (mockHass: MockHomeAssistant) => {
|
export const mockHistory = (mockHass: MockHomeAssistant) => {
|
||||||
@@ -469,7 +347,7 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
|
|||||||
mockHass.mockWS("history/list_statistic_ids", () => []);
|
mockHass.mockWS("history/list_statistic_ids", () => []);
|
||||||
mockHass.mockWS(
|
mockHass.mockWS(
|
||||||
"history/statistics_during_period",
|
"history/statistics_during_period",
|
||||||
({ statistic_ids, start_time, end_time, period }, hass) => {
|
({ statistic_ids, start_time, end_time }, hass) => {
|
||||||
const start = new Date(start_time);
|
const start = new Date(start_time);
|
||||||
const end = end_time ? new Date(end_time) : new Date();
|
const end = end_time ? new Date(end_time) : new Date();
|
||||||
|
|
||||||
@@ -477,7 +355,7 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
|
|||||||
|
|
||||||
statistic_ids.forEach((id: string) => {
|
statistic_ids.forEach((id: string) => {
|
||||||
if (id in statisticsFunctions) {
|
if (id in statisticsFunctions) {
|
||||||
statistics[id] = statisticsFunctions[id](id, start, end, period);
|
statistics[id] = statisticsFunctions[id](id, start, end);
|
||||||
} else {
|
} else {
|
||||||
const entityState = hass.states[id];
|
const entityState = hass.states[id];
|
||||||
const state = entityState ? Number(entityState.state) : 1;
|
const state = entityState ? Number(entityState.state) : 1;
|
||||||
@@ -487,7 +365,6 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
|
|||||||
id,
|
id,
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
period,
|
|
||||||
state,
|
state,
|
||||||
state * (state > 80 ? 0.01 : 0.05)
|
state * (state > 80 ? 0.01 : 0.05)
|
||||||
)
|
)
|
||||||
@@ -495,7 +372,6 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
|
|||||||
id,
|
id,
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
period,
|
|
||||||
state,
|
state,
|
||||||
state * (state > 80 ? 0.05 : 0.1)
|
state * (state > 80 ? 0.05 : 0.1)
|
||||||
);
|
);
|
||||||
|
@@ -1,88 +0,0 @@
|
|||||||
import { css, html, LitElement, TemplateResult } from "lit";
|
|
||||||
import { customElement } from "lit/decorators";
|
|
||||||
import "../../../src/components/ha-card";
|
|
||||||
import "../../../src/components/ha-faded";
|
|
||||||
import "../../../src/components/ha-markdown";
|
|
||||||
|
|
||||||
const LONG_TEXT = `
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc laoreet velit ut elit volutpat, eget ultrices odio lacinia. In imperdiet malesuada est, nec sagittis metus ultricies quis. Sed nisl ex, convallis porttitor ante quis, hendrerit tristique justo. Mauris pharetra venenatis augue, eu maximus sem cursus in. Quisque sed consequat risus. Suspendisse facilisis ligula a odio consectetur condimentum. Curabitur vehicula elit nec augue mollis, et volutpat massa dictum.
|
|
||||||
|
|
||||||
Nam pellentesque auctor rutrum. Suspendisse elit est, sodales vel diam nec, porttitor faucibus massa. Ut pretium ac orci eu pharetra. Praesent in nibh at magna viverra rutrum eu vitae tortor. Etiam eget sem ex. Fusce tristique odio nec lacus mattis, vitae tempor nunc malesuada. Maecenas faucibus magna vel libero maximus egestas. Vestibulum luctus semper velit, in lobortis risus tempus non. Curabitur bibendum ornare commodo. Quisque commodo neque sit amet tincidunt lacinia. Proin elementum ante velit, eu congue nulla semper quis. Pellentesque consequat vel nunc at scelerisque. Mauris sit amet venenatis diam, blandit viverra leo. Integer commodo laoreet orci.
|
|
||||||
|
|
||||||
Curabitur ipsum tortor, sodales ut augue sed, commodo porttitor libero. Pellentesque molestie vitae mi consectetur tempor. In sed lectus consequat, lobortis neque non, semper ipsum. Etiam eget ex et nibh sagittis pulvinar lacinia ac mauris. Aenean ligula eros, viverra ac nibh at, venenatis semper quam. Sed interdum ligula sit amet massa tincidunt tincidunt. Suspendisse potenti. Aliquam egestas facilisis est, sed faucibus erat scelerisque id. Duis dolor quam, viverra vitae orci euismod, laoreet pellentesque justo. Nunc malesuada non erat at ullamcorper. Mauris eget posuere odio. Vestibulum turpis nunc, pharetra eget ante in, feugiat mollis justo. Proin porttitor, diam nec vulputate pretium, tellus arcu rhoncus turpis, a blandit nisi nulla quis arcu. Nunc ac ullamcorper ligula, nec facilisis leo.
|
|
||||||
|
|
||||||
In vitae eros sollicitudin, iaculis ex eget, egestas orci. Etiam sed pretium lorem. Nam nisi enim, consectetur sit amet semper ac, semper pharetra diam. In pulvinar neque sapien, ac ullamcorper est lacinia a. Etiam tincidunt velit sed diam malesuada, eu ornare ex consectetur. Phasellus in imperdiet tellus. Sed bibendum, dui sit amet fringilla aliquet, enim odio sollicitudin lorem, vel semper turpis mauris vel mauris. Aenean congue magna ac massa cursus, in dictum orci commodo. Pellentesque mollis velit in sollicitudin tincidunt. Vestibulum et efficitur nulla.
|
|
||||||
|
|
||||||
Quisque posuere, velit sed porttitor dapibus, neque augue fringilla felis, eu luctus nisi nisl nec ipsum. Curabitur pellentesque ac lectus eget ultricies. Vestibulum est dolor, lacinia pharetra vulputate a, facilisis a magna. Nam vitae arcu nibh. Praesent finibus blandit ante, ac gravida ex mollis eget. Donec quam est, pulvinar vitae neque ut, bibendum aliquam erat. Nullam mollis arcu at sem tincidunt, in tristique lectus facilisis. Aenean ut lacus vel nisl finibus iaculis non a turpis. Integer eget ipsum ante. Donec nunc neque, vestibulum ac magna ac, posuere scelerisque dui. Pellentesque massa nibh, rhoncus id dolor quis, placerat posuere turpis. Donec aliquet augue nisi, eu finibus dui auctor et. Vestibulum eu varius lorem. Quisque lectus ante, malesuada pretium risus eget, interdum mattis enim.
|
|
||||||
`;
|
|
||||||
|
|
||||||
const SMALL_TEXT = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
|
|
||||||
|
|
||||||
@customElement("demo-ha-faded")
|
|
||||||
export class DemoHaFaded extends LitElement {
|
|
||||||
protected render(): TemplateResult {
|
|
||||||
return html`
|
|
||||||
<ha-card header="ha-faded demo">
|
|
||||||
<div class="card-content">
|
|
||||||
<h3>Long text directly as slotted content</h3>
|
|
||||||
<ha-faded>${LONG_TEXT}</ha-faded>
|
|
||||||
<h3>Long text with slotted element</h3>
|
|
||||||
<ha-faded><span>${LONG_TEXT}</span></ha-faded>
|
|
||||||
<h3>No text</h3>
|
|
||||||
<ha-faded><span></span></ha-faded>
|
|
||||||
<h3>Smal text</h3>
|
|
||||||
<ha-faded><span>${SMALL_TEXT}</span></ha-faded>
|
|
||||||
<h3>Long text in markdown</h3>
|
|
||||||
<ha-faded>
|
|
||||||
<ha-markdown .content=${LONG_TEXT}> </ha-markdown>
|
|
||||||
</ha-faded>
|
|
||||||
<h3>Missing 1px from hiding</h3>
|
|
||||||
<ha-faded faded-height="87">
|
|
||||||
<span>
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc
|
|
||||||
laoreet velit ut elit volutpat, eget ultrices odio lacinia. In
|
|
||||||
imperdiet malesuada est, nec sagittis metus ultricies quis. Sed
|
|
||||||
nisl ex, convallis porttitor ante quis, hendrerit tristique justo.
|
|
||||||
Mauris pharetra venenatis augue, eu maximus sem cursus in. Quisque
|
|
||||||
sed consequat risus. Suspendisse facilisis ligula a odio
|
|
||||||
consectetur condimentum. Curabitur vehicula elit nec augue mollis,
|
|
||||||
et volutpat massa dictum. Nam pellentesque auctor rutrum.
|
|
||||||
Suspendisse elit est, sodales vel diam nec, porttitor faucibus
|
|
||||||
massa. Ut pretium ac orci eu pharetra.
|
|
||||||
</span>
|
|
||||||
</ha-faded>
|
|
||||||
<h3>1px over hiding point</h3>
|
|
||||||
<ha-faded faded-height="85">
|
|
||||||
<span>
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc
|
|
||||||
laoreet velit ut elit volutpat, eget ultrices odio lacinia. In
|
|
||||||
imperdiet malesuada est, nec sagittis metus ultricies quis. Sed
|
|
||||||
nisl ex, convallis porttitor ante quis, hendrerit tristique justo.
|
|
||||||
Mauris pharetra venenatis augue, eu maximus sem cursus in. Quisque
|
|
||||||
sed consequat risus. Suspendisse facilisis ligula a odio
|
|
||||||
consectetur condimentum. Curabitur vehicula elit nec augue mollis,
|
|
||||||
et volutpat massa dictum. Nam pellentesque auctor rutrum.
|
|
||||||
Suspendisse elit est, sodales vel diam nec, porttitor faucibus
|
|
||||||
massa. Ut pretium ac orci eu pharetra.
|
|
||||||
</span>
|
|
||||||
</ha-faded>
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles() {
|
|
||||||
return css`
|
|
||||||
ha-card {
|
|
||||||
max-width: 600px;
|
|
||||||
margin: 24px auto;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"demo-ha-faded": DemoHaFaded;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,164 +0,0 @@
|
|||||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
|
||||||
import { customElement, property, query } from "lit/decorators";
|
|
||||||
import "../../../src/components/ha-card";
|
|
||||||
import {
|
|
||||||
SUPPORT_OPEN,
|
|
||||||
SUPPORT_STOP,
|
|
||||||
SUPPORT_CLOSE,
|
|
||||||
SUPPORT_SET_POSITION,
|
|
||||||
SUPPORT_OPEN_TILT,
|
|
||||||
SUPPORT_STOP_TILT,
|
|
||||||
SUPPORT_CLOSE_TILT,
|
|
||||||
SUPPORT_SET_TILT_POSITION,
|
|
||||||
} from "../../../src/data/cover";
|
|
||||||
import "../../../src/dialogs/more-info/more-info-content";
|
|
||||||
import { getEntity } from "../../../src/fake_data/entity";
|
|
||||||
import {
|
|
||||||
MockHomeAssistant,
|
|
||||||
provideHass,
|
|
||||||
} from "../../../src/fake_data/provide_hass";
|
|
||||||
import "../components/demo-more-infos";
|
|
||||||
|
|
||||||
const ENTITIES = [
|
|
||||||
getEntity("cover", "position_buttons", "on", {
|
|
||||||
friendly_name: "Position Buttons",
|
|
||||||
supported_features: SUPPORT_OPEN + SUPPORT_STOP + SUPPORT_CLOSE,
|
|
||||||
}),
|
|
||||||
getEntity("cover", "position_slider_half", "on", {
|
|
||||||
friendly_name: "Position Half-Open",
|
|
||||||
supported_features:
|
|
||||||
SUPPORT_OPEN + SUPPORT_STOP + SUPPORT_CLOSE + SUPPORT_SET_POSITION,
|
|
||||||
current_position: 50,
|
|
||||||
}),
|
|
||||||
getEntity("cover", "position_slider_open", "on", {
|
|
||||||
friendly_name: "Position Open",
|
|
||||||
supported_features:
|
|
||||||
SUPPORT_OPEN + SUPPORT_STOP + SUPPORT_CLOSE + SUPPORT_SET_POSITION,
|
|
||||||
current_position: 100,
|
|
||||||
}),
|
|
||||||
getEntity("cover", "position_slider_closed", "on", {
|
|
||||||
friendly_name: "Position Closed",
|
|
||||||
supported_features:
|
|
||||||
SUPPORT_OPEN + SUPPORT_STOP + SUPPORT_CLOSE + SUPPORT_SET_POSITION,
|
|
||||||
current_position: 0,
|
|
||||||
}),
|
|
||||||
getEntity("cover", "tilt_buttons", "on", {
|
|
||||||
friendly_name: "Tilt Buttons",
|
|
||||||
supported_features:
|
|
||||||
SUPPORT_OPEN_TILT + SUPPORT_STOP_TILT + SUPPORT_CLOSE_TILT,
|
|
||||||
}),
|
|
||||||
getEntity("cover", "tilt_slider_half", "on", {
|
|
||||||
friendly_name: "Tilt Half-Open",
|
|
||||||
supported_features:
|
|
||||||
SUPPORT_OPEN_TILT +
|
|
||||||
SUPPORT_STOP_TILT +
|
|
||||||
SUPPORT_CLOSE_TILT +
|
|
||||||
SUPPORT_SET_TILT_POSITION,
|
|
||||||
current_tilt_position: 50,
|
|
||||||
}),
|
|
||||||
getEntity("cover", "tilt_slider_open", "on", {
|
|
||||||
friendly_name: "Tilt Open",
|
|
||||||
supported_features:
|
|
||||||
SUPPORT_OPEN_TILT +
|
|
||||||
SUPPORT_STOP_TILT +
|
|
||||||
SUPPORT_CLOSE_TILT +
|
|
||||||
SUPPORT_SET_TILT_POSITION,
|
|
||||||
current_tilt_position: 100,
|
|
||||||
}),
|
|
||||||
getEntity("cover", "tilt_slider_closed", "on", {
|
|
||||||
friendly_name: "Tilt Closed",
|
|
||||||
supported_features:
|
|
||||||
SUPPORT_OPEN_TILT +
|
|
||||||
SUPPORT_STOP_TILT +
|
|
||||||
SUPPORT_CLOSE_TILT +
|
|
||||||
SUPPORT_SET_TILT_POSITION,
|
|
||||||
current_tilt_position: 0,
|
|
||||||
}),
|
|
||||||
getEntity("cover", "position_slider_tilt_slider", "on", {
|
|
||||||
friendly_name: "Both Sliders",
|
|
||||||
supported_features:
|
|
||||||
SUPPORT_OPEN +
|
|
||||||
SUPPORT_STOP +
|
|
||||||
SUPPORT_CLOSE +
|
|
||||||
SUPPORT_SET_POSITION +
|
|
||||||
SUPPORT_OPEN_TILT +
|
|
||||||
SUPPORT_STOP_TILT +
|
|
||||||
SUPPORT_CLOSE_TILT +
|
|
||||||
SUPPORT_SET_TILT_POSITION,
|
|
||||||
current_position: 30,
|
|
||||||
current_tilt_position: 70,
|
|
||||||
}),
|
|
||||||
getEntity("cover", "position_tilt_slider", "on", {
|
|
||||||
friendly_name: "Position & Tilt Slider",
|
|
||||||
supported_features:
|
|
||||||
SUPPORT_OPEN +
|
|
||||||
SUPPORT_STOP +
|
|
||||||
SUPPORT_CLOSE +
|
|
||||||
SUPPORT_OPEN_TILT +
|
|
||||||
SUPPORT_STOP_TILT +
|
|
||||||
SUPPORT_CLOSE_TILT +
|
|
||||||
SUPPORT_SET_TILT_POSITION,
|
|
||||||
current_tilt_position: 70,
|
|
||||||
}),
|
|
||||||
getEntity("cover", "position_slider_tilt", "on", {
|
|
||||||
friendly_name: "Position Slider & Tilt",
|
|
||||||
supported_features:
|
|
||||||
SUPPORT_OPEN +
|
|
||||||
SUPPORT_STOP +
|
|
||||||
SUPPORT_CLOSE +
|
|
||||||
SUPPORT_SET_POSITION +
|
|
||||||
SUPPORT_OPEN_TILT +
|
|
||||||
SUPPORT_STOP_TILT +
|
|
||||||
SUPPORT_CLOSE_TILT,
|
|
||||||
current_position: 30,
|
|
||||||
}),
|
|
||||||
getEntity("cover", "position_slider_only_tilt_slider", "on", {
|
|
||||||
friendly_name: "Position Slider Only & Tilt Buttons",
|
|
||||||
supported_features:
|
|
||||||
SUPPORT_SET_POSITION +
|
|
||||||
SUPPORT_OPEN_TILT +
|
|
||||||
SUPPORT_STOP_TILT +
|
|
||||||
SUPPORT_CLOSE_TILT,
|
|
||||||
current_position: 30,
|
|
||||||
}),
|
|
||||||
getEntity("cover", "position_slider_only_tilt", "on", {
|
|
||||||
friendly_name: "Position Slider Only & Tilt",
|
|
||||||
supported_features:
|
|
||||||
SUPPORT_SET_POSITION +
|
|
||||||
SUPPORT_OPEN_TILT +
|
|
||||||
SUPPORT_STOP_TILT +
|
|
||||||
SUPPORT_CLOSE_TILT +
|
|
||||||
SUPPORT_SET_TILT_POSITION,
|
|
||||||
current_position: 30,
|
|
||||||
current_tilt_position: 70,
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
|
|
||||||
@customElement("demo-more-info-cover")
|
|
||||||
class DemoMoreInfoCover extends LitElement {
|
|
||||||
@property() public hass!: MockHomeAssistant;
|
|
||||||
|
|
||||||
@query("demo-more-infos") private _demoRoot!: HTMLElement;
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
|
||||||
return html`
|
|
||||||
<demo-more-infos
|
|
||||||
.hass=${this.hass}
|
|
||||||
.entities=${ENTITIES.map((ent) => ent.entityId)}
|
|
||||||
></demo-more-infos>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected firstUpdated(changedProperties: PropertyValues) {
|
|
||||||
super.firstUpdated(changedProperties);
|
|
||||||
const hass = provideHass(this._demoRoot);
|
|
||||||
hass.updateTranslations(null, "en");
|
|
||||||
hass.addEntities(ENTITIES);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"demo-more-info-cover": DemoMoreInfoCover;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -173,8 +173,7 @@ export class HassioBackups extends LitElement {
|
|||||||
clickable
|
clickable
|
||||||
selectable
|
selectable
|
||||||
hasFab
|
hasFab
|
||||||
.mainPage=${!atLeastVersion(this.hass.config.version, 2021, 12)}
|
main-page
|
||||||
back-path="/config"
|
|
||||||
supervisor
|
supervisor
|
||||||
>
|
>
|
||||||
<ha-button-menu
|
<ha-button-menu
|
||||||
|
@@ -29,8 +29,7 @@ class HassioDashboard extends LitElement {
|
|||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
.route=${this.route}
|
.route=${this.route}
|
||||||
.tabs=${supervisorTabs(this.hass)}
|
.tabs=${supervisorTabs(this.hass)}
|
||||||
.mainPage=${!atLeastVersion(this.hass.config.version, 2021, 12)}
|
main-page
|
||||||
back-path="/config"
|
|
||||||
supervisor
|
supervisor
|
||||||
hasFab
|
hasFab
|
||||||
>
|
>
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { atLeastVersion } from "../../../src/common/config/version";
|
|
||||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||||
import "../../../src/layouts/hass-tabs-subpage";
|
import "../../../src/layouts/hass-tabs-subpage";
|
||||||
import { haStyle } from "../../../src/resources/styles";
|
import { haStyle } from "../../../src/resources/styles";
|
||||||
@@ -30,8 +29,7 @@ class HassioSystem extends LitElement {
|
|||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
.route=${this.route}
|
.route=${this.route}
|
||||||
.tabs=${supervisorTabs(this.hass)}
|
.tabs=${supervisorTabs(this.hass)}
|
||||||
.mainPage=${!atLeastVersion(this.hass.config.version, 2021, 12)}
|
main-page
|
||||||
back-path="/config"
|
|
||||||
supervisor
|
supervisor
|
||||||
>
|
>
|
||||||
<span slot="header"> ${this.supervisor.localize("panel.system")} </span>
|
<span slot="header"> ${this.supervisor.localize("panel.system")} </span>
|
||||||
|
@@ -16,7 +16,7 @@ import "../../../src/components/ha-alert";
|
|||||||
import "../../../src/components/ha-button-menu";
|
import "../../../src/components/ha-button-menu";
|
||||||
import "../../../src/components/ha-card";
|
import "../../../src/components/ha-card";
|
||||||
import "../../../src/components/ha-checkbox";
|
import "../../../src/components/ha-checkbox";
|
||||||
import "../../../src/components/ha-faded";
|
import "../../../src/components/ha-expansion-panel";
|
||||||
import "../../../src/components/ha-formfield";
|
import "../../../src/components/ha-formfield";
|
||||||
import "../../../src/components/ha-icon-button";
|
import "../../../src/components/ha-icon-button";
|
||||||
import "../../../src/components/ha-markdown";
|
import "../../../src/components/ha-markdown";
|
||||||
@@ -136,10 +136,10 @@ class UpdateAvailableCard extends LitElement {
|
|||||||
? html`
|
? html`
|
||||||
${this._changelogContent
|
${this._changelogContent
|
||||||
? html`
|
? html`
|
||||||
<ha-faded>
|
<ha-expansion-panel header="Changelog" outlined>
|
||||||
<ha-markdown .content=${this._changelogContent}>
|
<ha-markdown .content=${this._changelogContent}>
|
||||||
</ha-markdown>
|
</ha-markdown>
|
||||||
</ha-faded>
|
</ha-expansion-panel>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
<div class="versions">
|
<div class="versions">
|
||||||
@@ -387,6 +387,9 @@ class UpdateAvailableCard extends LitElement {
|
|||||||
ha-markdown {
|
ha-markdown {
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
ha-formfield {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -115,7 +115,6 @@
|
|||||||
"node-vibrant": "3.2.1-alpha.1",
|
"node-vibrant": "3.2.1-alpha.1",
|
||||||
"proxy-polyfill": "^0.3.2",
|
"proxy-polyfill": "^0.3.2",
|
||||||
"punycode": "^2.1.1",
|
"punycode": "^2.1.1",
|
||||||
"qr-scanner": "^1.3.0",
|
|
||||||
"qrcode": "^1.4.4",
|
"qrcode": "^1.4.4",
|
||||||
"regenerator-runtime": "^0.13.8",
|
"regenerator-runtime": "^0.13.8",
|
||||||
"resize-observer-polyfill": "^1.5.1",
|
"resize-observer-polyfill": "^1.5.1",
|
||||||
|
2
setup.py
2
setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="home-assistant-frontend",
|
name="home-assistant-frontend",
|
||||||
version="20211201.0",
|
version="20211123.0",
|
||||||
description="The Home Assistant frontend",
|
description="The Home Assistant frontend",
|
||||||
url="https://github.com/home-assistant/frontend",
|
url="https://github.com/home-assistant/frontend",
|
||||||
author="The Home Assistant Authors",
|
author="The Home Assistant Authors",
|
||||||
|
@@ -95,7 +95,7 @@ export default class HaChartBase extends LitElement {
|
|||||||
borderColor: dataset.borderColor as string,
|
borderColor: dataset.borderColor as string,
|
||||||
})}
|
})}
|
||||||
></div>
|
></div>
|
||||||
<div class="label">${dataset.label}</div>
|
${dataset.label}
|
||||||
</li>`
|
</li>`
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -278,9 +278,11 @@ export default class HaChartBase extends LitElement {
|
|||||||
}
|
}
|
||||||
.chartLegend li {
|
.chartLegend li {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: inline-grid;
|
display: inline-flex;
|
||||||
grid-auto-flow: column;
|
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
@@ -288,11 +290,6 @@ export default class HaChartBase extends LitElement {
|
|||||||
.chartLegend .hidden {
|
.chartLegend .hidden {
|
||||||
text-decoration: line-through;
|
text-decoration: line-through;
|
||||||
}
|
}
|
||||||
.chartLegend .label {
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.chartLegend .bullet,
|
.chartLegend .bullet,
|
||||||
.chartTooltip .bullet {
|
.chartTooltip .bullet {
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
|
@@ -1,30 +1,39 @@
|
|||||||
import { mdiStop } from "@mdi/js";
|
import { mdiStop } from "@mdi/js";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import type { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { customElement, property } from "lit/decorators";
|
import {
|
||||||
|
css,
|
||||||
|
CSSResultGroup,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
PropertyValues,
|
||||||
|
TemplateResult,
|
||||||
|
} from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { computeCloseIcon, computeOpenIcon } from "../common/entity/cover_icon";
|
import { computeCloseIcon, computeOpenIcon } from "../common/entity/cover_icon";
|
||||||
import {
|
|
||||||
CoverEntity,
|
|
||||||
isClosing,
|
|
||||||
isFullyClosed,
|
|
||||||
isFullyOpen,
|
|
||||||
isOpening,
|
|
||||||
supportsClose,
|
|
||||||
supportsOpen,
|
|
||||||
supportsStop,
|
|
||||||
} from "../data/cover";
|
|
||||||
import { UNAVAILABLE } from "../data/entity";
|
import { UNAVAILABLE } from "../data/entity";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
|
import CoverEntity from "../util/cover-model";
|
||||||
import "./ha-icon-button";
|
import "./ha-icon-button";
|
||||||
|
|
||||||
@customElement("ha-cover-controls")
|
@customElement("ha-cover-controls")
|
||||||
class HaCoverControls extends LitElement {
|
class HaCoverControls extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ attribute: false }) public stateObj!: CoverEntity;
|
@property({ attribute: false }) public stateObj!: HassEntity;
|
||||||
|
|
||||||
|
@state() private _entityObj?: CoverEntity;
|
||||||
|
|
||||||
|
public willUpdate(changedProperties: PropertyValues): void {
|
||||||
|
super.willUpdate(changedProperties);
|
||||||
|
|
||||||
|
if (changedProperties.has("stateObj")) {
|
||||||
|
this._entityObj = new CoverEntity(this.hass, this.stateObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (!this.stateObj) {
|
if (!this._entityObj) {
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,7 +41,7 @@ class HaCoverControls extends LitElement {
|
|||||||
<div class="state">
|
<div class="state">
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
hidden: !supportsOpen(this.stateObj),
|
hidden: !this._entityObj.supportsOpen,
|
||||||
})}
|
})}
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.dialogs.more_info_control.open_cover"
|
"ui.dialogs.more_info_control.open_cover"
|
||||||
@@ -44,7 +53,7 @@ class HaCoverControls extends LitElement {
|
|||||||
</ha-icon-button>
|
</ha-icon-button>
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
hidden: !supportsStop(this.stateObj),
|
hidden: !this._entityObj.supportsStop,
|
||||||
})}
|
})}
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.dialogs.more_info_control.stop_cover"
|
"ui.dialogs.more_info_control.stop_cover"
|
||||||
@@ -55,7 +64,7 @@ class HaCoverControls extends LitElement {
|
|||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
hidden: !supportsClose(this.stateObj),
|
hidden: !this._entityObj.supportsClose,
|
||||||
})}
|
})}
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.dialogs.more_info_control.close_cover"
|
"ui.dialogs.more_info_control.close_cover"
|
||||||
@@ -75,7 +84,8 @@ class HaCoverControls extends LitElement {
|
|||||||
}
|
}
|
||||||
const assumedState = this.stateObj.attributes.assumed_state === true;
|
const assumedState = this.stateObj.attributes.assumed_state === true;
|
||||||
return (
|
return (
|
||||||
(isFullyOpen(this.stateObj) || isOpening(this.stateObj)) && !assumedState
|
(this._entityObj.isFullyOpen || this._entityObj.isOpening) &&
|
||||||
|
!assumedState
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,30 +95,24 @@ class HaCoverControls extends LitElement {
|
|||||||
}
|
}
|
||||||
const assumedState = this.stateObj.attributes.assumed_state === true;
|
const assumedState = this.stateObj.attributes.assumed_state === true;
|
||||||
return (
|
return (
|
||||||
(isFullyClosed(this.stateObj) || isClosing(this.stateObj)) &&
|
(this._entityObj.isFullyClosed || this._entityObj.isClosing) &&
|
||||||
!assumedState
|
!assumedState
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onOpenTap(ev): void {
|
private _onOpenTap(ev): void {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
this.hass.callService("cover", "open_cover", {
|
this._entityObj.openCover();
|
||||||
entity_id: this.stateObj.entity_id,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onCloseTap(ev): void {
|
private _onCloseTap(ev): void {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
this.hass.callService("cover", "close_cover", {
|
this._entityObj.closeCover();
|
||||||
entity_id: this.stateObj.entity_id,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onStopTap(ev): void {
|
private _onStopTap(ev): void {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
this.hass.callService("cover", "stop_cover", {
|
this._entityObj.stopCover();
|
||||||
entity_id: this.stateObj.entity_id,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
|
@@ -1,33 +1,44 @@
|
|||||||
import { mdiArrowBottomLeft, mdiArrowTopRight, mdiStop } from "@mdi/js";
|
import { mdiArrowBottomLeft, mdiArrowTopRight, mdiStop } from "@mdi/js";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { customElement, property } from "lit/decorators";
|
|
||||||
import { classMap } from "lit/directives/class-map";
|
|
||||||
import {
|
import {
|
||||||
CoverEntity,
|
css,
|
||||||
isFullyClosedTilt,
|
CSSResultGroup,
|
||||||
isFullyOpenTilt,
|
html,
|
||||||
supportsCloseTilt,
|
LitElement,
|
||||||
supportsOpenTilt,
|
PropertyValues,
|
||||||
supportsStopTilt,
|
TemplateResult,
|
||||||
} from "../data/cover";
|
} from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { UNAVAILABLE } from "../data/entity";
|
import { UNAVAILABLE } from "../data/entity";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
|
import CoverEntity from "../util/cover-model";
|
||||||
import "./ha-icon-button";
|
import "./ha-icon-button";
|
||||||
|
|
||||||
@customElement("ha-cover-tilt-controls")
|
@customElement("ha-cover-tilt-controls")
|
||||||
class HaCoverTiltControls extends LitElement {
|
class HaCoverTiltControls extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ attribute: false }) stateObj!: CoverEntity;
|
@property({ attribute: false }) stateObj!: HassEntity;
|
||||||
|
|
||||||
|
@state() private _entityObj?: CoverEntity;
|
||||||
|
|
||||||
|
public willUpdate(changedProperties: PropertyValues): void {
|
||||||
|
super.willUpdate(changedProperties);
|
||||||
|
|
||||||
|
if (changedProperties.has("stateObj")) {
|
||||||
|
this._entityObj = new CoverEntity(this.hass, this.stateObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (!this.stateObj) {
|
if (!this._entityObj) {
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
return html` <ha-icon-button
|
return html` <ha-icon-button
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
invisible: !supportsOpenTilt(this.stateObj),
|
invisible: !this._entityObj.supportsOpenTilt,
|
||||||
})}
|
})}
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.dialogs.more_info_control.open_tilt_cover"
|
"ui.dialogs.more_info_control.open_tilt_cover"
|
||||||
@@ -38,7 +49,7 @@ class HaCoverTiltControls extends LitElement {
|
|||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
invisible: !supportsStopTilt(this.stateObj),
|
invisible: !this._entityObj.supportsStopTilt,
|
||||||
})}
|
})}
|
||||||
.label=${this.hass.localize("ui.dialogs.more_info_control.stop_cover")}
|
.label=${this.hass.localize("ui.dialogs.more_info_control.stop_cover")}
|
||||||
.path=${mdiStop}
|
.path=${mdiStop}
|
||||||
@@ -47,7 +58,7 @@ class HaCoverTiltControls extends LitElement {
|
|||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
invisible: !supportsCloseTilt(this.stateObj),
|
invisible: !this._entityObj.supportsCloseTilt,
|
||||||
})}
|
})}
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.dialogs.more_info_control.close_tilt_cover"
|
"ui.dialogs.more_info_control.close_tilt_cover"
|
||||||
@@ -63,7 +74,7 @@ class HaCoverTiltControls extends LitElement {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const assumedState = this.stateObj.attributes.assumed_state === true;
|
const assumedState = this.stateObj.attributes.assumed_state === true;
|
||||||
return isFullyOpenTilt(this.stateObj) && !assumedState;
|
return this._entityObj.isFullyOpenTilt && !assumedState;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _computeClosedDisabled(): boolean {
|
private _computeClosedDisabled(): boolean {
|
||||||
@@ -71,28 +82,22 @@ class HaCoverTiltControls extends LitElement {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const assumedState = this.stateObj.attributes.assumed_state === true;
|
const assumedState = this.stateObj.attributes.assumed_state === true;
|
||||||
return isFullyClosedTilt(this.stateObj) && !assumedState;
|
return this._entityObj.isFullyClosedTilt && !assumedState;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onOpenTiltTap(ev): void {
|
private _onOpenTiltTap(ev): void {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
this.hass.callService("cover", "open_cover_tilt", {
|
this._entityObj.openCoverTilt();
|
||||||
entity_id: this.stateObj.entity_id,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onCloseTiltTap(ev): void {
|
private _onCloseTiltTap(ev): void {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
this.hass.callService("cover", "close_cover_tilt", {
|
this._entityObj.closeCoverTilt();
|
||||||
entity_id: this.stateObj.entity_id,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onStopTiltTap(ev): void {
|
private _onStopTiltTap(ev): void {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
this.hass.callService("cover", "stop_cover_tilt", {
|
this._entityObj.stopCoverTilt();
|
||||||
entity_id: this.stateObj.entity_id,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
|
@@ -1,82 +0,0 @@
|
|||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
|
||||||
import { customElement, property, state } from "lit/decorators";
|
|
||||||
import { classMap } from "lit/directives/class-map";
|
|
||||||
|
|
||||||
@customElement("ha-faded")
|
|
||||||
class HaFaded extends LitElement {
|
|
||||||
@property({ type: Number, attribute: "faded-height" })
|
|
||||||
public fadedHeight = 102;
|
|
||||||
|
|
||||||
@state() _contentShown = false;
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
|
||||||
return html`
|
|
||||||
<div
|
|
||||||
class="container ${classMap({ faded: !this._contentShown })}"
|
|
||||||
style=${!this._contentShown ? `max-height: ${this.fadedHeight}px` : ""}
|
|
||||||
@click=${this._showContent}
|
|
||||||
>
|
|
||||||
<slot
|
|
||||||
@iron-resize=${
|
|
||||||
// ha-markdown-element fire this when render is complete
|
|
||||||
this._setShowContent
|
|
||||||
}
|
|
||||||
></slot>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
get _slottedHeight(): number {
|
|
||||||
return (
|
|
||||||
(
|
|
||||||
this.shadowRoot!.querySelector(".container")
|
|
||||||
?.firstElementChild as HTMLSlotElement
|
|
||||||
)
|
|
||||||
.assignedElements()
|
|
||||||
.reduce(
|
|
||||||
(partial, element) => partial + (element as HTMLElement).offsetHeight,
|
|
||||||
0
|
|
||||||
) || 0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _setShowContent() {
|
|
||||||
const height = this._slottedHeight;
|
|
||||||
this._contentShown = height !== 0 && height <= this.fadedHeight + 50;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected firstUpdated(changedProps) {
|
|
||||||
super.firstUpdated(changedProps);
|
|
||||||
this._setShowContent();
|
|
||||||
}
|
|
||||||
|
|
||||||
private _showContent(): void {
|
|
||||||
this._contentShown = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return css`
|
|
||||||
.container {
|
|
||||||
display: block;
|
|
||||||
height: auto;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
.faded {
|
|
||||||
cursor: pointer;
|
|
||||||
-webkit-mask-image: linear-gradient(
|
|
||||||
to bottom,
|
|
||||||
black 25%,
|
|
||||||
transparent 100%
|
|
||||||
);
|
|
||||||
mask-image: linear-gradient(to bottom, black 25%, transparent 100%);
|
|
||||||
overflow-y: hidden;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"ha-faded": HaFaded;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -7,11 +7,10 @@ import {
|
|||||||
PropertyValues,
|
PropertyValues,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query } from "lit/decorators";
|
||||||
import { nextRender } from "../common/util/render-status";
|
import { nextRender } from "../common/util/render-status";
|
||||||
import { getExternalConfig } from "../external_app/external_config";
|
import { getExternalConfig } from "../external_app/external_config";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
import "./ha-alert";
|
|
||||||
|
|
||||||
type HlsLite = Omit<
|
type HlsLite = Omit<
|
||||||
HlsType,
|
HlsType,
|
||||||
@@ -42,8 +41,6 @@ class HaHLSPlayer extends LitElement {
|
|||||||
// don't cache this, as we remove it on disconnects
|
// don't cache this, as we remove it on disconnects
|
||||||
@query("video") private _videoEl!: HTMLVideoElement;
|
@query("video") private _videoEl!: HTMLVideoElement;
|
||||||
|
|
||||||
@state() private _error?: string;
|
|
||||||
|
|
||||||
private _hlsPolyfillInstance?: HlsLite;
|
private _hlsPolyfillInstance?: HlsLite;
|
||||||
|
|
||||||
private _exoPlayer = false;
|
private _exoPlayer = false;
|
||||||
@@ -61,9 +58,6 @@ class HaHLSPlayer extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (this._error) {
|
|
||||||
return html`<ha-alert alert-type="error">${this._error}</ha-alert>`;
|
|
||||||
}
|
|
||||||
return html`
|
return html`
|
||||||
<video
|
<video
|
||||||
?autoplay=${this.autoPlay}
|
?autoplay=${this.autoPlay}
|
||||||
@@ -96,8 +90,6 @@ class HaHLSPlayer extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _startHls(): Promise<void> {
|
private async _startHls(): Promise<void> {
|
||||||
this._error = undefined;
|
|
||||||
|
|
||||||
const videoEl = this._videoEl;
|
const videoEl = this._videoEl;
|
||||||
const useExoPlayerPromise = this._getUseExoPlayer();
|
const useExoPlayerPromise = this._getUseExoPlayer();
|
||||||
const masterPlaylistPromise = fetch(this.url);
|
const masterPlaylistPromise = fetch(this.url);
|
||||||
@@ -117,7 +109,7 @@ class HaHLSPlayer extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!hlsSupported) {
|
if (!hlsSupported) {
|
||||||
this._error = this.hass.localize(
|
videoEl.innerHTML = this.hass.localize(
|
||||||
"ui.components.media-browser.video_not_supported"
|
"ui.components.media-browser.video_not_supported"
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@@ -204,44 +196,6 @@ class HaHLSPlayer extends LitElement {
|
|||||||
hls.on(Hls.Events.MEDIA_ATTACHED, () => {
|
hls.on(Hls.Events.MEDIA_ATTACHED, () => {
|
||||||
hls.loadSource(url);
|
hls.loadSource(url);
|
||||||
});
|
});
|
||||||
hls.on(Hls.Events.ERROR, (_, data: any) => {
|
|
||||||
if (!data.fatal) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (data.type === Hls.ErrorTypes.NETWORK_ERROR) {
|
|
||||||
switch (data.details) {
|
|
||||||
case Hls.ErrorDetails.MANIFEST_LOAD_ERROR: {
|
|
||||||
let error = "Error starting stream, see logs for details";
|
|
||||||
if (
|
|
||||||
data.response !== undefined &&
|
|
||||||
data.response.code !== undefined
|
|
||||||
) {
|
|
||||||
if (data.response.code >= 500) {
|
|
||||||
error += " (Server failure)";
|
|
||||||
} else if (data.response.code >= 400) {
|
|
||||||
error += " (Stream never started)";
|
|
||||||
} else {
|
|
||||||
error += " (" + data.response.code + ")";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this._error = error;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case Hls.ErrorDetails.MANIFEST_LOAD_TIMEOUT:
|
|
||||||
this._error = "Timeout while starting stream";
|
|
||||||
return;
|
|
||||||
default:
|
|
||||||
this._error = "Unknown stream network error (" + data.details + ")";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._error = "Error with media stream contents (" + data.details + ")";
|
|
||||||
} else if (data.type === Hls.ErrorTypes.MEDIA_ERROR) {
|
|
||||||
this._error = "Error with media stream contents (" + data.details + ")";
|
|
||||||
} else {
|
|
||||||
this._error =
|
|
||||||
"Unknown error with stream (" + data.type + ", " + data.details + ")";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _renderHLSNative(videoEl: HTMLVideoElement, url: string) {
|
private async _renderHLSNative(videoEl: HTMLVideoElement, url: string) {
|
||||||
@@ -277,11 +231,6 @@ class HaHLSPlayer extends LitElement {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
max-height: var(--video-max-height, calc(100vh - 97px));
|
max-height: var(--video-max-height, calc(100vh - 97px));
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-alert {
|
|
||||||
display: block;
|
|
||||||
padding: 100px 16px;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
import "@material/mwc-list/mwc-list-item";
|
|
||||||
import { mdiDotsVertical } from "@mdi/js";
|
|
||||||
import "@polymer/paper-tooltip/paper-tooltip";
|
|
||||||
import { css, html, LitElement, TemplateResult } from "lit";
|
import { css, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { HomeAssistant } from "../types";
|
|
||||||
import "./ha-button-menu";
|
import "./ha-button-menu";
|
||||||
import "./ha-icon-button";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
|
import "@material/mwc-icon-button";
|
||||||
import "./ha-svg-icon";
|
import "./ha-svg-icon";
|
||||||
|
import { mdiDotsVertical } from "@mdi/js";
|
||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
import "@polymer/paper-tooltip/paper-tooltip";
|
||||||
|
|
||||||
export interface IconOverflowMenuItem {
|
export interface IconOverflowMenuItem {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
@@ -37,11 +37,13 @@ export class HaIconOverflowMenu extends LitElement {
|
|||||||
corner="BOTTOM_START"
|
corner="BOTTOM_START"
|
||||||
absolute
|
absolute
|
||||||
>
|
>
|
||||||
<ha-icon-button
|
<mwc-icon-button
|
||||||
|
.title=${this.hass.localize("ui.common.menu")}
|
||||||
.label=${this.hass.localize("ui.common.overflow_menu")}
|
.label=${this.hass.localize("ui.common.overflow_menu")}
|
||||||
.path=${mdiDotsVertical}
|
|
||||||
slot="trigger"
|
slot="trigger"
|
||||||
></ha-icon-button>
|
>
|
||||||
|
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
|
||||||
|
</mwc-icon-button>
|
||||||
|
|
||||||
${this.items.map(
|
${this.items.map(
|
||||||
(item) => html`
|
(item) => html`
|
||||||
|
@@ -24,7 +24,7 @@ class HaMarkdownElement extends ReactiveElement {
|
|||||||
|
|
||||||
private async _render() {
|
private async _render() {
|
||||||
this.innerHTML = await renderMarkdown(
|
this.innerHTML = await renderMarkdown(
|
||||||
String(this.content),
|
this.content,
|
||||||
{
|
{
|
||||||
breaks: this.breaks,
|
breaks: this.breaks,
|
||||||
gfm: true,
|
gfm: true,
|
||||||
|
@@ -1,162 +0,0 @@
|
|||||||
import "@material/mwc-list/mwc-list-item";
|
|
||||||
import "@material/mwc-select/mwc-select";
|
|
||||||
import type { Select } from "@material/mwc-select/mwc-select";
|
|
||||||
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
|
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
|
||||||
import type QrScanner from "qr-scanner";
|
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
|
||||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
|
||||||
import { LocalizeFunc } from "../common/translations/localize";
|
|
||||||
import "./ha-alert";
|
|
||||||
|
|
||||||
@customElement("ha-qr-scanner")
|
|
||||||
class HaQrScanner extends LitElement {
|
|
||||||
@property() localize!: LocalizeFunc;
|
|
||||||
|
|
||||||
@state() private _cameras?: QrScanner.Camera[];
|
|
||||||
|
|
||||||
@state() private _error?: string;
|
|
||||||
|
|
||||||
private _qrScanner?: QrScanner;
|
|
||||||
|
|
||||||
private _qrNotFoundCount = 0;
|
|
||||||
|
|
||||||
@query("video", true) private _video!: HTMLVideoElement;
|
|
||||||
|
|
||||||
@query("#canvas-container", true) private _canvasContainer!: HTMLDivElement;
|
|
||||||
|
|
||||||
public disconnectedCallback(): void {
|
|
||||||
super.disconnectedCallback();
|
|
||||||
this._qrNotFoundCount = 0;
|
|
||||||
if (this._qrScanner) {
|
|
||||||
this._qrScanner.stop();
|
|
||||||
this._qrScanner.destroy();
|
|
||||||
this._qrScanner = undefined;
|
|
||||||
}
|
|
||||||
while (this._canvasContainer.lastChild) {
|
|
||||||
this._canvasContainer.removeChild(this._canvasContainer.lastChild);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public connectedCallback(): void {
|
|
||||||
super.connectedCallback();
|
|
||||||
if (this.hasUpdated && navigator.mediaDevices) {
|
|
||||||
this._loadQrScanner();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected firstUpdated() {
|
|
||||||
if (navigator.mediaDevices) {
|
|
||||||
this._loadQrScanner();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected updated(changedProps: PropertyValues) {
|
|
||||||
if (changedProps.has("_error") && this._error) {
|
|
||||||
fireEvent(this, "qr-code-error", { message: this._error });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
|
||||||
return html`${this._cameras && this._cameras.length > 1
|
|
||||||
? html`<mwc-select
|
|
||||||
.label=${this.localize(
|
|
||||||
"ui.panel.config.zwave_js.add_node.select_camera"
|
|
||||||
)}
|
|
||||||
fixedMenuPosition
|
|
||||||
naturalMenuWidth
|
|
||||||
@closed=${stopPropagation}
|
|
||||||
@selected=${this._cameraChanged}
|
|
||||||
>
|
|
||||||
${this._cameras!.map(
|
|
||||||
(camera) => html`
|
|
||||||
<mwc-list-item .value=${camera.id}>${camera.label}</mwc-list-item>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
</mwc-select>`
|
|
||||||
: ""}
|
|
||||||
${this._error
|
|
||||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
|
||||||
: ""}
|
|
||||||
${navigator.mediaDevices
|
|
||||||
? html`<video></video>
|
|
||||||
<div id="canvas-container"></div>`
|
|
||||||
: html`<ha-alert alert-type="warning"
|
|
||||||
>${!window.isSecureContext
|
|
||||||
? "You can only use your camera to scan a QR core when using HTTPS."
|
|
||||||
: "Your browser doesn't support QR scanning."}</ha-alert
|
|
||||||
>`}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _loadQrScanner() {
|
|
||||||
const QrScanner = (await import("qr-scanner")).default;
|
|
||||||
if (!(await QrScanner.hasCamera())) {
|
|
||||||
this._error = "No camera found";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
QrScanner.WORKER_PATH = "/static/js/qr-scanner-worker.min.js";
|
|
||||||
this._listCameras(QrScanner);
|
|
||||||
this._qrScanner = new QrScanner(
|
|
||||||
this._video,
|
|
||||||
this._qrCodeScanned,
|
|
||||||
this._qrCodeError
|
|
||||||
);
|
|
||||||
// @ts-ignore
|
|
||||||
const canvas = this._qrScanner.$canvas;
|
|
||||||
this._canvasContainer.appendChild(canvas);
|
|
||||||
canvas.style.display = "block";
|
|
||||||
try {
|
|
||||||
await this._qrScanner.start();
|
|
||||||
} catch (err: any) {
|
|
||||||
this._error = err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _listCameras(qrScanner: typeof QrScanner): Promise<void> {
|
|
||||||
this._cameras = await qrScanner.listCameras(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _qrCodeError = (err: any) => {
|
|
||||||
if (err === "No QR code found") {
|
|
||||||
this._qrNotFoundCount++;
|
|
||||||
if (this._qrNotFoundCount === 250) {
|
|
||||||
this._error = err;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._error = err.message || err;
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log(err);
|
|
||||||
};
|
|
||||||
|
|
||||||
private _qrCodeScanned = async (qrCodeString: string): Promise<void> => {
|
|
||||||
this._qrNotFoundCount = 0;
|
|
||||||
fireEvent(this, "qr-code-scanned", { value: qrCodeString });
|
|
||||||
};
|
|
||||||
|
|
||||||
private _cameraChanged(ev: CustomEvent): void {
|
|
||||||
this._qrScanner?.setCamera((ev.target as Select).value);
|
|
||||||
}
|
|
||||||
|
|
||||||
static styles = css`
|
|
||||||
canvas {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
mwc-select {
|
|
||||||
width: 100%;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
// for fire event
|
|
||||||
interface HASSDomEvents {
|
|
||||||
"qr-code-scanned": { value: string };
|
|
||||||
"qr-code-error": { message: string };
|
|
||||||
}
|
|
||||||
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"ha-qr-scanner": HaQrScanner;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -3,6 +3,7 @@ import {
|
|||||||
mdiBell,
|
mdiBell,
|
||||||
mdiCalendar,
|
mdiCalendar,
|
||||||
mdiCart,
|
mdiCart,
|
||||||
|
mdiCellphoneCog,
|
||||||
mdiChartBox,
|
mdiChartBox,
|
||||||
mdiClose,
|
mdiClose,
|
||||||
mdiCog,
|
mdiCog,
|
||||||
@@ -44,6 +45,10 @@ import {
|
|||||||
PersistentNotification,
|
PersistentNotification,
|
||||||
subscribeNotifications,
|
subscribeNotifications,
|
||||||
} from "../data/persistent_notification";
|
} from "../data/persistent_notification";
|
||||||
|
import {
|
||||||
|
ExternalConfig,
|
||||||
|
getExternalConfig,
|
||||||
|
} from "../external_app/external_config";
|
||||||
import { actionHandler } from "../panels/lovelace/common/directives/action-handler-directive";
|
import { actionHandler } from "../panels/lovelace/common/directives/action-handler-directive";
|
||||||
import { haStyleScrollbar } from "../resources/styles";
|
import { haStyleScrollbar } from "../resources/styles";
|
||||||
import type { HomeAssistant, PanelInfo, Route } from "../types";
|
import type { HomeAssistant, PanelInfo, Route } from "../types";
|
||||||
@@ -190,6 +195,8 @@ class HaSidebar extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public editMode = false;
|
@property({ type: Boolean }) public editMode = false;
|
||||||
|
|
||||||
|
@state() private _externalConfig?: ExternalConfig;
|
||||||
|
|
||||||
@state() private _notifications?: PersistentNotification[];
|
@state() private _notifications?: PersistentNotification[];
|
||||||
|
|
||||||
@state() private _renderEmptySortable = false;
|
@state() private _renderEmptySortable = false;
|
||||||
@@ -236,6 +243,7 @@ class HaSidebar extends LitElement {
|
|||||||
changedProps.has("expanded") ||
|
changedProps.has("expanded") ||
|
||||||
changedProps.has("narrow") ||
|
changedProps.has("narrow") ||
|
||||||
changedProps.has("alwaysExpand") ||
|
changedProps.has("alwaysExpand") ||
|
||||||
|
changedProps.has("_externalConfig") ||
|
||||||
changedProps.has("_notifications") ||
|
changedProps.has("_notifications") ||
|
||||||
changedProps.has("editMode") ||
|
changedProps.has("editMode") ||
|
||||||
changedProps.has("_renderEmptySortable") ||
|
changedProps.has("_renderEmptySortable") ||
|
||||||
@@ -266,6 +274,11 @@ class HaSidebar extends LitElement {
|
|||||||
protected firstUpdated(changedProps: PropertyValues) {
|
protected firstUpdated(changedProps: PropertyValues) {
|
||||||
super.firstUpdated(changedProps);
|
super.firstUpdated(changedProps);
|
||||||
|
|
||||||
|
if (this.hass && this.hass.auth.external) {
|
||||||
|
getExternalConfig(this.hass.auth.external).then((conf) => {
|
||||||
|
this._externalConfig = conf;
|
||||||
|
});
|
||||||
|
}
|
||||||
subscribeNotifications(this.hass.connection, (notifications) => {
|
subscribeNotifications(this.hass.connection, (notifications) => {
|
||||||
this._notifications = notifications;
|
this._notifications = notifications;
|
||||||
});
|
});
|
||||||
@@ -363,6 +376,7 @@ class HaSidebar extends LitElement {
|
|||||||
: this._renderPanels(beforeSpacer)}
|
: this._renderPanels(beforeSpacer)}
|
||||||
${this._renderSpacer()}
|
${this._renderSpacer()}
|
||||||
${this._renderPanels(afterSpacer)}
|
${this._renderPanels(afterSpacer)}
|
||||||
|
${this._renderExternalConfiguration()}
|
||||||
</paper-listbox>
|
</paper-listbox>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -370,9 +384,7 @@ class HaSidebar extends LitElement {
|
|||||||
private _renderPanels(panels: PanelInfo[]) {
|
private _renderPanels(panels: PanelInfo[]) {
|
||||||
return panels.map((panel) =>
|
return panels.map((panel) =>
|
||||||
this._renderPanel(
|
this._renderPanel(
|
||||||
panel.url_path === "hassio"
|
panel.url_path,
|
||||||
? "config/dashboard?focusedPath=hassio"
|
|
||||||
: panel.url_path,
|
|
||||||
panel.url_path === this.hass.defaultPanel
|
panel.url_path === this.hass.defaultPanel
|
||||||
? panel.title || this.hass.localize("panel.states")
|
? panel.title || this.hass.localize("panel.states")
|
||||||
: this.hass.localize(`panel.${panel.title}`) || panel.title,
|
: this.hass.localize(`panel.${panel.title}`) || panel.title,
|
||||||
@@ -549,6 +561,34 @@ class HaSidebar extends LitElement {
|
|||||||
</a>`;
|
</a>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _renderExternalConfiguration() {
|
||||||
|
return html`${this._externalConfig && this._externalConfig.hasSettingsScreen
|
||||||
|
? html`
|
||||||
|
<a
|
||||||
|
aria-role="option"
|
||||||
|
aria-label=${this.hass.localize(
|
||||||
|
"ui.sidebar.external_app_configuration"
|
||||||
|
)}
|
||||||
|
href="#external-app-configuration"
|
||||||
|
tabindex="-1"
|
||||||
|
@click=${this._handleExternalAppConfiguration}
|
||||||
|
@mouseenter=${this._itemMouseEnter}
|
||||||
|
@mouseleave=${this._itemMouseLeave}
|
||||||
|
>
|
||||||
|
<paper-icon-item>
|
||||||
|
<ha-svg-icon
|
||||||
|
slot="item-icon"
|
||||||
|
.path=${mdiCellphoneCog}
|
||||||
|
></ha-svg-icon>
|
||||||
|
<span class="item-text">
|
||||||
|
${this.hass.localize("ui.sidebar.external_app_configuration")}
|
||||||
|
</span>
|
||||||
|
</paper-icon-item>
|
||||||
|
</a>
|
||||||
|
`
|
||||||
|
: ""}`;
|
||||||
|
}
|
||||||
|
|
||||||
private get _tooltip() {
|
private get _tooltip() {
|
||||||
return this.shadowRoot!.querySelector(".tooltip")! as HTMLDivElement;
|
return this.shadowRoot!.querySelector(".tooltip")! as HTMLDivElement;
|
||||||
}
|
}
|
||||||
@@ -720,6 +760,13 @@ class HaSidebar extends LitElement {
|
|||||||
fireEvent(this, "hass-show-notifications");
|
fireEvent(this, "hass-show-notifications");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _handleExternalAppConfiguration(ev: Event) {
|
||||||
|
ev.preventDefault();
|
||||||
|
this.hass.auth.external!.fireMessage({
|
||||||
|
type: "config_screen/show",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private _toggleSidebar(ev: CustomEvent) {
|
private _toggleSidebar(ev: CustomEvent) {
|
||||||
if (ev.detail.action !== "tap") {
|
if (ev.detail.action !== "tap") {
|
||||||
return;
|
return;
|
||||||
|
@@ -27,7 +27,7 @@ export class HaTimeInput extends LitElement {
|
|||||||
const parts = this.value?.split(":") || [];
|
const parts = this.value?.split(":") || [];
|
||||||
let hours = parts[0];
|
let hours = parts[0];
|
||||||
const numberHours = Number(parts[0]);
|
const numberHours = Number(parts[0]);
|
||||||
if (numberHours && useAMPM && numberHours > 12 && numberHours < 24) {
|
if (numberHours && useAMPM && numberHours > 12) {
|
||||||
hours = String(numberHours - 12).padStart(2, "0");
|
hours = String(numberHours - 12).padStart(2, "0");
|
||||||
}
|
}
|
||||||
if (useAMPM && numberHours === 0) {
|
if (useAMPM && numberHours === 0) {
|
||||||
|
@@ -179,7 +179,7 @@ export interface StateCondition extends BaseCondition {
|
|||||||
condition: "state";
|
condition: "state";
|
||||||
entity_id: string;
|
entity_id: string;
|
||||||
attribute?: string;
|
attribute?: string;
|
||||||
state: string | number | string[];
|
state: string | number;
|
||||||
for?: string | number | ForDict;
|
for?: string | number | ForDict;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,95 +0,0 @@
|
|||||||
import {
|
|
||||||
HassEntityAttributeBase,
|
|
||||||
HassEntityBase,
|
|
||||||
} from "home-assistant-js-websocket";
|
|
||||||
import { supportsFeature } from "../common/entity/supports-feature";
|
|
||||||
|
|
||||||
export const SUPPORT_OPEN = 1;
|
|
||||||
export const SUPPORT_CLOSE = 2;
|
|
||||||
export const SUPPORT_SET_POSITION = 4;
|
|
||||||
export const SUPPORT_STOP = 8;
|
|
||||||
export const SUPPORT_OPEN_TILT = 16;
|
|
||||||
export const SUPPORT_CLOSE_TILT = 32;
|
|
||||||
export const SUPPORT_STOP_TILT = 64;
|
|
||||||
export const SUPPORT_SET_TILT_POSITION = 128;
|
|
||||||
|
|
||||||
export const FEATURE_CLASS_NAMES = {
|
|
||||||
4: "has-set_position",
|
|
||||||
16: "has-open_tilt",
|
|
||||||
32: "has-close_tilt",
|
|
||||||
64: "has-stop_tilt",
|
|
||||||
128: "has-set_tilt_position",
|
|
||||||
};
|
|
||||||
|
|
||||||
export const supportsOpen = (stateObj) =>
|
|
||||||
supportsFeature(stateObj, SUPPORT_OPEN);
|
|
||||||
|
|
||||||
export const supportsClose = (stateObj) =>
|
|
||||||
supportsFeature(stateObj, SUPPORT_CLOSE);
|
|
||||||
|
|
||||||
export const supportsSetPosition = (stateObj) =>
|
|
||||||
supportsFeature(stateObj, SUPPORT_SET_POSITION);
|
|
||||||
|
|
||||||
export const supportsStop = (stateObj) =>
|
|
||||||
supportsFeature(stateObj, SUPPORT_STOP);
|
|
||||||
|
|
||||||
export const supportsOpenTilt = (stateObj) =>
|
|
||||||
supportsFeature(stateObj, SUPPORT_OPEN_TILT);
|
|
||||||
|
|
||||||
export const supportsCloseTilt = (stateObj) =>
|
|
||||||
supportsFeature(stateObj, SUPPORT_CLOSE_TILT);
|
|
||||||
|
|
||||||
export const supportsStopTilt = (stateObj) =>
|
|
||||||
supportsFeature(stateObj, SUPPORT_STOP_TILT);
|
|
||||||
|
|
||||||
export const supportsSetTiltPosition = (stateObj) =>
|
|
||||||
supportsFeature(stateObj, SUPPORT_SET_TILT_POSITION);
|
|
||||||
|
|
||||||
export function isFullyOpen(stateObj: CoverEntity) {
|
|
||||||
if (stateObj.attributes.current_position !== undefined) {
|
|
||||||
return stateObj.attributes.current_position === 100;
|
|
||||||
}
|
|
||||||
return stateObj.state === "open";
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isFullyClosed(stateObj: CoverEntity) {
|
|
||||||
if (stateObj.attributes.current_position !== undefined) {
|
|
||||||
return stateObj.attributes.current_position === 0;
|
|
||||||
}
|
|
||||||
return stateObj.state === "closed";
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isFullyOpenTilt(stateObj: CoverEntity) {
|
|
||||||
return stateObj.attributes.current_tilt_position === 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isFullyClosedTilt(stateObj: CoverEntity) {
|
|
||||||
return stateObj.attributes.current_tilt_position === 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isOpening(stateObj: CoverEntity) {
|
|
||||||
return stateObj.state === "opening";
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isClosing(stateObj: CoverEntity) {
|
|
||||||
return stateObj.state === "closing";
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isTiltOnly(stateObj: CoverEntity) {
|
|
||||||
const supportsCover =
|
|
||||||
supportsOpen(stateObj) || supportsClose(stateObj) || supportsStop(stateObj);
|
|
||||||
const supportsTilt =
|
|
||||||
supportsOpenTilt(stateObj) ||
|
|
||||||
supportsCloseTilt(stateObj) ||
|
|
||||||
supportsStopTilt(stateObj);
|
|
||||||
return supportsTilt && !supportsCover;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CoverEntityAttributes extends HassEntityAttributeBase {
|
|
||||||
current_position: number;
|
|
||||||
current_tilt_position: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CoverEntity extends HassEntityBase {
|
|
||||||
attributes: CoverEntityAttributes;
|
|
||||||
}
|
|
@@ -1,6 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
addHours,
|
addHours,
|
||||||
differenceInDays,
|
|
||||||
endOfToday,
|
endOfToday,
|
||||||
endOfYesterday,
|
endOfYesterday,
|
||||||
startOfToday,
|
startOfToday,
|
||||||
@@ -192,27 +191,6 @@ export const saveEnergyPreferences = async (
|
|||||||
return newPrefs;
|
return newPrefs;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface FossilEnergyConsumption {
|
|
||||||
[date: string]: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getFossilEnergyConsumption = async (
|
|
||||||
hass: HomeAssistant,
|
|
||||||
startTime: Date,
|
|
||||||
energy_statistic_ids: string[],
|
|
||||||
co2_statistic_id: string,
|
|
||||||
endTime?: Date,
|
|
||||||
period: "5minute" | "hour" | "day" | "month" = "hour"
|
|
||||||
) =>
|
|
||||||
hass.callWS<FossilEnergyConsumption>({
|
|
||||||
type: "energy/fossil_energy_consumption",
|
|
||||||
start_time: startTime.toISOString(),
|
|
||||||
end_time: endTime?.toISOString(),
|
|
||||||
energy_statistic_ids,
|
|
||||||
co2_statistic_id,
|
|
||||||
period,
|
|
||||||
});
|
|
||||||
|
|
||||||
interface EnergySourceByType {
|
interface EnergySourceByType {
|
||||||
grid?: GridSourceTypeEnergyPreference[];
|
grid?: GridSourceTypeEnergyPreference[];
|
||||||
solar?: SolarSourceTypeEnergyPreference[];
|
solar?: SolarSourceTypeEnergyPreference[];
|
||||||
@@ -231,7 +209,6 @@ export interface EnergyData {
|
|||||||
stats: Statistics;
|
stats: Statistics;
|
||||||
co2SignalConfigEntry?: ConfigEntry;
|
co2SignalConfigEntry?: ConfigEntry;
|
||||||
co2SignalEntity?: string;
|
co2SignalEntity?: string;
|
||||||
fossilEnergyConsumption?: FossilEnergyConsumption;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getEnergyData = async (
|
const getEnergyData = async (
|
||||||
@@ -269,9 +246,12 @@ const getEnergyData = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const consumptionStatIDs: string[] = [];
|
|
||||||
const statIDs: string[] = [];
|
const statIDs: string[] = [];
|
||||||
|
|
||||||
|
if (co2SignalEntity !== undefined) {
|
||||||
|
statIDs.push(co2SignalEntity);
|
||||||
|
}
|
||||||
|
|
||||||
for (const source of prefs.energy_sources) {
|
for (const source of prefs.energy_sources) {
|
||||||
if (source.type === "solar") {
|
if (source.type === "solar") {
|
||||||
statIDs.push(source.stat_energy_from);
|
statIDs.push(source.stat_energy_from);
|
||||||
@@ -298,7 +278,6 @@ const getEnergyData = async (
|
|||||||
|
|
||||||
// grid source
|
// grid source
|
||||||
for (const flowFrom of source.flow_from) {
|
for (const flowFrom of source.flow_from) {
|
||||||
consumptionStatIDs.push(flowFrom.stat_energy_from);
|
|
||||||
statIDs.push(flowFrom.stat_energy_from);
|
statIDs.push(flowFrom.stat_energy_from);
|
||||||
if (flowFrom.stat_cost) {
|
if (flowFrom.stat_cost) {
|
||||||
statIDs.push(flowFrom.stat_cost);
|
statIDs.push(flowFrom.stat_cost);
|
||||||
@@ -320,44 +299,7 @@ const getEnergyData = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const dayDifference = differenceInDays(end || new Date(), start);
|
const stats = await fetchStatistics(hass!, addHours(start, -1), end, statIDs); // Subtract 1 hour from start to get starting point data
|
||||||
|
|
||||||
// Subtract 1 hour from start to get starting point data
|
|
||||||
const startMinHour = addHours(start, -1);
|
|
||||||
|
|
||||||
const stats = await fetchStatistics(
|
|
||||||
hass!,
|
|
||||||
startMinHour,
|
|
||||||
end,
|
|
||||||
statIDs,
|
|
||||||
dayDifference > 35 ? "month" : dayDifference > 2 ? "day" : "hour"
|
|
||||||
);
|
|
||||||
|
|
||||||
let fossilEnergyConsumption: FossilEnergyConsumption | undefined;
|
|
||||||
|
|
||||||
if (co2SignalEntity !== undefined) {
|
|
||||||
fossilEnergyConsumption = await getFossilEnergyConsumption(
|
|
||||||
hass!,
|
|
||||||
start,
|
|
||||||
consumptionStatIDs,
|
|
||||||
co2SignalEntity,
|
|
||||||
end,
|
|
||||||
dayDifference > 35 ? "month" : dayDifference > 2 ? "day" : "hour"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.values(stats).forEach((stat) => {
|
|
||||||
// if the start of the first value is after the requested period, we have the first data point, and should add a zero point
|
|
||||||
if (stat.length && new Date(stat[0].start) > startMinHour) {
|
|
||||||
stat.unshift({
|
|
||||||
...stat[0],
|
|
||||||
start: startMinHour.toISOString(),
|
|
||||||
end: startMinHour.toISOString(),
|
|
||||||
sum: 0,
|
|
||||||
state: 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
start,
|
start,
|
||||||
@@ -367,7 +309,6 @@ const getEnergyData = async (
|
|||||||
stats,
|
stats,
|
||||||
co2SignalConfigEntry,
|
co2SignalConfigEntry,
|
||||||
co2SignalEntity,
|
co2SignalEntity,
|
||||||
fossilEnergyConsumption,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import { addDays, addMonths, startOfDay, startOfMonth } from "date-fns";
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { computeStateDisplay } from "../common/entity/compute_state_display";
|
import { computeStateDisplay } from "../common/entity/compute_state_display";
|
||||||
import { computeStateDomain } from "../common/entity/compute_state_domain";
|
import { computeStateDomain } from "../common/entity/compute_state_domain";
|
||||||
@@ -62,7 +63,6 @@ export interface Statistics {
|
|||||||
export interface StatisticValue {
|
export interface StatisticValue {
|
||||||
statistic_id: string;
|
statistic_id: string;
|
||||||
start: string;
|
start: string;
|
||||||
end: string;
|
|
||||||
last_reset: string | null;
|
last_reset: string | null;
|
||||||
max: number | null;
|
max: number | null;
|
||||||
mean: number | null;
|
mean: number | null;
|
||||||
@@ -350,7 +350,7 @@ export const fetchStatistics = (
|
|||||||
startTime: Date,
|
startTime: Date,
|
||||||
endTime?: Date,
|
endTime?: Date,
|
||||||
statistic_ids?: string[],
|
statistic_ids?: string[],
|
||||||
period: "5minute" | "hour" | "day" | "month" = "hour"
|
period: "hour" | "5minute" = "hour"
|
||||||
) =>
|
) =>
|
||||||
hass.callWS<Statistics>({
|
hass.callWS<Statistics>({
|
||||||
type: "history/statistics_during_period",
|
type: "history/statistics_during_period",
|
||||||
@@ -428,3 +428,151 @@ export const statisticsHaveType = (
|
|||||||
stats: StatisticValue[],
|
stats: StatisticValue[],
|
||||||
type: StatisticType
|
type: StatisticType
|
||||||
) => stats.some((stat) => stat[type] !== null);
|
) => stats.some((stat) => stat[type] !== null);
|
||||||
|
|
||||||
|
// Merge the growth of multiple sum statistics into one
|
||||||
|
const mergeSumGrowthStatistics = (stats: StatisticValue[][]) => {
|
||||||
|
const result = {};
|
||||||
|
|
||||||
|
stats.forEach((stat) => {
|
||||||
|
if (stat.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let prevSum: number | null = null;
|
||||||
|
stat.forEach((statVal) => {
|
||||||
|
if (statVal.sum === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (prevSum === null) {
|
||||||
|
prevSum = statVal.sum;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const growth = statVal.sum - prevSum;
|
||||||
|
if (statVal.start in result) {
|
||||||
|
result[statVal.start] += growth;
|
||||||
|
} else {
|
||||||
|
result[statVal.start] = growth;
|
||||||
|
}
|
||||||
|
prevSum = statVal.sum;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the growth of a statistic over the given period while applying a
|
||||||
|
* per-period percentage.
|
||||||
|
*/
|
||||||
|
export const calculateStatisticsSumGrowthWithPercentage = (
|
||||||
|
percentageStat: StatisticValue[],
|
||||||
|
sumStats: StatisticValue[][]
|
||||||
|
): number | null => {
|
||||||
|
let sum: number | null = null;
|
||||||
|
|
||||||
|
if (sumStats.length === 0 || percentageStat.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sumGrowthToProcess = mergeSumGrowthStatistics(sumStats);
|
||||||
|
|
||||||
|
percentageStat.forEach((percentageStatValue) => {
|
||||||
|
const sumGrowth = sumGrowthToProcess[percentageStatValue.start];
|
||||||
|
if (sumGrowth === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (sum === null) {
|
||||||
|
sum = sumGrowth * (percentageStatValue.mean! / 100);
|
||||||
|
} else {
|
||||||
|
sum += sumGrowth * (percentageStatValue.mean! / 100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const reduceSumStatisticsByDay = (
|
||||||
|
values: StatisticValue[]
|
||||||
|
): StatisticValue[] => {
|
||||||
|
if (!values?.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const result: StatisticValue[] = [];
|
||||||
|
if (
|
||||||
|
values.length > 1 &&
|
||||||
|
new Date(values[0].start).getDate() === new Date(values[1].start).getDate()
|
||||||
|
) {
|
||||||
|
// add init value if the first value isn't end of previous period
|
||||||
|
result.push({
|
||||||
|
...values[0]!,
|
||||||
|
start: startOfDay(addDays(new Date(values[0].start), -1)).toISOString(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let lastValue: StatisticValue;
|
||||||
|
let prevDate: number | undefined;
|
||||||
|
for (const value of values) {
|
||||||
|
const date = new Date(value.start).getDate();
|
||||||
|
if (prevDate === undefined) {
|
||||||
|
prevDate = date;
|
||||||
|
}
|
||||||
|
if (prevDate !== date) {
|
||||||
|
// Last value of the day
|
||||||
|
result.push({
|
||||||
|
...lastValue!,
|
||||||
|
start: startOfDay(new Date(lastValue!.start)).toISOString(),
|
||||||
|
});
|
||||||
|
prevDate = date;
|
||||||
|
}
|
||||||
|
lastValue = value;
|
||||||
|
}
|
||||||
|
// Add final value
|
||||||
|
result.push({
|
||||||
|
...lastValue!,
|
||||||
|
start: startOfDay(new Date(lastValue!.start)).toISOString(),
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const reduceSumStatisticsByMonth = (
|
||||||
|
values: StatisticValue[]
|
||||||
|
): StatisticValue[] => {
|
||||||
|
if (!values?.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const result: StatisticValue[] = [];
|
||||||
|
if (
|
||||||
|
values.length > 1 &&
|
||||||
|
new Date(values[0].start).getMonth() ===
|
||||||
|
new Date(values[1].start).getMonth()
|
||||||
|
) {
|
||||||
|
// add init value if the first value isn't end of previous period
|
||||||
|
result.push({
|
||||||
|
...values[0]!,
|
||||||
|
start: startOfMonth(
|
||||||
|
addMonths(new Date(values[0].start), -1)
|
||||||
|
).toISOString(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let lastValue: StatisticValue;
|
||||||
|
let prevMonth: number | undefined;
|
||||||
|
for (const value of values) {
|
||||||
|
const month = new Date(value.start).getMonth();
|
||||||
|
if (prevMonth === undefined) {
|
||||||
|
prevMonth = month;
|
||||||
|
}
|
||||||
|
if (prevMonth !== month) {
|
||||||
|
// Last value of the month
|
||||||
|
result.push({
|
||||||
|
...lastValue!,
|
||||||
|
start: startOfMonth(new Date(lastValue!.start)).toISOString(),
|
||||||
|
});
|
||||||
|
prevMonth = month;
|
||||||
|
}
|
||||||
|
lastValue = value;
|
||||||
|
}
|
||||||
|
// Add final value
|
||||||
|
result.push({
|
||||||
|
...lastValue!,
|
||||||
|
start: startOfMonth(new Date(lastValue!.start)).toISOString(),
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
@@ -18,15 +18,10 @@ export const SCENE_IGNORED_DOMAINS = [
|
|||||||
"zone",
|
"zone",
|
||||||
];
|
];
|
||||||
|
|
||||||
let inititialSceneEditorData:
|
let inititialSceneEditorData: Partial<SceneConfig> | undefined;
|
||||||
| { config?: Partial<SceneConfig>; areaId?: string }
|
|
||||||
| undefined;
|
|
||||||
|
|
||||||
export const showSceneEditor = (
|
export const showSceneEditor = (data?: Partial<SceneConfig>) => {
|
||||||
config?: Partial<SceneConfig>,
|
inititialSceneEditorData = data;
|
||||||
areaId?: string
|
|
||||||
) => {
|
|
||||||
inititialSceneEditorData = { config, areaId };
|
|
||||||
navigate("/config/scene/edit/new");
|
navigate("/config/scene/edit/new");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -57,45 +57,6 @@ export enum SecurityClass {
|
|||||||
S0_Legacy = 7,
|
S0_Legacy = 7,
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A named list of Z-Wave features */
|
|
||||||
export enum ZWaveFeature {
|
|
||||||
// Available starting with Z-Wave SDK 6.81
|
|
||||||
SmartStart,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum QRCodeVersion {
|
|
||||||
S2 = 0,
|
|
||||||
SmartStart = 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Protocols {
|
|
||||||
ZWave = 0,
|
|
||||||
ZWaveLongRange = 1,
|
|
||||||
}
|
|
||||||
export interface QRProvisioningInformation {
|
|
||||||
version: QRCodeVersion;
|
|
||||||
securityClasses: SecurityClass[];
|
|
||||||
dsk: string;
|
|
||||||
genericDeviceClass: number;
|
|
||||||
specificDeviceClass: number;
|
|
||||||
installerIconType: number;
|
|
||||||
manufacturerId: number;
|
|
||||||
productType: number;
|
|
||||||
productId: number;
|
|
||||||
applicationVersion: string;
|
|
||||||
maxInclusionRequestInterval?: number | undefined;
|
|
||||||
uuid?: string | undefined;
|
|
||||||
supportedProtocols?: Protocols[] | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PlannedProvisioningEntry {
|
|
||||||
/** The device specific key (DSK) in the form aaaaa-bbbbb-ccccc-ddddd-eeeee-fffff-11111-22222 */
|
|
||||||
dsk: string;
|
|
||||||
security_classes: SecurityClass[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const MINIMUM_QR_STRING_LENGTH = 52;
|
|
||||||
|
|
||||||
export interface ZWaveJSNodeIdentifiers {
|
export interface ZWaveJSNodeIdentifiers {
|
||||||
home_id: string;
|
home_id: string;
|
||||||
node_id: number;
|
node_id: number;
|
||||||
@@ -236,7 +197,7 @@ export const migrateZwave = (
|
|||||||
dry_run,
|
dry_run,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const fetchZwaveNetworkStatus = (
|
export const fetchNetworkStatus = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry_id: string
|
entry_id: string
|
||||||
): Promise<ZWaveJSNetwork> =>
|
): Promise<ZWaveJSNetwork> =>
|
||||||
@@ -245,7 +206,7 @@ export const fetchZwaveNetworkStatus = (
|
|||||||
entry_id,
|
entry_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const fetchZwaveDataCollectionStatus = (
|
export const fetchDataCollectionStatus = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry_id: string
|
entry_id: string
|
||||||
): Promise<ZWaveJSDataCollectionStatus> =>
|
): Promise<ZWaveJSDataCollectionStatus> =>
|
||||||
@@ -254,7 +215,7 @@ export const fetchZwaveDataCollectionStatus = (
|
|||||||
entry_id,
|
entry_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setZwaveDataCollectionPreference = (
|
export const setDataCollectionPreference = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry_id: string,
|
entry_id: string,
|
||||||
opted_in: boolean
|
opted_in: boolean
|
||||||
@@ -265,31 +226,25 @@ export const setZwaveDataCollectionPreference = (
|
|||||||
opted_in,
|
opted_in,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const subscribeAddZwaveNode = (
|
export const subscribeAddNode = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry_id: string,
|
entry_id: string,
|
||||||
callbackFunction: (message: any) => void,
|
callbackFunction: (message: any) => void,
|
||||||
inclusion_strategy: InclusionStrategy = InclusionStrategy.Default,
|
inclusion_strategy: InclusionStrategy = InclusionStrategy.Default
|
||||||
qr_provisioning_information?: QRProvisioningInformation,
|
|
||||||
qr_code_string?: string,
|
|
||||||
planned_provisioning_entry?: PlannedProvisioningEntry
|
|
||||||
): Promise<UnsubscribeFunc> =>
|
): Promise<UnsubscribeFunc> =>
|
||||||
hass.connection.subscribeMessage((message) => callbackFunction(message), {
|
hass.connection.subscribeMessage((message) => callbackFunction(message), {
|
||||||
type: "zwave_js/add_node",
|
type: "zwave_js/add_node",
|
||||||
entry_id: entry_id,
|
entry_id: entry_id,
|
||||||
inclusion_strategy,
|
inclusion_strategy,
|
||||||
qr_code_string,
|
|
||||||
qr_provisioning_information,
|
|
||||||
planned_provisioning_entry,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const stopZwaveInclusion = (hass: HomeAssistant, entry_id: string) =>
|
export const stopInclusion = (hass: HomeAssistant, entry_id: string) =>
|
||||||
hass.callWS({
|
hass.callWS({
|
||||||
type: "zwave_js/stop_inclusion",
|
type: "zwave_js/stop_inclusion",
|
||||||
entry_id,
|
entry_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const zwaveGrantSecurityClasses = (
|
export const grantSecurityClasses = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry_id: string,
|
entry_id: string,
|
||||||
security_classes: SecurityClass[],
|
security_classes: SecurityClass[],
|
||||||
@@ -302,7 +257,7 @@ export const zwaveGrantSecurityClasses = (
|
|||||||
client_side_auth,
|
client_side_auth,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const zwaveValidateDskAndEnterPin = (
|
export const validateDskAndEnterPin = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry_id: string,
|
entry_id: string,
|
||||||
pin: string
|
pin: string
|
||||||
@@ -313,44 +268,7 @@ export const zwaveValidateDskAndEnterPin = (
|
|||||||
pin,
|
pin,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const zwaveSupportsFeature = (
|
export const fetchNodeStatus = (
|
||||||
hass: HomeAssistant,
|
|
||||||
entry_id: string,
|
|
||||||
feature: ZWaveFeature
|
|
||||||
): Promise<{ supported: boolean }> =>
|
|
||||||
hass.callWS({
|
|
||||||
type: "zwave_js/supports_feature",
|
|
||||||
entry_id,
|
|
||||||
feature,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const zwaveParseQrCode = (
|
|
||||||
hass: HomeAssistant,
|
|
||||||
entry_id: string,
|
|
||||||
qr_code_string: string
|
|
||||||
): Promise<QRProvisioningInformation> =>
|
|
||||||
hass.callWS({
|
|
||||||
type: "zwave_js/parse_qr_code_string",
|
|
||||||
entry_id,
|
|
||||||
qr_code_string,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const provisionZwaveSmartStartNode = (
|
|
||||||
hass: HomeAssistant,
|
|
||||||
entry_id: string,
|
|
||||||
qr_provisioning_information?: QRProvisioningInformation,
|
|
||||||
qr_code_string?: string,
|
|
||||||
planned_provisioning_entry?: PlannedProvisioningEntry
|
|
||||||
): Promise<QRProvisioningInformation> =>
|
|
||||||
hass.callWS({
|
|
||||||
type: "zwave_js/provision_smart_start_node",
|
|
||||||
entry_id,
|
|
||||||
qr_code_string,
|
|
||||||
qr_provisioning_information,
|
|
||||||
planned_provisioning_entry,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const fetchZwaveNodeStatus = (
|
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry_id: string,
|
entry_id: string,
|
||||||
node_id: number
|
node_id: number
|
||||||
@@ -361,7 +279,7 @@ export const fetchZwaveNodeStatus = (
|
|||||||
node_id,
|
node_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const fetchZwaveNodeMetadata = (
|
export const fetchNodeMetadata = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry_id: string,
|
entry_id: string,
|
||||||
node_id: number
|
node_id: number
|
||||||
@@ -372,7 +290,7 @@ export const fetchZwaveNodeMetadata = (
|
|||||||
node_id,
|
node_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const fetchZwaveNodeConfigParameters = (
|
export const fetchNodeConfigParameters = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry_id: string,
|
entry_id: string,
|
||||||
node_id: number
|
node_id: number
|
||||||
@@ -383,7 +301,7 @@ export const fetchZwaveNodeConfigParameters = (
|
|||||||
node_id,
|
node_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setZwaveNodeConfigParameter = (
|
export const setNodeConfigParameter = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry_id: string,
|
entry_id: string,
|
||||||
node_id: number,
|
node_id: number,
|
||||||
@@ -402,7 +320,7 @@ export const setZwaveNodeConfigParameter = (
|
|||||||
return hass.callWS(data);
|
return hass.callWS(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const reinterviewZwaveNode = (
|
export const reinterviewNode = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry_id: string,
|
entry_id: string,
|
||||||
node_id: number,
|
node_id: number,
|
||||||
@@ -417,7 +335,7 @@ export const reinterviewZwaveNode = (
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export const healZwaveNode = (
|
export const healNode = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry_id: string,
|
entry_id: string,
|
||||||
node_id: number
|
node_id: number
|
||||||
@@ -428,7 +346,7 @@ export const healZwaveNode = (
|
|||||||
node_id,
|
node_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const removeFailedZwaveNode = (
|
export const removeFailedNode = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry_id: string,
|
entry_id: string,
|
||||||
node_id: number,
|
node_id: number,
|
||||||
@@ -443,7 +361,7 @@ export const removeFailedZwaveNode = (
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export const healZwaveNetwork = (
|
export const healNetwork = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry_id: string
|
entry_id: string
|
||||||
): Promise<UnsubscribeFunc> =>
|
): Promise<UnsubscribeFunc> =>
|
||||||
@@ -452,7 +370,7 @@ export const healZwaveNetwork = (
|
|||||||
entry_id,
|
entry_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const stopHealZwaveNetwork = (
|
export const stopHealNetwork = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry_id: string
|
entry_id: string
|
||||||
): Promise<UnsubscribeFunc> =>
|
): Promise<UnsubscribeFunc> =>
|
||||||
@@ -461,7 +379,7 @@ export const stopHealZwaveNetwork = (
|
|||||||
entry_id,
|
entry_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const subscribeZwaveNodeReady = (
|
export const subscribeNodeReady = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry_id: string,
|
entry_id: string,
|
||||||
node_id: number,
|
node_id: number,
|
||||||
@@ -476,7 +394,7 @@ export const subscribeZwaveNodeReady = (
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export const subscribeHealZwaveNetworkProgress = (
|
export const subscribeHealNetworkProgress = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry_id: string,
|
entry_id: string,
|
||||||
callbackFunction: (message: ZWaveJSHealNetworkStatusMessage) => void
|
callbackFunction: (message: ZWaveJSHealNetworkStatusMessage) => void
|
||||||
@@ -489,7 +407,7 @@ export const subscribeHealZwaveNetworkProgress = (
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getZwaveJsIdentifiersFromDevice = (
|
export const getIdentifiersFromDevice = (
|
||||||
device: DeviceRegistryEntry
|
device: DeviceRegistryEntry
|
||||||
): ZWaveJSNodeIdentifiers | undefined => {
|
): ZWaveJSNodeIdentifiers | undefined => {
|
||||||
if (!device) {
|
if (!device) {
|
||||||
|
@@ -192,7 +192,7 @@ class MoreInfoClimate extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
${supportPresetMode && stateObj.attributes.preset_modes
|
${supportPresetMode
|
||||||
? html`
|
? html`
|
||||||
<div class="container-preset_modes">
|
<div class="container-preset_modes">
|
||||||
<ha-paper-dropdown-menu
|
<ha-paper-dropdown-menu
|
||||||
@@ -220,7 +220,7 @@ class MoreInfoClimate extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
${supportFanMode && stateObj.attributes.fan_modes
|
${supportFanMode
|
||||||
? html`
|
? html`
|
||||||
<div class="container-fan_list">
|
<div class="container-fan_list">
|
||||||
<ha-paper-dropdown-menu
|
<ha-paper-dropdown-menu
|
||||||
@@ -248,7 +248,7 @@ class MoreInfoClimate extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
${supportSwingMode && stateObj.attributes.swing_modes
|
${supportSwingMode
|
||||||
? html`
|
? html`
|
||||||
<div class="container-swing_list">
|
<div class="container-swing_list">
|
||||||
<ha-paper-dropdown-menu
|
<ha-paper-dropdown-menu
|
||||||
|
124
src/dialogs/more-info/controls/more-info-cover.js
Normal file
124
src/dialogs/more-info/controls/more-info-cover.js
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
||||||
|
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||||
|
/* eslint-plugin-disable lit */
|
||||||
|
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||||
|
import { attributeClassNames } from "../../../common/entity/attribute_class_names";
|
||||||
|
import { featureClassNames } from "../../../common/entity/feature_class_names";
|
||||||
|
import "../../../components/ha-cover-tilt-controls";
|
||||||
|
import "../../../components/ha-labeled-slider";
|
||||||
|
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||||
|
import CoverEntity from "../../../util/cover-model";
|
||||||
|
|
||||||
|
const FEATURE_CLASS_NAMES = {
|
||||||
|
4: "has-set_position",
|
||||||
|
128: "has-set_tilt_position",
|
||||||
|
};
|
||||||
|
class MoreInfoCover extends LocalizeMixin(PolymerElement) {
|
||||||
|
static get template() {
|
||||||
|
return html`
|
||||||
|
<style include="iron-flex"></style>
|
||||||
|
<style>
|
||||||
|
.current_position,
|
||||||
|
.tilt {
|
||||||
|
max-height: 0px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.has-set_position .current_position,
|
||||||
|
.has-current_position .current_position,
|
||||||
|
.has-set_tilt_position .tilt,
|
||||||
|
.has-current_tilt_position .tilt {
|
||||||
|
max-height: 208px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[invisible] {
|
||||||
|
visibility: hidden !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class$="[[computeClassNames(stateObj)]]">
|
||||||
|
<div class="current_position">
|
||||||
|
<ha-labeled-slider
|
||||||
|
caption="[[localize('ui.card.cover.position')]]"
|
||||||
|
pin=""
|
||||||
|
value="{{coverPositionSliderValue}}"
|
||||||
|
disabled="[[!entityObj.supportsSetPosition]]"
|
||||||
|
on-change="coverPositionSliderChanged"
|
||||||
|
></ha-labeled-slider>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tilt">
|
||||||
|
<ha-labeled-slider
|
||||||
|
caption="[[localize('ui.card.cover.tilt_position')]]"
|
||||||
|
pin=""
|
||||||
|
extra=""
|
||||||
|
value="{{coverTiltPositionSliderValue}}"
|
||||||
|
disabled="[[!entityObj.supportsSetTiltPosition]]"
|
||||||
|
on-change="coverTiltPositionSliderChanged"
|
||||||
|
>
|
||||||
|
<ha-cover-tilt-controls
|
||||||
|
slot="extra"
|
||||||
|
hidden$="[[entityObj.isTiltOnly]]"
|
||||||
|
hass="[[hass]]"
|
||||||
|
state-obj="[[stateObj]]"
|
||||||
|
></ha-cover-tilt-controls>
|
||||||
|
</ha-labeled-slider>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ha-attributes
|
||||||
|
hass="[[hass]]"
|
||||||
|
state-obj="[[stateObj]]"
|
||||||
|
extra-filters="current_position,current_tilt_position"
|
||||||
|
></ha-attributes>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
hass: Object,
|
||||||
|
stateObj: {
|
||||||
|
type: Object,
|
||||||
|
observer: "stateObjChanged",
|
||||||
|
},
|
||||||
|
entityObj: {
|
||||||
|
type: Object,
|
||||||
|
computed: "computeEntityObj(hass, stateObj)",
|
||||||
|
},
|
||||||
|
coverPositionSliderValue: Number,
|
||||||
|
coverTiltPositionSliderValue: Number,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
computeEntityObj(hass, stateObj) {
|
||||||
|
return new CoverEntity(hass, stateObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
stateObjChanged(newVal) {
|
||||||
|
if (newVal) {
|
||||||
|
this.setProperties({
|
||||||
|
coverPositionSliderValue: newVal.attributes.current_position,
|
||||||
|
coverTiltPositionSliderValue: newVal.attributes.current_tilt_position,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
computeClassNames(stateObj) {
|
||||||
|
const classes = [
|
||||||
|
attributeClassNames(stateObj, [
|
||||||
|
"current_position",
|
||||||
|
"current_tilt_position",
|
||||||
|
]),
|
||||||
|
featureClassNames(stateObj, FEATURE_CLASS_NAMES),
|
||||||
|
];
|
||||||
|
return classes.join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
coverPositionSliderChanged(ev) {
|
||||||
|
this.entityObj.setCoverPosition(ev.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
coverTiltPositionSliderChanged(ev) {
|
||||||
|
this.entityObj.setCoverTiltPosition(ev.target.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("more-info-cover", MoreInfoCover);
|
@@ -1,140 +0,0 @@
|
|||||||
import { css, CSSResult, html, LitElement, TemplateResult } from "lit";
|
|
||||||
import { customElement, property } from "lit/decorators";
|
|
||||||
import { attributeClassNames } from "../../../common/entity/attribute_class_names";
|
|
||||||
import { featureClassNames } from "../../../common/entity/feature_class_names";
|
|
||||||
import "../../../components/ha-attributes";
|
|
||||||
import "../../../components/ha-cover-tilt-controls";
|
|
||||||
import "../../../components/ha-labeled-slider";
|
|
||||||
import {
|
|
||||||
CoverEntity,
|
|
||||||
FEATURE_CLASS_NAMES,
|
|
||||||
isTiltOnly,
|
|
||||||
supportsSetPosition,
|
|
||||||
supportsSetTiltPosition,
|
|
||||||
} from "../../../data/cover";
|
|
||||||
import { HomeAssistant } from "../../../types";
|
|
||||||
|
|
||||||
@customElement("more-info-cover")
|
|
||||||
class MoreInfoCover extends LitElement {
|
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public stateObj!: CoverEntity;
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
|
||||||
if (!this.stateObj) {
|
|
||||||
return html``;
|
|
||||||
}
|
|
||||||
|
|
||||||
const _isTiltOnly = isTiltOnly(this.stateObj);
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<div class=${this._computeClassNames(this.stateObj)}>
|
|
||||||
<div class="current_position">
|
|
||||||
<ha-labeled-slider
|
|
||||||
.caption=${this.hass.localize("ui.card.cover.position")}
|
|
||||||
pin=""
|
|
||||||
.value=${this.stateObj.attributes.current_position}
|
|
||||||
.disabled=${!supportsSetPosition(this.stateObj)}
|
|
||||||
@change=${this._coverPositionSliderChanged}
|
|
||||||
></ha-labeled-slider>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tilt">
|
|
||||||
${supportsSetTiltPosition(this.stateObj)
|
|
||||||
? // Either render the labeled slider and put the tilt buttons into its slot
|
|
||||||
// or (if tilt position is not supported and therefore no slider is shown)
|
|
||||||
// render a title <div> (same style as for a labeled slider) and directly put
|
|
||||||
// the tilt controls on the more-info.
|
|
||||||
html` <ha-labeled-slider
|
|
||||||
.caption=${this.hass.localize("ui.card.cover.tilt_position")}
|
|
||||||
pin=""
|
|
||||||
extra=""
|
|
||||||
.value=${this.stateObj.attributes.current_tilt_position}
|
|
||||||
@change=${this._coverTiltPositionSliderChanged}
|
|
||||||
>
|
|
||||||
${!_isTiltOnly
|
|
||||||
? html`<ha-cover-tilt-controls
|
|
||||||
.hass=${this.hass}
|
|
||||||
slot="extra"
|
|
||||||
.stateObj=${this.stateObj}
|
|
||||||
></ha-cover-tilt-controls> `
|
|
||||||
: html``}
|
|
||||||
</ha-labeled-slider>`
|
|
||||||
: !_isTiltOnly
|
|
||||||
? html`
|
|
||||||
<div class="title">
|
|
||||||
${this.hass.localize("ui.card.cover.tilt_position")}
|
|
||||||
</div>
|
|
||||||
<ha-cover-tilt-controls
|
|
||||||
.hass=${this.hass}
|
|
||||||
.stateObj=${this.stateObj}
|
|
||||||
></ha-cover-tilt-controls>
|
|
||||||
`
|
|
||||||
: html``}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ha-attributes
|
|
||||||
.hass=${this.hass}
|
|
||||||
.stateObj=${this.stateObj}
|
|
||||||
extra-filters="current_position,current_tilt_position"
|
|
||||||
></ha-attributes>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _computeClassNames(stateObj) {
|
|
||||||
const classes = [
|
|
||||||
attributeClassNames(stateObj, [
|
|
||||||
"current_position",
|
|
||||||
"current_tilt_position",
|
|
||||||
]),
|
|
||||||
featureClassNames(stateObj, FEATURE_CLASS_NAMES),
|
|
||||||
];
|
|
||||||
return classes.join(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
private _coverPositionSliderChanged(ev) {
|
|
||||||
this.hass.callService("cover", "set_cover_position", {
|
|
||||||
entity_id: this.stateObj.entity_id,
|
|
||||||
position: ev.target.value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private _coverTiltPositionSliderChanged(ev) {
|
|
||||||
this.hass.callService("cover", "set_cover_tilt_position", {
|
|
||||||
entity_id: this.stateObj.entity_id,
|
|
||||||
tilt_position: ev.target.value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResult {
|
|
||||||
return css`
|
|
||||||
.current_position,
|
|
||||||
.tilt {
|
|
||||||
max-height: 0px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.has-set_position .current_position,
|
|
||||||
.has-current_position .current_position,
|
|
||||||
.has-open_tilt .tilt,
|
|
||||||
.has-close_tilt .tilt,
|
|
||||||
.has-stop_tilt .tilt,
|
|
||||||
.has-set_tilt_position .tilt,
|
|
||||||
.has-current_tilt_position .tilt {
|
|
||||||
max-height: 208px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* from ha-labeled-slider for consistent look */
|
|
||||||
.title {
|
|
||||||
margin: 5px 0 8px;
|
|
||||||
color: var(--primary-text-color);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"more-info-cover": MoreInfoCover;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -21,7 +21,7 @@ class HaInitPage extends LitElement {
|
|||||||
Home Assistant is not currently connected. You can ask it to
|
Home Assistant is not currently connected. You can ask it to
|
||||||
come online from your
|
come online from your
|
||||||
<a href="https://account.nabucasa.com/"
|
<a href="https://account.nabucasa.com/"
|
||||||
>Nabu Casa account page</a
|
>Naba Casa account page</a
|
||||||
>.
|
>.
|
||||||
</p>
|
</p>
|
||||||
`
|
`
|
||||||
|
@@ -91,7 +91,7 @@ class HassSubpage extends LitElement {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
.toolbar a {
|
.toolbar a {
|
||||||
color: var(--sidebar-text-color);
|
color: var(--app-header-text-color);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -227,7 +227,7 @@ class HassTabsSubpage extends LitElement {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
.toolbar a {
|
.toolbar a {
|
||||||
color: var(--sidebar-text-color);
|
color: var(--app-header-text-color);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
.bottom-bar a {
|
.bottom-bar a {
|
||||||
|
@@ -13,11 +13,12 @@ import { extractSearchParamsObject } from "../common/url/search-params";
|
|||||||
import { subscribeOne } from "../common/util/subscribe-one";
|
import { subscribeOne } from "../common/util/subscribe-one";
|
||||||
import { AuthUrlSearchParams, hassUrl } from "../data/auth";
|
import { AuthUrlSearchParams, hassUrl } from "../data/auth";
|
||||||
import {
|
import {
|
||||||
fetchInstallationType,
|
InstallationType,
|
||||||
fetchOnboardingOverview,
|
fetchOnboardingOverview,
|
||||||
OnboardingResponses,
|
OnboardingResponses,
|
||||||
OnboardingStep,
|
OnboardingStep,
|
||||||
onboardIntegrationStep,
|
onboardIntegrationStep,
|
||||||
|
fetchInstallationType,
|
||||||
} from "../data/onboarding";
|
} from "../data/onboarding";
|
||||||
import { subscribeUser } from "../data/ws-user";
|
import { subscribeUser } from "../data/ws-user";
|
||||||
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
|
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
|
||||||
@@ -68,6 +69,8 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
|||||||
|
|
||||||
@state() private _steps?: OnboardingStep[];
|
@state() private _steps?: OnboardingStep[];
|
||||||
|
|
||||||
|
@state() private _installation_type?: InstallationType;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
const step = this._curStep()!;
|
const step = this._curStep()!;
|
||||||
|
|
||||||
@@ -87,6 +90,7 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
|||||||
? html`<onboarding-restore-backup
|
? html`<onboarding-restore-backup
|
||||||
.localize=${this.localize}
|
.localize=${this.localize}
|
||||||
.restoring=${this._restoring}
|
.restoring=${this._restoring}
|
||||||
|
.installtionType=${this._installation_type}
|
||||||
@restoring=${this._restoringBackup}
|
@restoring=${this._restoringBackup}
|
||||||
>
|
>
|
||||||
</onboarding-restore-backup>`
|
</onboarding-restore-backup>`
|
||||||
|
@@ -2,15 +2,15 @@ import "@material/mwc-button/mwc-button";
|
|||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import "../../hassio/src/components/hassio-ansi-to-html";
|
import "../../hassio/src/components/hassio-ansi-to-html";
|
||||||
import { showBackupUploadDialog } from "../../hassio/src/dialogs/backup/show-dialog-backup-upload";
|
|
||||||
import { showHassioBackupDialog } from "../../hassio/src/dialogs/backup/show-dialog-hassio-backup";
|
import { showHassioBackupDialog } from "../../hassio/src/dialogs/backup/show-dialog-hassio-backup";
|
||||||
|
import { showBackupUploadDialog } from "../../hassio/src/dialogs/backup/show-dialog-backup-upload";
|
||||||
import type { LocalizeFunc } from "../common/translations/localize";
|
import type { LocalizeFunc } from "../common/translations/localize";
|
||||||
import "../components/ha-card";
|
import "../components/ha-card";
|
||||||
import { fetchInstallationType } from "../data/onboarding";
|
|
||||||
import { makeDialogManager } from "../dialogs/make-dialog-manager";
|
import { makeDialogManager } from "../dialogs/make-dialog-manager";
|
||||||
import { ProvideHassLitMixin } from "../mixins/provide-hass-lit-mixin";
|
import { ProvideHassLitMixin } from "../mixins/provide-hass-lit-mixin";
|
||||||
import { haStyle } from "../resources/styles";
|
import { haStyle } from "../resources/styles";
|
||||||
import "./onboarding-loading";
|
import "./onboarding-loading";
|
||||||
|
import { fetchInstallationType, InstallationType } from "../data/onboarding";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HASSDomEvents {
|
interface HASSDomEvents {
|
||||||
@@ -26,6 +26,9 @@ class OnboardingRestoreBackup extends ProvideHassLitMixin(LitElement) {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public restoring = false;
|
@property({ type: Boolean }) public restoring = false;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
public installationType?: InstallationType;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return this.restoring
|
return this.restoring
|
||||||
? html`<ha-card
|
? html`<ha-card
|
||||||
|
@@ -5,7 +5,6 @@ import "@polymer/paper-item/paper-item";
|
|||||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
import { handleStructError } from "../../../../common/structs/handle-errors";
|
|
||||||
import "../../../../components/ha-button-menu";
|
import "../../../../components/ha-button-menu";
|
||||||
import "../../../../components/ha-card";
|
import "../../../../components/ha-card";
|
||||||
import "../../../../components/ha-icon-button";
|
import "../../../../components/ha-icon-button";
|
||||||
@@ -52,8 +51,6 @@ export default class HaAutomationConditionRow extends LitElement {
|
|||||||
|
|
||||||
@state() private _yamlMode = false;
|
@state() private _yamlMode = false;
|
||||||
|
|
||||||
@state() private _warnings?: string[];
|
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
if (!this.condition) {
|
if (!this.condition) {
|
||||||
return html``;
|
return html``;
|
||||||
@@ -90,25 +87,7 @@ export default class HaAutomationConditionRow extends LitElement {
|
|||||||
</mwc-list-item>
|
</mwc-list-item>
|
||||||
</ha-button-menu>
|
</ha-button-menu>
|
||||||
</div>
|
</div>
|
||||||
${this._warnings
|
|
||||||
? html`<ha-alert
|
|
||||||
alert-type="warning"
|
|
||||||
.title=${this.hass.localize(
|
|
||||||
"ui.errors.config.editor_not_supported"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
${this._warnings!.length > 0 && this._warnings![0] !== undefined
|
|
||||||
? html` <ul>
|
|
||||||
${this._warnings!.map(
|
|
||||||
(warning) => html`<li>${warning}</li>`
|
|
||||||
)}
|
|
||||||
</ul>`
|
|
||||||
: ""}
|
|
||||||
${this.hass.localize("ui.errors.config.edit_in_yaml_supported")}
|
|
||||||
</ha-alert>`
|
|
||||||
: ""}
|
|
||||||
<ha-automation-condition-editor
|
<ha-automation-condition-editor
|
||||||
@ui-mode-not-available=${this._handleUiModeNotAvailable}
|
|
||||||
.yamlMode=${this._yamlMode}
|
.yamlMode=${this._yamlMode}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.condition=${this.condition}
|
.condition=${this.condition}
|
||||||
@@ -118,15 +97,6 @@ export default class HaAutomationConditionRow extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleUiModeNotAvailable(ev: CustomEvent) {
|
|
||||||
// Prevent possible parent action-row from switching to yamlMode
|
|
||||||
ev.stopPropagation();
|
|
||||||
this._warnings = handleStructError(this.hass, ev.detail).warnings;
|
|
||||||
if (!this._yamlMode) {
|
|
||||||
this._yamlMode = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _handleAction(ev: CustomEvent<ActionDetail>) {
|
private _handleAction(ev: CustomEvent<ActionDetail>) {
|
||||||
switch (ev.detail.index) {
|
switch (ev.detail.index) {
|
||||||
case 0:
|
case 0:
|
||||||
@@ -155,7 +125,6 @@ export default class HaAutomationConditionRow extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _switchYamlMode() {
|
private _switchYamlMode() {
|
||||||
this._warnings = undefined;
|
|
||||||
this._yamlMode = !this._yamlMode;
|
this._yamlMode = !this._yamlMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
import { html, LitElement, PropertyValues } from "lit";
|
import { html, LitElement } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { createDurationData } from "../../../../../common/datetime/create_duration_data";
|
import { createDurationData } from "../../../../../common/datetime/create_duration_data";
|
||||||
import "../../../../../components/entity/ha-entity-attribute-picker";
|
import "../../../../../components/entity/ha-entity-attribute-picker";
|
||||||
@@ -11,7 +11,6 @@ import {
|
|||||||
handleChangeEvent,
|
handleChangeEvent,
|
||||||
} from "../ha-automation-condition-row";
|
} from "../ha-automation-condition-row";
|
||||||
import "../../../../../components/ha-duration-input";
|
import "../../../../../components/ha-duration-input";
|
||||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
|
||||||
|
|
||||||
@customElement("ha-automation-condition-state")
|
@customElement("ha-automation-condition-state")
|
||||||
export class HaStateCondition extends LitElement implements ConditionElement {
|
export class HaStateCondition extends LitElement implements ConditionElement {
|
||||||
@@ -23,23 +22,6 @@ export class HaStateCondition extends LitElement implements ConditionElement {
|
|||||||
return { entity_id: "", state: "" };
|
return { entity_id: "", state: "" };
|
||||||
}
|
}
|
||||||
|
|
||||||
public willUpdate(changedProperties: PropertyValues): boolean {
|
|
||||||
if (
|
|
||||||
changedProperties.has("condition") &&
|
|
||||||
Array.isArray(this.condition?.state)
|
|
||||||
) {
|
|
||||||
fireEvent(
|
|
||||||
this,
|
|
||||||
"ui-mode-not-available",
|
|
||||||
Error(this.hass.localize("ui.errors.config.no_state_array_support"))
|
|
||||||
);
|
|
||||||
// We have to stop the update if state is an array.
|
|
||||||
// Otherwise the state will be changed to a comma-separated string by the input element.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
const { entity_id, attribute, state } = this.condition;
|
const { entity_id, attribute, state } = this.condition;
|
||||||
const forTime = createDurationData(this.condition.for);
|
const forTime = createDurationData(this.condition.for);
|
||||||
|
@@ -1,33 +1,21 @@
|
|||||||
import { mdiCellphoneCog, mdiCloudLock } from "@mdi/js";
|
import "./ha-config-updates";
|
||||||
|
import { mdiCloudLock } from "@mdi/js";
|
||||||
import "@polymer/app-layout/app-header/app-header";
|
import "@polymer/app-layout/app-header/app-header";
|
||||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||||
import {
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
css,
|
import { customElement, property } from "lit/decorators";
|
||||||
CSSResultGroup,
|
|
||||||
html,
|
|
||||||
LitElement,
|
|
||||||
PropertyValues,
|
|
||||||
TemplateResult,
|
|
||||||
} from "lit";
|
|
||||||
import { customElement, property, state } from "lit/decorators";
|
|
||||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||||
import { extractSearchParam } from "../../../common/url/search-params";
|
|
||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
import "../../../components/ha-icon-next";
|
import "../../../components/ha-icon-next";
|
||||||
import "../../../components/ha-menu-button";
|
import "../../../components/ha-menu-button";
|
||||||
import { CloudStatus } from "../../../data/cloud";
|
import { CloudStatus } from "../../../data/cloud";
|
||||||
import { SupervisorAvailableUpdates } from "../../../data/supervisor/supervisor";
|
|
||||||
import {
|
|
||||||
ExternalConfig,
|
|
||||||
getExternalConfig,
|
|
||||||
} from "../../../external_app/external_config";
|
|
||||||
import "../../../layouts/ha-app-layout";
|
import "../../../layouts/ha-app-layout";
|
||||||
import { haStyle } from "../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import "../ha-config-section";
|
import "../ha-config-section";
|
||||||
import { configSections } from "../ha-panel-config";
|
import { configSections } from "../ha-panel-config";
|
||||||
import "./ha-config-navigation";
|
import "./ha-config-navigation";
|
||||||
import "./ha-config-updates";
|
import { SupervisorAvailableUpdates } from "../../../data/supervisor/supervisor";
|
||||||
|
|
||||||
@customElement("ha-config-dashboard")
|
@customElement("ha-config-dashboard")
|
||||||
class HaConfigDashboard extends LitElement {
|
class HaConfigDashboard extends LitElement {
|
||||||
@@ -44,18 +32,6 @@ class HaConfigDashboard extends LitElement {
|
|||||||
|
|
||||||
@property() public showAdvanced!: boolean;
|
@property() public showAdvanced!: boolean;
|
||||||
|
|
||||||
@state() private _externalConfig?: ExternalConfig;
|
|
||||||
|
|
||||||
protected firstUpdated(changedProps: PropertyValues) {
|
|
||||||
super.firstUpdated(changedProps);
|
|
||||||
|
|
||||||
if (this.hass && this.hass.auth.external) {
|
|
||||||
getExternalConfig(this.hass.auth.external).then((conf) => {
|
|
||||||
this._externalConfig = conf;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<ha-app-layout>
|
<ha-app-layout>
|
||||||
@@ -77,7 +53,7 @@ class HaConfigDashboard extends LitElement {
|
|||||||
${isComponentLoaded(this.hass, "hassio") &&
|
${isComponentLoaded(this.hass, "hassio") &&
|
||||||
this.supervisorUpdates === undefined
|
this.supervisorUpdates === undefined
|
||||||
? html``
|
? html``
|
||||||
: html`${this.supervisorUpdates?.length
|
: html`${this.supervisorUpdates !== null
|
||||||
? html`<ha-card>
|
? html`<ha-card>
|
||||||
<ha-config-updates
|
<ha-config-updates
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@@ -87,7 +63,7 @@ class HaConfigDashboard extends LitElement {
|
|||||||
</ha-card>`
|
</ha-card>`
|
||||||
: ""}
|
: ""}
|
||||||
<ha-card>
|
<ha-card>
|
||||||
${this.narrow && this.supervisorUpdates?.length
|
${this.narrow && this.supervisorUpdates !== null
|
||||||
? html`<div class="title">
|
? html`<div class="title">
|
||||||
${this.hass.localize("panel.config")}
|
${this.hass.localize("panel.config")}
|
||||||
</div>`
|
</div>`
|
||||||
@@ -96,7 +72,6 @@ class HaConfigDashboard extends LitElement {
|
|||||||
? html`
|
? html`
|
||||||
<ha-config-navigation
|
<ha-config-navigation
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.narrow=${this.narrow}
|
|
||||||
.showAdvanced=${this.showAdvanced}
|
.showAdvanced=${this.showAdvanced}
|
||||||
.pages=${[
|
.pages=${[
|
||||||
{
|
{
|
||||||
@@ -111,46 +86,31 @@ class HaConfigDashboard extends LitElement {
|
|||||||
></ha-config-navigation>
|
></ha-config-navigation>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
${this._externalConfig?.hasSettingsScreen
|
|
||||||
? html`
|
|
||||||
<ha-config-navigation
|
|
||||||
.hass=${this.hass}
|
|
||||||
.narrow=${this.narrow}
|
|
||||||
.showAdvanced=${this.showAdvanced}
|
|
||||||
.pages=${[
|
|
||||||
{
|
|
||||||
path: "#external-app-configuration",
|
|
||||||
name: "Companion App",
|
|
||||||
description: "Location and notifications",
|
|
||||||
iconPath: mdiCellphoneCog,
|
|
||||||
iconColor: "#37474F",
|
|
||||||
core: true,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
@click=${this._handleExternalAppConfiguration}
|
|
||||||
></ha-config-navigation>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
<ha-config-navigation
|
<ha-config-navigation
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.narrow=${this.narrow}
|
|
||||||
.showAdvanced=${this.showAdvanced}
|
.showAdvanced=${this.showAdvanced}
|
||||||
.pages=${configSections.dashboard}
|
.pages=${configSections.dashboard}
|
||||||
.focusedPath=${extractSearchParam("focusedPath")}
|
|
||||||
></ha-config-navigation>
|
></ha-config-navigation>
|
||||||
</ha-card>`}
|
</ha-card>
|
||||||
|
${!this.showAdvanced
|
||||||
|
? html`
|
||||||
|
<div class="promo-advanced">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.advanced_mode.hint_enable"
|
||||||
|
)}
|
||||||
|
<a href="/profile"
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.config.advanced_mode.link_profile_page"
|
||||||
|
)}</a
|
||||||
|
>.
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""}`}
|
||||||
</ha-config-section>
|
</ha-config-section>
|
||||||
</ha-app-layout>
|
</ha-app-layout>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleExternalAppConfiguration(ev: Event) {
|
|
||||||
ev.preventDefault();
|
|
||||||
this.hass.auth.external!.fireMessage({
|
|
||||||
type: "config_screen/show",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
haStyle,
|
haStyle,
|
||||||
@@ -179,6 +139,14 @@ class HaConfigDashboard extends LitElement {
|
|||||||
padding: 16px;
|
padding: 16px;
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
|
.promo-advanced {
|
||||||
|
text-align: center;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
.promo-advanced a {
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
:host([narrow]) ha-card {
|
:host([narrow]) ha-card {
|
||||||
background-color: var(--primary-background-color);
|
background-color: var(--primary-background-color);
|
||||||
box-shadow: unset;
|
box-shadow: unset;
|
||||||
|
@@ -1,13 +1,6 @@
|
|||||||
import "@polymer/paper-item/paper-icon-item";
|
import "@polymer/paper-item/paper-icon-item";
|
||||||
import "@polymer/paper-item/paper-item-body";
|
import "@polymer/paper-item/paper-item-body";
|
||||||
import {
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
css,
|
|
||||||
CSSResultGroup,
|
|
||||||
html,
|
|
||||||
LitElement,
|
|
||||||
PropertyValues,
|
|
||||||
TemplateResult,
|
|
||||||
} from "lit";
|
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { canShowPage } from "../../../common/config/can_show_page";
|
import { canShowPage } from "../../../common/config/can_show_page";
|
||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
@@ -20,27 +13,10 @@ import { HomeAssistant } from "../../../types";
|
|||||||
class HaConfigNavigation extends LitElement {
|
class HaConfigNavigation extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ type: Boolean }) public narrow!: boolean;
|
|
||||||
|
|
||||||
@property() public showAdvanced!: boolean;
|
@property() public showAdvanced!: boolean;
|
||||||
|
|
||||||
@property() public pages!: PageNavigation[];
|
@property() public pages!: PageNavigation[];
|
||||||
|
|
||||||
@property() public focusedPath?: string | null;
|
|
||||||
|
|
||||||
protected firstUpdated(changedProps: PropertyValues) {
|
|
||||||
super.firstUpdated(changedProps);
|
|
||||||
if (!this.focusedPath) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (const a of this.shadowRoot!.querySelectorAll("a")) {
|
|
||||||
if (a.href.endsWith(this.focusedPath)) {
|
|
||||||
a.querySelector("paper-icon-item")?.focus();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
${this.pages.map((page) =>
|
${this.pages.map((page) =>
|
||||||
@@ -88,7 +64,7 @@ class HaConfigNavigation extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
`}
|
`}
|
||||||
</paper-item-body>
|
</paper-item-body>
|
||||||
${!this.narrow ? html`<ha-icon-next></ha-icon-next>` : ""}
|
<ha-icon-next></ha-icon-next>
|
||||||
</paper-icon-item>
|
</paper-icon-item>
|
||||||
</a>
|
</a>
|
||||||
`
|
`
|
||||||
|
@@ -29,7 +29,7 @@ class HaConfigUpdates extends LitElement {
|
|||||||
@state() private _showAll = false;
|
@state() private _showAll = false;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (!this.supervisorUpdates?.length) {
|
if (!this.supervisorUpdates) {
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,12 +76,11 @@ class HaConfigUpdates extends LitElement {
|
|||||||
</paper-icon-item>
|
</paper-icon-item>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
|
${!this._showAll && !this.narrow ? html`<div class="divider"></div>` : ""}
|
||||||
${!this._showAll && this.supervisorUpdates.length >= 4
|
${!this._showAll && this.supervisorUpdates.length >= 4
|
||||||
? html`
|
? html`
|
||||||
<button class="link show-all" @click=${this._showAllClicked}>
|
<button class="link show-all" @click=${this._showAllClicked}>
|
||||||
${this.hass.localize("ui.panel.config.updates.more_updates", {
|
${this.hass.localize("ui.panel.config.updates.show_all_updates")}
|
||||||
count: this.supervisorUpdates!.length - updates.length,
|
|
||||||
})}
|
|
||||||
</button>
|
</button>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
@@ -123,7 +122,13 @@ class HaConfigUpdates extends LitElement {
|
|||||||
button.show-all {
|
button.show-all {
|
||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
margin: 16px;
|
margin: 8px 16px;
|
||||||
|
}
|
||||||
|
.divider::before {
|
||||||
|
content: " ";
|
||||||
|
display: block;
|
||||||
|
height: 1px;
|
||||||
|
background-color: var(--divider-color);
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
@@ -9,7 +9,7 @@ import {
|
|||||||
PropertyValues,
|
PropertyValues,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { computeDomain } from "../../../../common/entity/compute_domain";
|
import { computeDomain } from "../../../../common/entity/compute_domain";
|
||||||
import { domainIcon } from "../../../../common/entity/domain_icon";
|
import { domainIcon } from "../../../../common/entity/domain_icon";
|
||||||
import "../../../../components/entity/state-badge";
|
import "../../../../components/entity/state-badge";
|
||||||
@@ -25,10 +25,6 @@ import { showEntityEditorDialog } from "../../entities/show-dialog-entity-editor
|
|||||||
import { EntityRegistryStateEntry } from "../ha-config-device-page";
|
import { EntityRegistryStateEntry } from "../ha-config-device-page";
|
||||||
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
||||||
import { stripPrefixFromEntityName } from "../../../../common/entity/strip_prefix_from_entity_name";
|
import { stripPrefixFromEntityName } from "../../../../common/entity/strip_prefix_from_entity_name";
|
||||||
import {
|
|
||||||
ExtEntityRegistryEntry,
|
|
||||||
getExtendedEntityRegistryEntry,
|
|
||||||
} from "../../../../data/entity_registry";
|
|
||||||
|
|
||||||
@customElement("ha-device-entities-card")
|
@customElement("ha-device-entities-card")
|
||||||
export class HaDeviceEntitiesCard extends LitElement {
|
export class HaDeviceEntitiesCard extends LitElement {
|
||||||
@@ -42,11 +38,6 @@ export class HaDeviceEntitiesCard extends LitElement {
|
|||||||
|
|
||||||
@property() public showDisabled = false;
|
@property() public showDisabled = false;
|
||||||
|
|
||||||
@state() private _extDisabledEntityEntries?: Record<
|
|
||||||
string,
|
|
||||||
ExtEntityRegistryEntry
|
|
||||||
>;
|
|
||||||
|
|
||||||
private _entityRows: Array<LovelaceRow | HuiErrorCard> = [];
|
private _entityRows: Array<LovelaceRow | HuiErrorCard> = [];
|
||||||
|
|
||||||
protected shouldUpdate(changedProps: PropertyValues) {
|
protected shouldUpdate(changedProps: PropertyValues) {
|
||||||
@@ -69,13 +60,7 @@ export class HaDeviceEntitiesCard extends LitElement {
|
|||||||
<div id="entities" @hass-more-info=${this._overrideMoreInfo}>
|
<div id="entities" @hass-more-info=${this._overrideMoreInfo}>
|
||||||
${this.entities.map((entry: EntityRegistryStateEntry) => {
|
${this.entities.map((entry: EntityRegistryStateEntry) => {
|
||||||
if (entry.disabled_by) {
|
if (entry.disabled_by) {
|
||||||
if (this._extDisabledEntityEntries) {
|
disabledEntities.push(entry);
|
||||||
disabledEntities.push(
|
|
||||||
this._extDisabledEntityEntries[entry.entity_id] || entry
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
disabledEntities.push(entry);
|
|
||||||
}
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return this.hass.states[entry.entity_id]
|
return this.hass.states[entry.entity_id]
|
||||||
@@ -130,28 +115,6 @@ export class HaDeviceEntitiesCard extends LitElement {
|
|||||||
|
|
||||||
private _toggleShowDisabled() {
|
private _toggleShowDisabled() {
|
||||||
this.showDisabled = !this.showDisabled;
|
this.showDisabled = !this.showDisabled;
|
||||||
if (!this.showDisabled || this._extDisabledEntityEntries !== undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._extDisabledEntityEntries = {};
|
|
||||||
const toFetch = this.entities.filter((entry) => entry.disabled_by);
|
|
||||||
|
|
||||||
const worker = async () => {
|
|
||||||
if (toFetch.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const entityId = toFetch.pop()!.entity_id;
|
|
||||||
const entry = await getExtendedEntityRegistryEntry(this.hass, entityId);
|
|
||||||
this._extDisabledEntityEntries![entityId] = entry;
|
|
||||||
this.requestUpdate("_extDisabledEntityEntries");
|
|
||||||
worker();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fetch 3 in parallel
|
|
||||||
worker();
|
|
||||||
worker();
|
|
||||||
worker();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _renderEntity(entry: EntityRegistryStateEntry): TemplateResult {
|
private _renderEntity(entry: EntityRegistryStateEntry): TemplateResult {
|
||||||
@@ -162,9 +125,9 @@ export class HaDeviceEntitiesCard extends LitElement {
|
|||||||
const element = createRowElement(config);
|
const element = createRowElement(config);
|
||||||
if (this.hass) {
|
if (this.hass) {
|
||||||
element.hass = this.hass;
|
element.hass = this.hass;
|
||||||
const stateObj = this.hass.states[entry.entity_id];
|
const state = this.hass.states[entry.entity_id];
|
||||||
const name = stripPrefixFromEntityName(
|
const name = stripPrefixFromEntityName(
|
||||||
computeStateName(stateObj),
|
computeStateName(state),
|
||||||
`${this.deviceName} `.toLowerCase()
|
`${this.deviceName} `.toLowerCase()
|
||||||
);
|
);
|
||||||
if (name) {
|
if (name) {
|
||||||
@@ -178,11 +141,6 @@ export class HaDeviceEntitiesCard extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _renderEntry(entry: EntityRegistryStateEntry): TemplateResult {
|
private _renderEntry(entry: EntityRegistryStateEntry): TemplateResult {
|
||||||
const name =
|
|
||||||
entry.stateName ||
|
|
||||||
entry.name ||
|
|
||||||
(entry as ExtEntityRegistryEntry).original_name;
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<paper-icon-item
|
<paper-icon-item
|
||||||
class="disabled-entry"
|
class="disabled-entry"
|
||||||
@@ -195,9 +153,9 @@ export class HaDeviceEntitiesCard extends LitElement {
|
|||||||
></ha-svg-icon>
|
></ha-svg-icon>
|
||||||
<paper-item-body>
|
<paper-item-body>
|
||||||
<div class="name">
|
<div class="name">
|
||||||
${name
|
${entry.stateName
|
||||||
? stripPrefixFromEntityName(
|
? stripPrefixFromEntityName(
|
||||||
name,
|
entry.stateName,
|
||||||
`${this.deviceName} `.toLowerCase()
|
`${this.deviceName} `.toLowerCase()
|
||||||
)
|
)
|
||||||
: entry.entity_id}
|
: entry.entity_id}
|
||||||
|
@@ -10,7 +10,7 @@ import {
|
|||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||||
import {
|
import {
|
||||||
getZwaveJsIdentifiersFromDevice,
|
getIdentifiersFromDevice,
|
||||||
ZWaveJSNodeIdentifiers,
|
ZWaveJSNodeIdentifiers,
|
||||||
} from "../../../../../../data/zwave_js";
|
} from "../../../../../../data/zwave_js";
|
||||||
import { haStyle } from "../../../../../../resources/styles";
|
import { haStyle } from "../../../../../../resources/styles";
|
||||||
@@ -34,7 +34,7 @@ export class HaDeviceActionsZWaveJS extends LitElement {
|
|||||||
this._entryId = this.device.config_entries[0];
|
this._entryId = this.device.config_entries[0];
|
||||||
|
|
||||||
const identifiers: ZWaveJSNodeIdentifiers | undefined =
|
const identifiers: ZWaveJSNodeIdentifiers | undefined =
|
||||||
getZwaveJsIdentifiersFromDevice(this.device);
|
getIdentifiersFromDevice(this.device);
|
||||||
if (!identifiers) {
|
if (!identifiers) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@@ -13,8 +13,8 @@ import {
|
|||||||
getConfigEntries,
|
getConfigEntries,
|
||||||
} from "../../../../../../data/config_entries";
|
} from "../../../../../../data/config_entries";
|
||||||
import {
|
import {
|
||||||
fetchZwaveNodeStatus,
|
fetchNodeStatus,
|
||||||
getZwaveJsIdentifiersFromDevice,
|
getIdentifiersFromDevice,
|
||||||
nodeStatus,
|
nodeStatus,
|
||||||
ZWaveJSNodeStatus,
|
ZWaveJSNodeStatus,
|
||||||
ZWaveJSNodeIdentifiers,
|
ZWaveJSNodeIdentifiers,
|
||||||
@@ -42,7 +42,7 @@ export class HaDeviceInfoZWaveJS extends LitElement {
|
|||||||
protected updated(changedProperties: PropertyValues) {
|
protected updated(changedProperties: PropertyValues) {
|
||||||
if (changedProperties.has("device")) {
|
if (changedProperties.has("device")) {
|
||||||
const identifiers: ZWaveJSNodeIdentifiers | undefined =
|
const identifiers: ZWaveJSNodeIdentifiers | undefined =
|
||||||
getZwaveJsIdentifiersFromDevice(this.device);
|
getIdentifiersFromDevice(this.device);
|
||||||
if (!identifiers) {
|
if (!identifiers) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -76,11 +76,7 @@ export class HaDeviceInfoZWaveJS extends LitElement {
|
|||||||
zwaveJsConfEntries++;
|
zwaveJsConfEntries++;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._node = await fetchZwaveNodeStatus(
|
this._node = await fetchNodeStatus(this.hass, this._entryId, this._nodeId);
|
||||||
this.hass,
|
|
||||||
this._entryId,
|
|
||||||
this._nodeId
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
|
@@ -320,6 +320,8 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
entities.forEach((entity) => entity);
|
||||||
|
|
||||||
let filteredEntities = showReadOnly
|
let filteredEntities = showReadOnly
|
||||||
? entities.concat(stateEntities)
|
? entities.concat(stateEntities)
|
||||||
: entities;
|
: entities;
|
||||||
|
@@ -56,7 +56,7 @@ export const configSections: { [name: string]: PageNavigation[] } = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/config/automation",
|
path: "/config/automation",
|
||||||
name: "Automations & Scenes",
|
name: "Automations",
|
||||||
description: "Automations, blueprints, scenes and scripts",
|
description: "Automations, blueprints, scenes and scripts",
|
||||||
iconPath: mdiRobot,
|
iconPath: mdiRobot,
|
||||||
iconColor: "#518C43",
|
iconColor: "#518C43",
|
||||||
@@ -64,7 +64,7 @@ export const configSections: { [name: string]: PageNavigation[] } = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/config/helpers",
|
path: "/config/helpers",
|
||||||
name: "Automation Helpers",
|
name: "Helpers",
|
||||||
description: "Elements that help build automations",
|
description: "Elements that help build automations",
|
||||||
iconPath: mdiTools,
|
iconPath: mdiTools,
|
||||||
iconColor: "#4D2EA4",
|
iconColor: "#4D2EA4",
|
||||||
|
@@ -21,10 +21,10 @@ import {
|
|||||||
import {
|
import {
|
||||||
migrateZwave,
|
migrateZwave,
|
||||||
ZWaveJsMigrationData,
|
ZWaveJsMigrationData,
|
||||||
fetchZwaveNetworkStatus as fetchZwaveJsNetworkStatus,
|
fetchNetworkStatus as fetchZwaveJsNetworkStatus,
|
||||||
fetchZwaveNodeStatus,
|
fetchNodeStatus,
|
||||||
getZwaveJsIdentifiersFromDevice,
|
getIdentifiersFromDevice,
|
||||||
subscribeZwaveNodeReady,
|
subscribeNodeReady,
|
||||||
} from "../../../../../data/zwave_js";
|
} from "../../../../../data/zwave_js";
|
||||||
import {
|
import {
|
||||||
fetchMigrationConfig,
|
fetchMigrationConfig,
|
||||||
@@ -425,7 +425,7 @@ export class ZwaveMigration extends LitElement {
|
|||||||
this._zwaveJsEntryId!
|
this._zwaveJsEntryId!
|
||||||
);
|
);
|
||||||
const nodeStatePromisses = networkStatus.controller.nodes.map((nodeId) =>
|
const nodeStatePromisses = networkStatus.controller.nodes.map((nodeId) =>
|
||||||
fetchZwaveNodeStatus(this.hass, this._zwaveJsEntryId!, nodeId)
|
fetchNodeStatus(this.hass, this._zwaveJsEntryId!, nodeId)
|
||||||
);
|
);
|
||||||
const nodesNotReady = (await Promise.all(nodeStatePromisses)).filter(
|
const nodesNotReady = (await Promise.all(nodeStatePromisses)).filter(
|
||||||
(node) => !node.ready
|
(node) => !node.ready
|
||||||
@@ -436,18 +436,13 @@ export class ZwaveMigration extends LitElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._nodeReadySubscriptions = nodesNotReady.map((node) =>
|
this._nodeReadySubscriptions = nodesNotReady.map((node) =>
|
||||||
subscribeZwaveNodeReady(
|
subscribeNodeReady(this.hass, this._zwaveJsEntryId!, node.node_id, () => {
|
||||||
this.hass,
|
this._getZwaveJSNodesStatus();
|
||||||
this._zwaveJsEntryId!,
|
})
|
||||||
node.node_id,
|
|
||||||
() => {
|
|
||||||
this._getZwaveJSNodesStatus();
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
const deviceReg = await fetchDeviceRegistry(this.hass);
|
const deviceReg = await fetchDeviceRegistry(this.hass);
|
||||||
this._waitingOnDevices = deviceReg
|
this._waitingOnDevices = deviceReg
|
||||||
.map((device) => getZwaveJsIdentifiersFromDevice(device))
|
.map((device) => getIdentifiersFromDevice(device))
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,40 +1,30 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
import type { TextField } from "@material/mwc-textfield/mwc-textfield";
|
import { mdiAlertCircle, mdiCheckCircle, mdiCloseCircle } from "@mdi/js";
|
||||||
import "@material/mwc-textfield/mwc-textfield";
|
|
||||||
import { mdiAlertCircle, mdiCheckCircle, mdiQrcodeScan } from "@mdi/js";
|
|
||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
import "../../../../../components/ha-alert";
|
|
||||||
import { HaCheckbox } from "../../../../../components/ha-checkbox";
|
|
||||||
import "../../../../../components/ha-circular-progress";
|
import "../../../../../components/ha-circular-progress";
|
||||||
import { createCloseHeading } from "../../../../../components/ha-dialog";
|
import { createCloseHeading } from "../../../../../components/ha-dialog";
|
||||||
import "../../../../../components/ha-formfield";
|
import "../../../../../components/ha-formfield";
|
||||||
import "../../../../../components/ha-radio";
|
|
||||||
import "../../../../../components/ha-switch";
|
import "../../../../../components/ha-switch";
|
||||||
import {
|
import {
|
||||||
zwaveGrantSecurityClasses,
|
grantSecurityClasses,
|
||||||
InclusionStrategy,
|
InclusionStrategy,
|
||||||
MINIMUM_QR_STRING_LENGTH,
|
|
||||||
zwaveParseQrCode,
|
|
||||||
provisionZwaveSmartStartNode,
|
|
||||||
QRProvisioningInformation,
|
|
||||||
RequestedGrant,
|
RequestedGrant,
|
||||||
SecurityClass,
|
SecurityClass,
|
||||||
stopZwaveInclusion,
|
stopInclusion,
|
||||||
subscribeAddZwaveNode,
|
subscribeAddNode,
|
||||||
zwaveSupportsFeature,
|
validateDskAndEnterPin,
|
||||||
zwaveValidateDskAndEnterPin,
|
|
||||||
ZWaveFeature,
|
|
||||||
PlannedProvisioningEntry,
|
|
||||||
} from "../../../../../data/zwave_js";
|
} from "../../../../../data/zwave_js";
|
||||||
import { haStyle, haStyleDialog } from "../../../../../resources/styles";
|
import { haStyle, haStyleDialog } from "../../../../../resources/styles";
|
||||||
import { HomeAssistant } from "../../../../../types";
|
import { HomeAssistant } from "../../../../../types";
|
||||||
import { ZWaveJSAddNodeDialogParams } from "./show-dialog-zwave_js-add-node";
|
import { ZWaveJSAddNodeDialogParams } from "./show-dialog-zwave_js-add-node";
|
||||||
import "../../../../../components/ha-qr-scanner";
|
import "../../../../../components/ha-radio";
|
||||||
|
import { HaCheckbox } from "../../../../../components/ha-checkbox";
|
||||||
|
import "../../../../../components/ha-alert";
|
||||||
|
|
||||||
export interface ZWaveJSAddNodeDevice {
|
export interface ZWaveJSAddNodeDevice {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -50,14 +40,11 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
@state() private _status?:
|
@state() private _status?:
|
||||||
| "loading"
|
| "loading"
|
||||||
| "started"
|
| "started"
|
||||||
| "started_specific"
|
|
||||||
| "choose_strategy"
|
| "choose_strategy"
|
||||||
| "qr_scan"
|
|
||||||
| "interviewing"
|
| "interviewing"
|
||||||
| "failed"
|
| "failed"
|
||||||
| "timed_out"
|
| "timed_out"
|
||||||
| "finished"
|
| "finished"
|
||||||
| "provisioned"
|
|
||||||
| "validate_dsk_enter_pin"
|
| "validate_dsk_enter_pin"
|
||||||
| "grant_security_classes";
|
| "grant_security_classes";
|
||||||
|
|
||||||
@@ -77,14 +64,10 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
|
|
||||||
@state() private _lowSecurity = false;
|
@state() private _lowSecurity = false;
|
||||||
|
|
||||||
@state() private _supportsSmartStart?: boolean;
|
|
||||||
|
|
||||||
private _addNodeTimeoutHandle?: number;
|
private _addNodeTimeoutHandle?: number;
|
||||||
|
|
||||||
private _subscribed?: Promise<UnsubscribeFunc>;
|
private _subscribed?: Promise<UnsubscribeFunc>;
|
||||||
|
|
||||||
private _qrProcessing = false;
|
|
||||||
|
|
||||||
public disconnectedCallback(): void {
|
public disconnectedCallback(): void {
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
this._unsubscribe();
|
this._unsubscribe();
|
||||||
@@ -93,7 +76,6 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
public async showDialog(params: ZWaveJSAddNodeDialogParams): Promise<void> {
|
public async showDialog(params: ZWaveJSAddNodeDialogParams): Promise<void> {
|
||||||
this._entryId = params.entry_id;
|
this._entryId = params.entry_id;
|
||||||
this._status = "loading";
|
this._status = "loading";
|
||||||
this._checkSmartStartSupport();
|
|
||||||
this._startInclusion();
|
this._startInclusion();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,22 +157,6 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
>
|
>
|
||||||
Search device
|
Search device
|
||||||
</mwc-button>`
|
</mwc-button>`
|
||||||
: this._status === "qr_scan"
|
|
||||||
? html`<ha-qr-scanner
|
|
||||||
.localize=${this.hass.localize}
|
|
||||||
@qr-code-scanned=${this._qrCodeScanned}
|
|
||||||
></ha-qr-scanner>
|
|
||||||
<p>
|
|
||||||
If scanning doesn't work, you can enter the QR code value
|
|
||||||
manually:
|
|
||||||
</p>
|
|
||||||
<mwc-textfield
|
|
||||||
.label=${this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.add_node.enter_qr_code"
|
|
||||||
)}
|
|
||||||
.disabled=${this._qrProcessing}
|
|
||||||
@keydown=${this._qrKeyDown}
|
|
||||||
></mwc-textfield>`
|
|
||||||
: this._status === "validate_dsk_enter_pin"
|
: this._status === "validate_dsk_enter_pin"
|
||||||
? html`
|
? html`
|
||||||
<p>
|
<p>
|
||||||
@@ -275,28 +241,18 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
Retry
|
Retry
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
`
|
`
|
||||||
: this._status === "started_specific"
|
|
||||||
? html`<h3>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.add_node.searching_device"
|
|
||||||
)}
|
|
||||||
</h3>
|
|
||||||
<ha-circular-progress active></ha-circular-progress>
|
|
||||||
<p>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.add_node.follow_device_instructions"
|
|
||||||
)}
|
|
||||||
</p>`
|
|
||||||
: this._status === "started"
|
: this._status === "started"
|
||||||
? html`
|
? html`
|
||||||
<div class="select-inclusion">
|
<div class="flex-container">
|
||||||
<div class="outline">
|
<ha-circular-progress active></ha-circular-progress>
|
||||||
<h2>
|
<div class="status">
|
||||||
${this.hass.localize(
|
<p>
|
||||||
"ui.panel.config.zwave_js.add_node.searching_device"
|
<b
|
||||||
)}
|
>${this.hass.localize(
|
||||||
</h2>
|
"ui.panel.config.zwave_js.add_node.controller_in_inclusion_mode"
|
||||||
<ha-circular-progress active></ha-circular-progress>
|
)}</b
|
||||||
|
>
|
||||||
|
</p>
|
||||||
<p>
|
<p>
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.zwave_js.add_node.follow_device_instructions"
|
"ui.panel.config.zwave_js.add_node.follow_device_instructions"
|
||||||
@@ -307,37 +263,15 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
class="link"
|
class="link"
|
||||||
@click=${this._chooseInclusionStrategy}
|
@click=${this._chooseInclusionStrategy}
|
||||||
>
|
>
|
||||||
${this.hass.localize(
|
Advanced inclusion
|
||||||
"ui.panel.config.zwave_js.add_node.choose_inclusion_strategy"
|
|
||||||
)}
|
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
${this._supportsSmartStart
|
|
||||||
? html` <div class="outline">
|
|
||||||
<h2>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.add_node.qr_code"
|
|
||||||
)}
|
|
||||||
</h2>
|
|
||||||
<ha-svg-icon .path=${mdiQrcodeScan}></ha-svg-icon>
|
|
||||||
<p>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.add_node.qr_code_paragraph"
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<mwc-button @click=${this._scanQRCode}>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.add_node.scan_qr_code"
|
|
||||||
)}
|
|
||||||
</mwc-button>
|
|
||||||
</p>
|
|
||||||
</div>`
|
|
||||||
: ""}
|
|
||||||
</div>
|
</div>
|
||||||
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
|
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
|
||||||
${this.hass.localize("ui.common.cancel")}
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.zwave_js.add_node.cancel_inclusion"
|
||||||
|
)}
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
`
|
`
|
||||||
: this._status === "interviewing"
|
: this._status === "interviewing"
|
||||||
@@ -376,18 +310,16 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
: this._status === "failed"
|
: this._status === "failed"
|
||||||
? html`
|
? html`
|
||||||
<div class="flex-container">
|
<div class="flex-container">
|
||||||
|
<ha-svg-icon
|
||||||
|
.path=${mdiCloseCircle}
|
||||||
|
class="failed"
|
||||||
|
></ha-svg-icon>
|
||||||
<div class="status">
|
<div class="status">
|
||||||
<ha-alert
|
<p>
|
||||||
alert-type="error"
|
${this.hass.localize(
|
||||||
.title=${this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.add_node.inclusion_failed"
|
"ui.panel.config.zwave_js.add_node.inclusion_failed"
|
||||||
)}
|
)}
|
||||||
>
|
</p>
|
||||||
${this._error ||
|
|
||||||
this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.add_node.check_logs"
|
|
||||||
)}
|
|
||||||
</ha-alert>
|
|
||||||
${this._stages
|
${this._stages
|
||||||
? html` <div class="stages">
|
? html` <div class="stages">
|
||||||
${this._stages.map(
|
${this._stages.map(
|
||||||
@@ -459,23 +391,6 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
${this.hass.localize("ui.panel.config.zwave_js.common.close")}
|
${this.hass.localize("ui.panel.config.zwave_js.common.close")}
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
`
|
`
|
||||||
: this._status === "provisioned"
|
|
||||||
? html` <div class="flex-container">
|
|
||||||
<ha-svg-icon
|
|
||||||
.path=${mdiCheckCircle}
|
|
||||||
class="success"
|
|
||||||
></ha-svg-icon>
|
|
||||||
<div class="status">
|
|
||||||
<p>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.add_node.provisioning_finished"
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
|
|
||||||
${this.hass.localize("ui.panel.config.zwave_js.common.close")}
|
|
||||||
</mwc-button>`
|
|
||||||
: ""}
|
: ""}
|
||||||
</ha-dialog>
|
</ha-dialog>
|
||||||
`;
|
`;
|
||||||
@@ -502,83 +417,6 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _scanQRCode(): Promise<void> {
|
|
||||||
this._unsubscribe();
|
|
||||||
this._status = "qr_scan";
|
|
||||||
}
|
|
||||||
|
|
||||||
private _qrKeyDown(ev: KeyboardEvent) {
|
|
||||||
if (this._qrProcessing) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (ev.key === "Enter") {
|
|
||||||
this._handleQrCodeScanned((ev.target as TextField).value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _qrCodeScanned(ev: CustomEvent): void {
|
|
||||||
if (this._qrProcessing) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._handleQrCodeScanned(ev.detail.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _handleQrCodeScanned(qrCodeString: string): Promise<void> {
|
|
||||||
this._error = undefined;
|
|
||||||
if (this._status !== "qr_scan" || this._qrProcessing) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._qrProcessing = true;
|
|
||||||
if (
|
|
||||||
qrCodeString.length < MINIMUM_QR_STRING_LENGTH ||
|
|
||||||
!qrCodeString.startsWith("90")
|
|
||||||
) {
|
|
||||||
this._qrProcessing = false;
|
|
||||||
this._error = `Invalid QR code (${qrCodeString})`;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let provisioningInfo: QRProvisioningInformation;
|
|
||||||
try {
|
|
||||||
provisioningInfo = await zwaveParseQrCode(
|
|
||||||
this.hass,
|
|
||||||
this._entryId!,
|
|
||||||
qrCodeString
|
|
||||||
);
|
|
||||||
} catch (err: any) {
|
|
||||||
this._qrProcessing = false;
|
|
||||||
this._error = err.message;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._status = "loading";
|
|
||||||
// wait for QR scanner to be removed before resetting qr processing
|
|
||||||
this.updateComplete.then(() => {
|
|
||||||
this._qrProcessing = false;
|
|
||||||
});
|
|
||||||
if (provisioningInfo.version === 1) {
|
|
||||||
try {
|
|
||||||
await provisionZwaveSmartStartNode(
|
|
||||||
this.hass,
|
|
||||||
this._entryId!,
|
|
||||||
provisioningInfo
|
|
||||||
);
|
|
||||||
this._status = "provisioned";
|
|
||||||
} catch (err: any) {
|
|
||||||
this._error = err.message;
|
|
||||||
this._status = "failed";
|
|
||||||
}
|
|
||||||
} else if (provisioningInfo.version === 0) {
|
|
||||||
this._inclusionStrategy = InclusionStrategy.Security_S2;
|
|
||||||
// this._startInclusion(provisioningInfo);
|
|
||||||
this._startInclusion(undefined, undefined, {
|
|
||||||
dsk: "34673-15546-46480-39591-32400-22155-07715-45994",
|
|
||||||
security_classes: [0, 1, 7],
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this._error = "This QR code is not supported";
|
|
||||||
this._status = "failed";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _handlePinKeyUp(ev: KeyboardEvent) {
|
private _handlePinKeyUp(ev: KeyboardEvent) {
|
||||||
if (ev.key === "Enter") {
|
if (ev.key === "Enter") {
|
||||||
this._validateDskAndEnterPin();
|
this._validateDskAndEnterPin();
|
||||||
@@ -589,7 +427,7 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
this._status = "loading";
|
this._status = "loading";
|
||||||
this._error = undefined;
|
this._error = undefined;
|
||||||
try {
|
try {
|
||||||
await zwaveValidateDskAndEnterPin(
|
await validateDskAndEnterPin(
|
||||||
this.hass,
|
this.hass,
|
||||||
this._entryId!,
|
this._entryId!,
|
||||||
this._pinInput!.value as string
|
this._pinInput!.value as string
|
||||||
@@ -604,7 +442,7 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
this._status = "loading";
|
this._status = "loading";
|
||||||
this._error = undefined;
|
this._error = undefined;
|
||||||
try {
|
try {
|
||||||
await zwaveGrantSecurityClasses(
|
await grantSecurityClasses(
|
||||||
this.hass,
|
this.hass,
|
||||||
this._entryId!,
|
this._entryId!,
|
||||||
this._securityClasses
|
this._securityClasses
|
||||||
@@ -622,33 +460,17 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
this._startInclusion();
|
this._startInclusion();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _checkSmartStartSupport() {
|
private _startInclusion(): void {
|
||||||
this._supportsSmartStart = (
|
|
||||||
await zwaveSupportsFeature(
|
|
||||||
this.hass,
|
|
||||||
this._entryId!,
|
|
||||||
ZWaveFeature.SmartStart
|
|
||||||
)
|
|
||||||
).supported;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _startInclusion(
|
|
||||||
qrProvisioningInformation?: QRProvisioningInformation,
|
|
||||||
qrCodeString?: string,
|
|
||||||
plannedProvisioningEntry?: PlannedProvisioningEntry
|
|
||||||
): void {
|
|
||||||
if (!this.hass) {
|
if (!this.hass) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._lowSecurity = false;
|
this._lowSecurity = false;
|
||||||
const specificDevice =
|
this._subscribed = subscribeAddNode(
|
||||||
qrProvisioningInformation || qrCodeString || plannedProvisioningEntry;
|
|
||||||
this._subscribed = subscribeAddZwaveNode(
|
|
||||||
this.hass,
|
this.hass,
|
||||||
this._entryId!,
|
this._entryId!,
|
||||||
(message) => {
|
(message) => {
|
||||||
if (message.event === "inclusion started") {
|
if (message.event === "inclusion started") {
|
||||||
this._status = specificDevice ? "started_specific" : "started";
|
this._status = "started";
|
||||||
}
|
}
|
||||||
if (message.event === "inclusion failed") {
|
if (message.event === "inclusion failed") {
|
||||||
this._unsubscribe();
|
this._unsubscribe();
|
||||||
@@ -669,7 +491,7 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
|
|
||||||
if (message.event === "grant security classes") {
|
if (message.event === "grant security classes") {
|
||||||
if (this._inclusionStrategy === undefined) {
|
if (this._inclusionStrategy === undefined) {
|
||||||
zwaveGrantSecurityClasses(
|
grantSecurityClasses(
|
||||||
this.hass,
|
this.hass,
|
||||||
this._entryId!,
|
this._entryId!,
|
||||||
message.requested_grant.securityClasses,
|
message.requested_grant.securityClasses,
|
||||||
@@ -703,10 +525,7 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
this._inclusionStrategy,
|
this._inclusionStrategy
|
||||||
qrProvisioningInformation,
|
|
||||||
qrCodeString,
|
|
||||||
plannedProvisioningEntry
|
|
||||||
);
|
);
|
||||||
this._addNodeTimeoutHandle = window.setTimeout(() => {
|
this._addNodeTimeoutHandle = window.setTimeout(() => {
|
||||||
this._unsubscribe();
|
this._unsubscribe();
|
||||||
@@ -720,7 +539,7 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
this._subscribed = undefined;
|
this._subscribed = undefined;
|
||||||
}
|
}
|
||||||
if (this._entryId) {
|
if (this._entryId) {
|
||||||
stopZwaveInclusion(this.hass, this._entryId);
|
stopInclusion(this.hass, this._entryId);
|
||||||
}
|
}
|
||||||
this._requestedGrant = undefined;
|
this._requestedGrant = undefined;
|
||||||
this._dsk = undefined;
|
this._dsk = undefined;
|
||||||
@@ -739,7 +558,6 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
this._status = undefined;
|
this._status = undefined;
|
||||||
this._device = undefined;
|
this._device = undefined;
|
||||||
this._stages = undefined;
|
this._stages = undefined;
|
||||||
this._error = undefined;
|
|
||||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -760,6 +578,10 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
color: var(--warning-color);
|
color: var(--warning-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.failed {
|
||||||
|
color: var(--error-color);
|
||||||
|
}
|
||||||
|
|
||||||
.stages {
|
.stages {
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -788,39 +610,6 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
padding: 8px 0;
|
padding: 8px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.select-inclusion {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select-inclusion .outline:nth-child(2) {
|
|
||||||
margin-left: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select-inclusion .outline {
|
|
||||||
border: 1px solid var(--divider-color);
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 16px;
|
|
||||||
min-height: 250px;
|
|
||||||
text-align: center;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media all and (max-width: 500px) {
|
|
||||||
.select-inclusion {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select-inclusion .outline:nth-child(2) {
|
|
||||||
margin-left: 0;
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mwc-textfield {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-svg-icon {
|
ha-svg-icon {
|
||||||
width: 68px;
|
width: 68px;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
|
@@ -7,10 +7,10 @@ import { customElement, property, state } from "lit/decorators";
|
|||||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
import { createCloseHeading } from "../../../../../components/ha-dialog";
|
import { createCloseHeading } from "../../../../../components/ha-dialog";
|
||||||
import {
|
import {
|
||||||
fetchZwaveNetworkStatus,
|
fetchNetworkStatus,
|
||||||
healZwaveNetwork,
|
healNetwork,
|
||||||
stopHealZwaveNetwork,
|
stopHealNetwork,
|
||||||
subscribeHealZwaveNetworkProgress,
|
subscribeHealNetworkProgress,
|
||||||
ZWaveJSHealNetworkStatusMessage,
|
ZWaveJSHealNetworkStatusMessage,
|
||||||
ZWaveJSNetwork,
|
ZWaveJSNetwork,
|
||||||
} from "../../../../../data/zwave_js";
|
} from "../../../../../data/zwave_js";
|
||||||
@@ -202,13 +202,13 @@ class DialogZWaveJSHealNetwork extends LitElement {
|
|||||||
if (!this.hass) {
|
if (!this.hass) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const network: ZWaveJSNetwork = await fetchZwaveNetworkStatus(
|
const network: ZWaveJSNetwork = await fetchNetworkStatus(
|
||||||
this.hass!,
|
this.hass!,
|
||||||
this.entry_id!
|
this.entry_id!
|
||||||
);
|
);
|
||||||
if (network.controller.is_heal_network_active) {
|
if (network.controller.is_heal_network_active) {
|
||||||
this._status = "started";
|
this._status = "started";
|
||||||
this._subscribed = subscribeHealZwaveNetworkProgress(
|
this._subscribed = subscribeHealNetworkProgress(
|
||||||
this.hass,
|
this.hass,
|
||||||
this.entry_id!,
|
this.entry_id!,
|
||||||
this._handleMessage.bind(this)
|
this._handleMessage.bind(this)
|
||||||
@@ -220,9 +220,9 @@ class DialogZWaveJSHealNetwork extends LitElement {
|
|||||||
if (!this.hass) {
|
if (!this.hass) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
healZwaveNetwork(this.hass, this.entry_id!);
|
healNetwork(this.hass, this.entry_id!);
|
||||||
this._status = "started";
|
this._status = "started";
|
||||||
this._subscribed = subscribeHealZwaveNetworkProgress(
|
this._subscribed = subscribeHealNetworkProgress(
|
||||||
this.hass,
|
this.hass,
|
||||||
this.entry_id!,
|
this.entry_id!,
|
||||||
this._handleMessage.bind(this)
|
this._handleMessage.bind(this)
|
||||||
@@ -233,7 +233,7 @@ class DialogZWaveJSHealNetwork extends LitElement {
|
|||||||
if (!this.hass) {
|
if (!this.hass) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
stopHealZwaveNetwork(this.hass, this.entry_id!);
|
stopHealNetwork(this.hass, this.entry_id!);
|
||||||
this._unsubscribe();
|
this._unsubscribe();
|
||||||
this._status = "cancelled";
|
this._status = "cancelled";
|
||||||
}
|
}
|
||||||
|
@@ -10,8 +10,8 @@ import {
|
|||||||
computeDeviceName,
|
computeDeviceName,
|
||||||
} from "../../../../../data/device_registry";
|
} from "../../../../../data/device_registry";
|
||||||
import {
|
import {
|
||||||
fetchZwaveNetworkStatus,
|
fetchNetworkStatus,
|
||||||
healZwaveNode,
|
healNode,
|
||||||
ZWaveJSNetwork,
|
ZWaveJSNetwork,
|
||||||
} from "../../../../../data/zwave_js";
|
} from "../../../../../data/zwave_js";
|
||||||
import { haStyleDialog } from "../../../../../resources/styles";
|
import { haStyleDialog } from "../../../../../resources/styles";
|
||||||
@@ -206,7 +206,7 @@ class DialogZWaveJSHealNode extends LitElement {
|
|||||||
if (!this.hass) {
|
if (!this.hass) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const network: ZWaveJSNetwork = await fetchZwaveNetworkStatus(
|
const network: ZWaveJSNetwork = await fetchNetworkStatus(
|
||||||
this.hass!,
|
this.hass!,
|
||||||
this.entry_id!
|
this.entry_id!
|
||||||
);
|
);
|
||||||
@@ -221,11 +221,7 @@ class DialogZWaveJSHealNode extends LitElement {
|
|||||||
}
|
}
|
||||||
this._status = "started";
|
this._status = "started";
|
||||||
try {
|
try {
|
||||||
this._status = (await healZwaveNode(
|
this._status = (await healNode(this.hass, this.entry_id!, this.node_id!))
|
||||||
this.hass,
|
|
||||||
this.entry_id!,
|
|
||||||
this.node_id!
|
|
||||||
))
|
|
||||||
? "finished"
|
? "finished"
|
||||||
: "failed";
|
: "failed";
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
|
@@ -6,7 +6,7 @@ import { customElement, property, state } from "lit/decorators";
|
|||||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
import "../../../../../components/ha-circular-progress";
|
import "../../../../../components/ha-circular-progress";
|
||||||
import { createCloseHeading } from "../../../../../components/ha-dialog";
|
import { createCloseHeading } from "../../../../../components/ha-dialog";
|
||||||
import { reinterviewZwaveNode } from "../../../../../data/zwave_js";
|
import { reinterviewNode } from "../../../../../data/zwave_js";
|
||||||
import { haStyleDialog } from "../../../../../resources/styles";
|
import { haStyleDialog } from "../../../../../resources/styles";
|
||||||
import { HomeAssistant } from "../../../../../types";
|
import { HomeAssistant } from "../../../../../types";
|
||||||
import { ZWaveJSReinterviewNodeDialogParams } from "./show-dialog-zwave_js-reinterview-node";
|
import { ZWaveJSReinterviewNodeDialogParams } from "./show-dialog-zwave_js-reinterview-node";
|
||||||
@@ -157,7 +157,7 @@ class DialogZWaveJSReinterviewNode extends LitElement {
|
|||||||
if (!this.hass) {
|
if (!this.hass) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._subscribed = reinterviewZwaveNode(
|
this._subscribed = reinterviewNode(
|
||||||
this.hass,
|
this.hass,
|
||||||
this.entry_id!,
|
this.entry_id!,
|
||||||
this.node_id!,
|
this.node_id!,
|
||||||
|
@@ -7,7 +7,7 @@ import { fireEvent } from "../../../../../common/dom/fire_event";
|
|||||||
import "../../../../../components/ha-circular-progress";
|
import "../../../../../components/ha-circular-progress";
|
||||||
import { createCloseHeading } from "../../../../../components/ha-dialog";
|
import { createCloseHeading } from "../../../../../components/ha-dialog";
|
||||||
import {
|
import {
|
||||||
removeFailedZwaveNode,
|
removeFailedNode,
|
||||||
ZWaveJSRemovedNode,
|
ZWaveJSRemovedNode,
|
||||||
} from "../../../../../data/zwave_js";
|
} from "../../../../../data/zwave_js";
|
||||||
import { haStyleDialog } from "../../../../../resources/styles";
|
import { haStyleDialog } from "../../../../../resources/styles";
|
||||||
@@ -164,7 +164,7 @@ class DialogZWaveJSRemoveFailedNode extends LitElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._status = "started";
|
this._status = "started";
|
||||||
this._subscribed = removeFailedZwaveNode(
|
this._subscribed = removeFailedNode(
|
||||||
this.hass,
|
this.hass,
|
||||||
this.entry_id!,
|
this.entry_id!,
|
||||||
this.node_id!,
|
this.node_id!,
|
||||||
|
@@ -9,11 +9,11 @@ import "../../../../../components/ha-icon-next";
|
|||||||
import "../../../../../components/ha-svg-icon";
|
import "../../../../../components/ha-svg-icon";
|
||||||
import { getSignedPath } from "../../../../../data/auth";
|
import { getSignedPath } from "../../../../../data/auth";
|
||||||
import {
|
import {
|
||||||
fetchZwaveDataCollectionStatus,
|
fetchDataCollectionStatus,
|
||||||
fetchZwaveNetworkStatus,
|
fetchNetworkStatus,
|
||||||
fetchZwaveNodeStatus,
|
fetchNodeStatus,
|
||||||
NodeStatus,
|
NodeStatus,
|
||||||
setZwaveDataCollectionPreference,
|
setDataCollectionPreference,
|
||||||
ZWaveJSNetwork,
|
ZWaveJSNetwork,
|
||||||
ZWaveJSNodeStatus,
|
ZWaveJSNodeStatus,
|
||||||
} from "../../../../../data/zwave_js";
|
} from "../../../../../data/zwave_js";
|
||||||
@@ -317,8 +317,8 @@ class ZWaveJSConfigDashboard extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const [network, dataCollectionStatus] = await Promise.all([
|
const [network, dataCollectionStatus] = await Promise.all([
|
||||||
fetchZwaveNetworkStatus(this.hass!, this.configEntryId),
|
fetchNetworkStatus(this.hass!, this.configEntryId),
|
||||||
fetchZwaveDataCollectionStatus(this.hass!, this.configEntryId),
|
fetchDataCollectionStatus(this.hass!, this.configEntryId),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
this._network = network;
|
this._network = network;
|
||||||
@@ -340,7 +340,7 @@ class ZWaveJSConfigDashboard extends LitElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const nodeStatePromisses = this._network.controller.nodes.map((nodeId) =>
|
const nodeStatePromisses = this._network.controller.nodes.map((nodeId) =>
|
||||||
fetchZwaveNodeStatus(this.hass, this.configEntryId!, nodeId)
|
fetchNodeStatus(this.hass, this.configEntryId!, nodeId)
|
||||||
);
|
);
|
||||||
this._nodes = await Promise.all(nodeStatePromisses);
|
this._nodes = await Promise.all(nodeStatePromisses);
|
||||||
}
|
}
|
||||||
@@ -364,7 +364,7 @@ class ZWaveJSConfigDashboard extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _dataCollectionToggled(ev) {
|
private _dataCollectionToggled(ev) {
|
||||||
setZwaveDataCollectionPreference(
|
setDataCollectionPreference(
|
||||||
this.hass!,
|
this.hass!,
|
||||||
this.configEntryId!,
|
this.configEntryId!,
|
||||||
ev.target.checked
|
ev.target.checked
|
||||||
|
@@ -32,9 +32,9 @@ import {
|
|||||||
subscribeDeviceRegistry,
|
subscribeDeviceRegistry,
|
||||||
} from "../../../../../data/device_registry";
|
} from "../../../../../data/device_registry";
|
||||||
import {
|
import {
|
||||||
fetchZwaveNodeConfigParameters,
|
fetchNodeConfigParameters,
|
||||||
fetchZwaveNodeMetadata,
|
fetchNodeMetadata,
|
||||||
setZwaveNodeConfigParameter,
|
setNodeConfigParameter,
|
||||||
ZWaveJSNodeConfigParams,
|
ZWaveJSNodeConfigParams,
|
||||||
ZwaveJSNodeMetadata,
|
ZwaveJSNodeMetadata,
|
||||||
ZWaveJSSetConfigParamResult,
|
ZWaveJSSetConfigParamResult,
|
||||||
@@ -377,7 +377,7 @@ class ZWaveJSNodeConfig extends SubscribeMixin(LitElement) {
|
|||||||
private async _updateConfigParameter(target, value) {
|
private async _updateConfigParameter(target, value) {
|
||||||
const nodeId = getNodeId(this._device!);
|
const nodeId = getNodeId(this._device!);
|
||||||
try {
|
try {
|
||||||
const result = await setZwaveNodeConfigParameter(
|
const result = await setNodeConfigParameter(
|
||||||
this.hass,
|
this.hass,
|
||||||
this.configEntryId!,
|
this.configEntryId!,
|
||||||
nodeId!,
|
nodeId!,
|
||||||
@@ -429,8 +429,8 @@ class ZWaveJSNodeConfig extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[this._nodeMetadata, this._config] = await Promise.all([
|
[this._nodeMetadata, this._config] = await Promise.all([
|
||||||
fetchZwaveNodeMetadata(this.hass, this.configEntryId, nodeId!),
|
fetchNodeMetadata(this.hass, this.configEntryId, nodeId!),
|
||||||
fetchZwaveNodeConfigParameters(this.hass, this.configEntryId, nodeId!),
|
fetchNodeConfigParameters(this.hass, this.configEntryId, nodeId!),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -20,7 +20,7 @@ class ErrorLogCard extends LitElement {
|
|||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
.path=${mdiRefresh}
|
.path=${mdiRefresh}
|
||||||
@click=${this._refreshErrorLog}
|
@click=${this._refreshErrorLog}
|
||||||
.label=${this.hass.localize("ui.common.refresh")}
|
.label=${this.hass!.localize("ui.common.refresh")}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
<div class="card-content error-log">${this._errorHTML}</div>
|
<div class="card-content error-log">${this._errorHTML}</div>
|
||||||
</ha-card>
|
</ha-card>
|
||||||
@@ -38,7 +38,6 @@ class ErrorLogCard extends LitElement {
|
|||||||
super.firstUpdated(changedProps);
|
super.firstUpdated(changedProps);
|
||||||
|
|
||||||
if (this.hass?.config.safe_mode) {
|
if (this.hass?.config.safe_mode) {
|
||||||
this.hass.loadFragmentTranslation("config");
|
|
||||||
this._refreshErrorLog();
|
this._refreshErrorLog();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -32,7 +32,6 @@ import "../../../components/ha-card";
|
|||||||
import "../../../components/ha-fab";
|
import "../../../components/ha-fab";
|
||||||
import "../../../components/ha-icon-button";
|
import "../../../components/ha-icon-button";
|
||||||
import "../../../components/ha-icon-picker";
|
import "../../../components/ha-icon-picker";
|
||||||
import "../../../components/ha-area-picker";
|
|
||||||
import "../../../components/ha-svg-icon";
|
import "../../../components/ha-svg-icon";
|
||||||
import {
|
import {
|
||||||
computeDeviceName,
|
computeDeviceName,
|
||||||
@@ -42,7 +41,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
EntityRegistryEntry,
|
EntityRegistryEntry,
|
||||||
subscribeEntityRegistry,
|
subscribeEntityRegistry,
|
||||||
updateEntityRegistryEntry,
|
|
||||||
} from "../../../data/entity_registry";
|
} from "../../../data/entity_registry";
|
||||||
import {
|
import {
|
||||||
activateScene,
|
activateScene,
|
||||||
@@ -123,22 +121,6 @@ export class HaSceneEditor extends SubscribeMixin(
|
|||||||
|
|
||||||
private _activateContextId?: string;
|
private _activateContextId?: string;
|
||||||
|
|
||||||
@state() private _saving = false;
|
|
||||||
|
|
||||||
// undefined means not set in this session
|
|
||||||
// null means picked nothing.
|
|
||||||
@state() private _updatedAreaId?: string | null;
|
|
||||||
|
|
||||||
// Callback to be called when scene is set.
|
|
||||||
private _scenesSet?: () => void;
|
|
||||||
|
|
||||||
private _getRegistryAreaId = memoizeOne(
|
|
||||||
(entries: EntityRegistryEntry[], entity_id: string) => {
|
|
||||||
const entry = entries.find((ent) => ent.entity_id === entity_id);
|
|
||||||
return entry ? entry.area_id : null;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
private _getEntitiesDevices = memoizeOne(
|
private _getEntitiesDevices = memoizeOne(
|
||||||
(
|
(
|
||||||
entities: string[],
|
entities: string[],
|
||||||
@@ -305,16 +287,6 @@ export class HaSceneEditor extends SubscribeMixin(
|
|||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
>
|
>
|
||||||
</ha-icon-picker>
|
</ha-icon-picker>
|
||||||
<ha-area-picker
|
|
||||||
.hass=${this.hass}
|
|
||||||
.label=${this.hass.localize(
|
|
||||||
"ui.panel.config.scene.editor.area"
|
|
||||||
)}
|
|
||||||
.name=${"area"}
|
|
||||||
.value=${this._sceneAreaIdWithUpdates || ""}
|
|
||||||
@value-changed=${this._areaChanged}
|
|
||||||
>
|
|
||||||
</ha-area-picker>
|
|
||||||
</div>
|
</div>
|
||||||
</ha-card>
|
</ha-card>
|
||||||
</ha-config-section>
|
</ha-config-section>
|
||||||
@@ -472,9 +444,8 @@ export class HaSceneEditor extends SubscribeMixin(
|
|||||||
slot="fab"
|
slot="fab"
|
||||||
.label=${this.hass.localize("ui.panel.config.scene.editor.save")}
|
.label=${this.hass.localize("ui.panel.config.scene.editor.save")}
|
||||||
extended
|
extended
|
||||||
.disabled=${this._saving}
|
|
||||||
@click=${this._saveScene}
|
@click=${this._saveScene}
|
||||||
class=${classMap({ dirty: this._dirty, saving: this._saving })}
|
class=${classMap({ dirty: this._dirty })}
|
||||||
>
|
>
|
||||||
<ha-svg-icon slot="icon" .path=${mdiContentSave}></ha-svg-icon>
|
<ha-svg-icon slot="icon" .path=${mdiContentSave}></ha-svg-icon>
|
||||||
</ha-fab>
|
</ha-fab>
|
||||||
@@ -503,15 +474,12 @@ export class HaSceneEditor extends SubscribeMixin(
|
|||||||
this._config = {
|
this._config = {
|
||||||
name: this.hass.localize("ui.panel.config.scene.editor.default_name"),
|
name: this.hass.localize("ui.panel.config.scene.editor.default_name"),
|
||||||
entities: {},
|
entities: {},
|
||||||
...initData?.config,
|
...initData,
|
||||||
};
|
};
|
||||||
this._initEntities(this._config);
|
this._initEntities(this._config);
|
||||||
if (initData?.areaId) {
|
if (initData) {
|
||||||
this._updatedAreaId = initData.areaId;
|
this._dirty = true;
|
||||||
}
|
}
|
||||||
this._dirty =
|
|
||||||
initData !== undefined &&
|
|
||||||
(initData.areaId !== undefined || initData.config !== undefined);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changedProps.has("_entityRegistryEntries")) {
|
if (changedProps.has("_entityRegistryEntries")) {
|
||||||
@@ -546,9 +514,6 @@ export class HaSceneEditor extends SubscribeMixin(
|
|||||||
) {
|
) {
|
||||||
this._setScene();
|
this._setScene();
|
||||||
}
|
}
|
||||||
if (this._scenesSet && changedProps.has("scenes")) {
|
|
||||||
this._scenesSet();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _handleMenuAction(ev: CustomEvent<ActionDetail>) {
|
private async _handleMenuAction(ev: CustomEvent<ActionDetail>) {
|
||||||
@@ -724,21 +689,6 @@ export class HaSceneEditor extends SubscribeMixin(
|
|||||||
this._dirty = true;
|
this._dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _areaChanged(ev: CustomEvent) {
|
|
||||||
const newValue = ev.detail.value === "" ? null : ev.detail.value;
|
|
||||||
|
|
||||||
if (newValue === (this._sceneAreaIdWithUpdates || "")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newValue === this._sceneAreaIdCurrent) {
|
|
||||||
this._updatedAreaId = undefined;
|
|
||||||
} else {
|
|
||||||
this._updatedAreaId = newValue;
|
|
||||||
this._dirty = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _stateChanged(event: HassEvent) {
|
private _stateChanged(event: HassEvent) {
|
||||||
if (
|
if (
|
||||||
event.context.id !== this._activateContextId &&
|
event.context.id !== this._activateContextId &&
|
||||||
@@ -799,16 +749,13 @@ export class HaSceneEditor extends SubscribeMixin(
|
|||||||
// Wait for dialog to complete closing
|
// Wait for dialog to complete closing
|
||||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||||
}
|
}
|
||||||
showSceneEditor(
|
showSceneEditor({
|
||||||
{
|
...this._config,
|
||||||
...this._config,
|
id: undefined,
|
||||||
id: undefined,
|
name: `${this._config?.name} (${this.hass.localize(
|
||||||
name: `${this._config?.name} (${this.hass.localize(
|
"ui.panel.config.scene.picker.duplicate"
|
||||||
"ui.panel.config.scene.picker.duplicate"
|
)})`,
|
||||||
)})`,
|
});
|
||||||
},
|
|
||||||
this._sceneAreaIdCurrent || undefined
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _calculateStates(): SceneEntities {
|
private _calculateStates(): SceneEntities {
|
||||||
@@ -845,41 +792,7 @@ export class HaSceneEditor extends SubscribeMixin(
|
|||||||
const id = !this.sceneId ? "" + Date.now() : this.sceneId!;
|
const id = !this.sceneId ? "" + Date.now() : this.sceneId!;
|
||||||
this._config = { ...this._config!, entities: this._calculateStates() };
|
this._config = { ...this._config!, entities: this._calculateStates() };
|
||||||
try {
|
try {
|
||||||
this._saving = true;
|
|
||||||
await saveScene(this.hass, id, this._config);
|
await saveScene(this.hass, id, this._config);
|
||||||
|
|
||||||
if (this._updatedAreaId !== undefined) {
|
|
||||||
let scene =
|
|
||||||
this._scene ||
|
|
||||||
this.scenes.find(
|
|
||||||
(entity: SceneEntity) => entity.attributes.id === id
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!scene) {
|
|
||||||
try {
|
|
||||||
await new Promise<void>((resolve, reject) => {
|
|
||||||
setTimeout(reject, 3000);
|
|
||||||
this._scenesSet = resolve;
|
|
||||||
});
|
|
||||||
scene = this.scenes.find(
|
|
||||||
(entity: SceneEntity) => entity.attributes.id === id
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
// We do nothing.
|
|
||||||
} finally {
|
|
||||||
this._scenesSet = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (scene) {
|
|
||||||
await updateEntityRegistryEntry(this.hass, scene.entity_id, {
|
|
||||||
area_id: this._updatedAreaId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this._updatedAreaId = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._dirty = false;
|
this._dirty = false;
|
||||||
|
|
||||||
if (!this.sceneId) {
|
if (!this.sceneId) {
|
||||||
@@ -891,8 +804,6 @@ export class HaSceneEditor extends SubscribeMixin(
|
|||||||
message: err.body.message || err.message,
|
message: err.body.message || err.message,
|
||||||
});
|
});
|
||||||
throw err;
|
throw err;
|
||||||
} finally {
|
|
||||||
this._saving = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -900,21 +811,6 @@ export class HaSceneEditor extends SubscribeMixin(
|
|||||||
this._saveScene();
|
this._saveScene();
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _sceneAreaIdWithUpdates(): string | undefined | null {
|
|
||||||
return this._updatedAreaId !== undefined
|
|
||||||
? this._updatedAreaId
|
|
||||||
: this._sceneAreaIdCurrent;
|
|
||||||
}
|
|
||||||
|
|
||||||
private get _sceneAreaIdCurrent(): string | undefined | null {
|
|
||||||
return this._scene
|
|
||||||
? this._getRegistryAreaId(
|
|
||||||
this._entityRegistryEntries,
|
|
||||||
this._scene.entity_id
|
|
||||||
)
|
|
||||||
: undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
haStyle,
|
haStyle,
|
||||||
@@ -981,9 +877,6 @@ export class HaSceneEditor extends SubscribeMixin(
|
|||||||
ha-fab.dirty {
|
ha-fab.dirty {
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
}
|
}
|
||||||
ha-fab.saving {
|
|
||||||
opacity: var(--light-disabled-opacity);
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
@@ -7,7 +7,6 @@ import { fireEvent } from "../../../common/dom/fire_event";
|
|||||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||||
import "../../../components/data-table/ha-data-table";
|
import "../../../components/data-table/ha-data-table";
|
||||||
import type { DataTableColumnContainer } from "../../../components/data-table/ha-data-table";
|
import type { DataTableColumnContainer } from "../../../components/data-table/ha-data-table";
|
||||||
import { subscribeEntityRegistry } from "../../../data/entity_registry";
|
|
||||||
import {
|
import {
|
||||||
clearStatistics,
|
clearStatistics,
|
||||||
getStatisticIds,
|
getStatisticIds,
|
||||||
@@ -19,7 +18,6 @@ import {
|
|||||||
showAlertDialog,
|
showAlertDialog,
|
||||||
showConfirmationDialog,
|
showConfirmationDialog,
|
||||||
} from "../../../dialogs/generic/show-dialog-box";
|
} from "../../../dialogs/generic/show-dialog-box";
|
||||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
|
||||||
import { haStyle } from "../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { showFixStatisticsUnitsChangedDialog } from "./show-dialog-statistics-fix-units-changed";
|
import { showFixStatisticsUnitsChangedDialog } from "./show-dialog-statistics-fix-units-changed";
|
||||||
@@ -35,7 +33,7 @@ const FIX_ISSUES_ORDER = {
|
|||||||
unsupported_unit_metadata: 5,
|
unsupported_unit_metadata: 5,
|
||||||
};
|
};
|
||||||
@customElement("developer-tools-statistics")
|
@customElement("developer-tools-statistics")
|
||||||
class HaPanelDevStatistics extends SubscribeMixin(LitElement) {
|
class HaPanelDevStatistics extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ type: Boolean }) public narrow!: boolean;
|
@property({ type: Boolean }) public narrow!: boolean;
|
||||||
@@ -45,8 +43,6 @@ class HaPanelDevStatistics extends SubscribeMixin(LitElement) {
|
|||||||
state?: HassEntity;
|
state?: HassEntity;
|
||||||
})[] = [] as StatisticsMetaData[];
|
})[] = [] as StatisticsMetaData[];
|
||||||
|
|
||||||
private _disabledEntities = new Set<string>();
|
|
||||||
|
|
||||||
protected firstUpdated() {
|
protected firstUpdated() {
|
||||||
this._validateStatistics();
|
this._validateStatistics();
|
||||||
}
|
}
|
||||||
@@ -134,25 +130,6 @@ class HaPanelDevStatistics extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public hassSubscribe(): UnsubscribeFunc[] {
|
|
||||||
return [
|
|
||||||
subscribeEntityRegistry(this.hass.connection!, (entities) => {
|
|
||||||
const disabledEntities = new Set<string>();
|
|
||||||
for (const confEnt of entities) {
|
|
||||||
if (!confEnt.disabled_by) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
disabledEntities.add(confEnt.entity_id);
|
|
||||||
}
|
|
||||||
// If the disabled entities changed, re-validate the statistics
|
|
||||||
if (disabledEntities !== this._disabledEntities) {
|
|
||||||
this._disabledEntities = disabledEntities;
|
|
||||||
this._validateStatistics();
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _validateStatistics() {
|
private async _validateStatistics() {
|
||||||
const [statisticIds, issues] = await Promise.all([
|
const [statisticIds, issues] = await Promise.all([
|
||||||
getStatisticIds(this.hass),
|
getStatisticIds(this.hass),
|
||||||
@@ -161,24 +138,17 @@ class HaPanelDevStatistics extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
const statsIds = new Set();
|
const statsIds = new Set();
|
||||||
|
|
||||||
this._data = statisticIds
|
this._data = statisticIds.map((statistic) => {
|
||||||
.filter(
|
statsIds.add(statistic.statistic_id);
|
||||||
(statistic) => !this._disabledEntities.has(statistic.statistic_id)
|
return {
|
||||||
)
|
...statistic,
|
||||||
.map((statistic) => {
|
state: this.hass.states[statistic.statistic_id],
|
||||||
statsIds.add(statistic.statistic_id);
|
issues: issues[statistic.statistic_id],
|
||||||
return {
|
};
|
||||||
...statistic,
|
});
|
||||||
state: this.hass.states[statistic.statistic_id],
|
|
||||||
issues: issues[statistic.statistic_id],
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.keys(issues).forEach((statisticId) => {
|
Object.keys(issues).forEach((statisticId) => {
|
||||||
if (
|
if (!statsIds.has(statisticId)) {
|
||||||
!statsIds.has(statisticId) &&
|
|
||||||
!this._disabledEntities.has(statisticId)
|
|
||||||
) {
|
|
||||||
this._data.push({
|
this._data.push({
|
||||||
statistic_id: statisticId,
|
statistic_id: statisticId,
|
||||||
unit_of_measurement: "",
|
unit_of_measurement: "",
|
||||||
|
@@ -13,7 +13,10 @@ import {
|
|||||||
energySourcesByType,
|
energySourcesByType,
|
||||||
getEnergyDataCollection,
|
getEnergyDataCollection,
|
||||||
} from "../../../../data/energy";
|
} from "../../../../data/energy";
|
||||||
import { calculateStatisticsSumGrowth } from "../../../../data/history";
|
import {
|
||||||
|
calculateStatisticsSumGrowth,
|
||||||
|
calculateStatisticsSumGrowthWithPercentage,
|
||||||
|
} from "../../../../data/history";
|
||||||
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||||
import type { HomeAssistant } from "../../../../types";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
import { createEntityNotFoundWarning } from "../../components/hui-warning";
|
import { createEntityNotFoundWarning } from "../../components/hui-warning";
|
||||||
@@ -87,13 +90,19 @@ class HuiEnergyCarbonGaugeCard
|
|||||||
value = 100;
|
value = 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._data.fossilEnergyConsumption && totalGridConsumption) {
|
if (
|
||||||
const highCarbonEnergy = this._data.fossilEnergyConsumption
|
this._data.co2SignalEntity in this._data.stats &&
|
||||||
? Object.values(this._data.fossilEnergyConsumption).reduce(
|
totalGridConsumption
|
||||||
(sum, a) => sum + a,
|
) {
|
||||||
0
|
const highCarbonEnergy =
|
||||||
)
|
calculateStatisticsSumGrowthWithPercentage(
|
||||||
: 0;
|
this._data.stats[this._data.co2SignalEntity],
|
||||||
|
types
|
||||||
|
.grid![0].flow_from.map(
|
||||||
|
(flow) => this._data!.stats![flow.stat_energy_from]
|
||||||
|
)
|
||||||
|
.filter(Boolean)
|
||||||
|
) || 0;
|
||||||
|
|
||||||
const totalSolarProduction = types.solar
|
const totalSolarProduction = types.solar
|
||||||
? calculateStatisticsSumGrowth(
|
? calculateStatisticsSumGrowth(
|
||||||
|
@@ -6,7 +6,7 @@ import {
|
|||||||
ScatterDataPoint,
|
ScatterDataPoint,
|
||||||
} from "chart.js";
|
} from "chart.js";
|
||||||
import { getRelativePosition } from "chart.js/helpers";
|
import { getRelativePosition } from "chart.js/helpers";
|
||||||
import { addHours, differenceInDays } from "date-fns";
|
import { addHours } from "date-fns";
|
||||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
@@ -155,19 +155,13 @@ export class HuiEnergyDevicesGraphCard
|
|||||||
);
|
);
|
||||||
|
|
||||||
private async _getStatistics(energyData: EnergyData): Promise<void> {
|
private async _getStatistics(energyData: EnergyData): Promise<void> {
|
||||||
const dayDifference = differenceInDays(
|
|
||||||
energyData.end || new Date(),
|
|
||||||
energyData.start
|
|
||||||
);
|
|
||||||
|
|
||||||
this._data = await fetchStatistics(
|
this._data = await fetchStatistics(
|
||||||
this.hass,
|
this.hass,
|
||||||
addHours(energyData.start, -1),
|
addHours(energyData.start, -1),
|
||||||
energyData.end,
|
energyData.end,
|
||||||
energyData.prefs.device_consumption.map(
|
energyData.prefs.device_consumption.map(
|
||||||
(device) => device.stat_consumption
|
(device) => device.stat_consumption
|
||||||
),
|
)
|
||||||
dayDifference > 35 ? "month" : dayDifference > 2 ? "day" : "hour"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const data: Array<ChartDataset<"bar", ParsedDataType<"bar">>["data"]> = [];
|
const data: Array<ChartDataset<"bar", ParsedDataType<"bar">>["data"]> = [];
|
||||||
|
@@ -24,7 +24,10 @@ import {
|
|||||||
getEnergyDataCollection,
|
getEnergyDataCollection,
|
||||||
getEnergyGasUnit,
|
getEnergyGasUnit,
|
||||||
} from "../../../../data/energy";
|
} from "../../../../data/energy";
|
||||||
import { calculateStatisticsSumGrowth } from "../../../../data/history";
|
import {
|
||||||
|
calculateStatisticsSumGrowth,
|
||||||
|
calculateStatisticsSumGrowthWithPercentage,
|
||||||
|
} from "../../../../data/history";
|
||||||
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||||
import { HomeAssistant } from "../../../../types";
|
import { HomeAssistant } from "../../../../types";
|
||||||
import { LovelaceCard } from "../../types";
|
import { LovelaceCard } from "../../types";
|
||||||
@@ -206,11 +209,19 @@ class HuiEnergyDistrubutionCard
|
|||||||
// This fallback is used in the demo
|
// This fallback is used in the demo
|
||||||
let electricityMapUrl = "https://www.electricitymap.org";
|
let electricityMapUrl = "https://www.electricitymap.org";
|
||||||
|
|
||||||
if (this._data.co2SignalEntity && this._data.fossilEnergyConsumption) {
|
if (
|
||||||
|
this._data.co2SignalEntity &&
|
||||||
|
this._data.co2SignalEntity in this._data.stats
|
||||||
|
) {
|
||||||
// Calculate high carbon consumption
|
// Calculate high carbon consumption
|
||||||
const highCarbonEnergy = Object.values(
|
const highCarbonEnergy = calculateStatisticsSumGrowthWithPercentage(
|
||||||
this._data.fossilEnergyConsumption
|
this._data.stats[this._data.co2SignalEntity],
|
||||||
).reduce((sum, a) => sum + a, 0);
|
types
|
||||||
|
.grid![0].flow_from.map(
|
||||||
|
(flow) => this._data!.stats[flow.stat_energy_from]
|
||||||
|
)
|
||||||
|
.filter(Boolean)
|
||||||
|
);
|
||||||
|
|
||||||
const co2State = this.hass.states[this._data.co2SignalEntity];
|
const co2State = this.hass.states[this._data.co2SignalEntity];
|
||||||
|
|
||||||
|
@@ -41,6 +41,10 @@ import {
|
|||||||
} from "../../../../common/number/format_number";
|
} from "../../../../common/number/format_number";
|
||||||
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||||
import { FrontendLocaleData } from "../../../../data/translation";
|
import { FrontendLocaleData } from "../../../../data/translation";
|
||||||
|
import {
|
||||||
|
reduceSumStatisticsByMonth,
|
||||||
|
reduceSumStatisticsByDay,
|
||||||
|
} from "../../../../data/history";
|
||||||
import { formatTime } from "../../../../common/datetime/format_time";
|
import { formatTime } from "../../../../common/datetime/format_time";
|
||||||
|
|
||||||
@customElement("hui-energy-gas-graph-card")
|
@customElement("hui-energy-gas-graph-card")
|
||||||
@@ -243,6 +247,11 @@ export class HuiEnergyGasGraphCard
|
|||||||
.getPropertyValue("--energy-gas-color")
|
.getPropertyValue("--energy-gas-color")
|
||||||
.trim();
|
.trim();
|
||||||
|
|
||||||
|
const dayDifference = differenceInDays(
|
||||||
|
energyData.end || new Date(),
|
||||||
|
energyData.start
|
||||||
|
);
|
||||||
|
|
||||||
gasSources.forEach((source, idx) => {
|
gasSources.forEach((source, idx) => {
|
||||||
const data: ChartDataset<"bar" | "line">[] = [];
|
const data: ChartDataset<"bar" | "line">[] = [];
|
||||||
const entity = this.hass.states[source.stat_energy_from];
|
const entity = this.hass.states[source.stat_energy_from];
|
||||||
@@ -259,7 +268,16 @@ export class HuiEnergyGasGraphCard
|
|||||||
|
|
||||||
// Process gas consumption data.
|
// Process gas consumption data.
|
||||||
if (source.stat_energy_from in energyData.stats) {
|
if (source.stat_energy_from in energyData.stats) {
|
||||||
const stats = energyData.stats[source.stat_energy_from];
|
const stats =
|
||||||
|
dayDifference > 35
|
||||||
|
? reduceSumStatisticsByMonth(
|
||||||
|
energyData.stats[source.stat_energy_from]
|
||||||
|
)
|
||||||
|
: dayDifference > 2
|
||||||
|
? reduceSumStatisticsByDay(
|
||||||
|
energyData.stats[source.stat_energy_from]
|
||||||
|
)
|
||||||
|
: energyData.stats[source.stat_energy_from];
|
||||||
|
|
||||||
for (const point of stats) {
|
for (const point of stats) {
|
||||||
if (point.sum === null) {
|
if (point.sum === null) {
|
||||||
|
@@ -42,6 +42,10 @@ import {
|
|||||||
} from "../../../../common/number/format_number";
|
} from "../../../../common/number/format_number";
|
||||||
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||||
import { FrontendLocaleData } from "../../../../data/translation";
|
import { FrontendLocaleData } from "../../../../data/translation";
|
||||||
|
import {
|
||||||
|
reduceSumStatisticsByMonth,
|
||||||
|
reduceSumStatisticsByDay,
|
||||||
|
} from "../../../../data/history";
|
||||||
import { formatTime } from "../../../../common/datetime/format_time";
|
import { formatTime } from "../../../../common/datetime/format_time";
|
||||||
|
|
||||||
@customElement("hui-energy-solar-graph-card")
|
@customElement("hui-energy-solar-graph-card")
|
||||||
@@ -270,7 +274,16 @@ export class HuiEnergySolarGraphCard
|
|||||||
|
|
||||||
// Process solar production data.
|
// Process solar production data.
|
||||||
if (source.stat_energy_from in energyData.stats) {
|
if (source.stat_energy_from in energyData.stats) {
|
||||||
const stats = energyData.stats[source.stat_energy_from];
|
const stats =
|
||||||
|
dayDifference > 35
|
||||||
|
? reduceSumStatisticsByMonth(
|
||||||
|
energyData.stats[source.stat_energy_from]
|
||||||
|
)
|
||||||
|
: dayDifference > 2
|
||||||
|
? reduceSumStatisticsByDay(
|
||||||
|
energyData.stats[source.stat_energy_from]
|
||||||
|
)
|
||||||
|
: energyData.stats[source.stat_energy_from];
|
||||||
|
|
||||||
for (const point of stats) {
|
for (const point of stats) {
|
||||||
if (point.sum === null) {
|
if (point.sum === null) {
|
||||||
|
@@ -27,6 +27,10 @@ import {
|
|||||||
import "../../../../components/chart/ha-chart-base";
|
import "../../../../components/chart/ha-chart-base";
|
||||||
import "../../../../components/ha-card";
|
import "../../../../components/ha-card";
|
||||||
import { EnergyData, getEnergyDataCollection } from "../../../../data/energy";
|
import { EnergyData, getEnergyDataCollection } from "../../../../data/energy";
|
||||||
|
import {
|
||||||
|
reduceSumStatisticsByDay,
|
||||||
|
reduceSumStatisticsByMonth,
|
||||||
|
} from "../../../../data/history";
|
||||||
import { FrontendLocaleData } from "../../../../data/translation";
|
import { FrontendLocaleData } from "../../../../data/translation";
|
||||||
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||||
import { HomeAssistant } from "../../../../types";
|
import { HomeAssistant } from "../../../../types";
|
||||||
@@ -294,6 +298,11 @@ export class HuiEnergyUsageGraphCard
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dayDifference = differenceInDays(
|
||||||
|
energyData.end || new Date(),
|
||||||
|
energyData.start
|
||||||
|
);
|
||||||
|
|
||||||
this._start = energyData.start;
|
this._start = energyData.start;
|
||||||
this._end = energyData.end || endOfToday();
|
this._end = energyData.end || endOfToday();
|
||||||
|
|
||||||
@@ -359,7 +368,12 @@ export class HuiEnergyUsageGraphCard
|
|||||||
const totalStats: { [start: string]: number } = {};
|
const totalStats: { [start: string]: number } = {};
|
||||||
const sets: { [statId: string]: { [start: string]: number } } = {};
|
const sets: { [statId: string]: { [start: string]: number } } = {};
|
||||||
statIds!.forEach((id) => {
|
statIds!.forEach((id) => {
|
||||||
const stats = energyData.stats[id];
|
const stats =
|
||||||
|
dayDifference > 35
|
||||||
|
? reduceSumStatisticsByMonth(energyData.stats[id])
|
||||||
|
: dayDifference > 2
|
||||||
|
? reduceSumStatisticsByDay(energyData.stats[id])
|
||||||
|
: energyData.stats[id];
|
||||||
if (!stats) {
|
if (!stats) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@@ -274,7 +274,7 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
|
|||||||
|
|
||||||
ha-chip {
|
ha-chip {
|
||||||
--ha-chip-background-color: var(--alarm-state-color);
|
--ha-chip-background-color: var(--alarm-state-color);
|
||||||
--primary-text-color: var(--text-primary-color);
|
--ha-chip-text-color: var(--text-primary-color);
|
||||||
line-height: initial;
|
line-height: initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -289,8 +289,7 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
|
|||||||
|
|
||||||
private renderEntity(entityConf: LovelaceRowConfig): TemplateResult {
|
private renderEntity(entityConf: LovelaceRowConfig): TemplateResult {
|
||||||
const element = createRowElement(
|
const element = createRowElement(
|
||||||
(!("type" in entityConf) || entityConf.type === "conditional") &&
|
!("type" in entityConf) && this._config!.state_color
|
||||||
this._config!.state_color
|
|
||||||
? ({
|
? ({
|
||||||
state_color: true,
|
state_color: true,
|
||||||
...(entityConf as EntityConfig),
|
...(entityConf as EntityConfig),
|
||||||
|
@@ -87,8 +87,7 @@ const splitByAreas = (
|
|||||||
|
|
||||||
export const computeCards = (
|
export const computeCards = (
|
||||||
states: Array<[string, HassEntity?]>,
|
states: Array<[string, HassEntity?]>,
|
||||||
entityCardOptions: Partial<EntitiesCardConfig>,
|
entityCardOptions: Partial<EntitiesCardConfig>
|
||||||
renderFooterEntities = true
|
|
||||||
): LovelaceCardConfig[] => {
|
): LovelaceCardConfig[] => {
|
||||||
const cards: LovelaceCardConfig[] = [];
|
const cards: LovelaceCardConfig[] = [];
|
||||||
|
|
||||||
@@ -147,28 +146,12 @@ export const computeCards = (
|
|||||||
show_forecast: false,
|
show_forecast: false,
|
||||||
};
|
};
|
||||||
cards.push(cardConfig);
|
cards.push(cardConfig);
|
||||||
} else if (
|
} else if (domain === "scene" || domain === "script") {
|
||||||
renderFooterEntities &&
|
footerEntities.push({
|
||||||
(domain === "scene" || domain === "script")
|
|
||||||
) {
|
|
||||||
const conf: typeof footerEntities[0] = {
|
|
||||||
entity: entityId,
|
entity: entityId,
|
||||||
show_icon: true,
|
show_icon: true,
|
||||||
show_name: true,
|
show_name: true,
|
||||||
};
|
});
|
||||||
let name: string | undefined;
|
|
||||||
if (
|
|
||||||
titlePrefix &&
|
|
||||||
stateObj &&
|
|
||||||
// eslint-disable-next-line no-cond-assign
|
|
||||||
(name = stripPrefixFromEntityName(
|
|
||||||
computeStateName(stateObj),
|
|
||||||
titlePrefix
|
|
||||||
))
|
|
||||||
) {
|
|
||||||
conf.name = name;
|
|
||||||
}
|
|
||||||
footerEntities.push(conf);
|
|
||||||
} else if (
|
} else if (
|
||||||
domain === "sensor" &&
|
domain === "sensor" &&
|
||||||
stateObj?.attributes.device_class === SENSOR_DEVICE_CLASS_BATTERY
|
stateObj?.attributes.device_class === SENSOR_DEVICE_CLASS_BATTERY
|
||||||
@@ -194,12 +177,6 @@ export const computeCards = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we ended up with footer entities but no normal entities,
|
|
||||||
// render the footer entities as normal entities.
|
|
||||||
if (entities.length === 0 && footerEntities.length > 0) {
|
|
||||||
return computeCards(states, entityCardOptions, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entities.length > 0 || footerEntities.length > 0) {
|
if (entities.length > 0 || footerEntities.length > 0) {
|
||||||
const card: EntitiesCardConfig = {
|
const card: EntitiesCardConfig = {
|
||||||
type: "entities",
|
type: "entities",
|
||||||
@@ -448,9 +425,7 @@ export const generateDefaultViewConfig = (
|
|||||||
|
|
||||||
if (grid && grid.flow_from.length > 0) {
|
if (grid && grid.flow_from.length > 0) {
|
||||||
areaCards.push({
|
areaCards.push({
|
||||||
title: localize(
|
title: "Energy distribution today",
|
||||||
"ui.panel.lovelace.cards.energy.energy_distribution.title_today"
|
|
||||||
),
|
|
||||||
type: "energy-distribution",
|
type: "energy-distribution",
|
||||||
link_dashboard: true,
|
link_dashboard: true,
|
||||||
});
|
});
|
||||||
|
@@ -65,8 +65,6 @@ export class HuiButtonsBase extends LitElement {
|
|||||||
:host {
|
:host {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-evenly;
|
justify-content: space-evenly;
|
||||||
flex-wrap: wrap;
|
|
||||||
padding: 0 8px;
|
|
||||||
}
|
}
|
||||||
div {
|
div {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@@ -9,8 +9,8 @@ import {
|
|||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import "../../../components/ha-cover-controls";
|
import "../../../components/ha-cover-controls";
|
||||||
import "../../../components/ha-cover-tilt-controls";
|
import "../../../components/ha-cover-tilt-controls";
|
||||||
import { CoverEntity, isTiltOnly } from "../../../data/cover";
|
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
|
import { isTiltOnly } from "../../../util/cover-model";
|
||||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||||
import "../components/hui-generic-entity-row";
|
import "../components/hui-generic-entity-row";
|
||||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||||
@@ -38,7 +38,7 @@ class HuiCoverEntityRow extends LitElement implements LovelaceRow {
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
const stateObj = this.hass.states[this._config.entity] as CoverEntity;
|
const stateObj = this.hass.states[this._config.entity];
|
||||||
|
|
||||||
if (!stateObj) {
|
if (!stateObj) {
|
||||||
return html`
|
return html`
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import { html, LitElement, TemplateResult } from "lit";
|
import { html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { processConfigEntities } from "../common/process-config-entities";
|
import { processConfigEntities } from "../common/process-config-entities";
|
||||||
import "../components/hui-buttons-base";
|
import "../components/hui-buttons-base";
|
||||||
@@ -27,21 +26,11 @@ export class HuiButtonsHeaderFooter
|
|||||||
|
|
||||||
public setConfig(config: ButtonsHeaderFooterConfig): void {
|
public setConfig(config: ButtonsHeaderFooterConfig): void {
|
||||||
this._configEntities = processConfigEntities(config.entities).map(
|
this._configEntities = processConfigEntities(config.entities).map(
|
||||||
(entityConfig) => {
|
(entityConfig) => ({
|
||||||
const conf = {
|
tap_action: { action: "toggle" },
|
||||||
tap_action: { action: "toggle" },
|
hold_action: { action: "more-info" },
|
||||||
hold_action: { action: "more-info" },
|
...entityConfig,
|
||||||
...entityConfig,
|
})
|
||||||
};
|
|
||||||
if (computeDomain(entityConfig.entity) === "scene") {
|
|
||||||
conf.tap_action = {
|
|
||||||
action: "call-service",
|
|
||||||
service: "scene.turn_on",
|
|
||||||
target: { entity_id: conf.entity },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return conf;
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,12 +1,7 @@
|
|||||||
import { customElement } from "lit/decorators";
|
import { customElement } from "lit/decorators";
|
||||||
import { EntityCardConfig } from "../cards/types";
|
|
||||||
import { HuiConditionalBase } from "../components/hui-conditional-base";
|
import { HuiConditionalBase } from "../components/hui-conditional-base";
|
||||||
import { createRowElement } from "../create-element/create-row-element";
|
import { createRowElement } from "../create-element/create-row-element";
|
||||||
import {
|
import { ConditionalRowConfig, LovelaceRow } from "../entity-rows/types";
|
||||||
ConditionalRowConfig,
|
|
||||||
EntityConfig,
|
|
||||||
LovelaceRow,
|
|
||||||
} from "../entity-rows/types";
|
|
||||||
|
|
||||||
@customElement("hui-conditional-row")
|
@customElement("hui-conditional-row")
|
||||||
class HuiConditionalRow extends HuiConditionalBase implements LovelaceRow {
|
class HuiConditionalRow extends HuiConditionalBase implements LovelaceRow {
|
||||||
@@ -17,14 +12,7 @@ class HuiConditionalRow extends HuiConditionalBase implements LovelaceRow {
|
|||||||
throw new Error("No row configured");
|
throw new Error("No row configured");
|
||||||
}
|
}
|
||||||
|
|
||||||
this._element = createRowElement(
|
this._element = createRowElement(config.row) as LovelaceRow;
|
||||||
(config as EntityCardConfig).state_color
|
|
||||||
? ({
|
|
||||||
state_color: true,
|
|
||||||
...(config.row as EntityConfig),
|
|
||||||
} as EntityConfig)
|
|
||||||
: config.row
|
|
||||||
) as LovelaceRow;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
68
src/state-summary/state-card-cover.js
Normal file
68
src/state-summary/state-card-cover.js
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
||||||
|
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||||
|
/* eslint-plugin-disable lit */
|
||||||
|
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||||
|
import "../components/entity/state-info";
|
||||||
|
import "../components/ha-cover-controls";
|
||||||
|
import "../components/ha-cover-tilt-controls";
|
||||||
|
import CoverEntity from "../util/cover-model";
|
||||||
|
|
||||||
|
class StateCardCover extends PolymerElement {
|
||||||
|
static get template() {
|
||||||
|
return html`
|
||||||
|
<style include="iron-flex iron-flex-alignment"></style>
|
||||||
|
<style>
|
||||||
|
:host {
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="horizontal justified layout">
|
||||||
|
${this.stateInfoTemplate}
|
||||||
|
<div class="horizontal layout">
|
||||||
|
<ha-cover-controls
|
||||||
|
hidden$="[[entityObj.isTiltOnly]]"
|
||||||
|
hass="[[hass]]"
|
||||||
|
state-obj="[[stateObj]]"
|
||||||
|
></ha-cover-controls>
|
||||||
|
<ha-cover-tilt-controls
|
||||||
|
hidden$="[[!entityObj.isTiltOnly]]"
|
||||||
|
hass="[[hass]]"
|
||||||
|
state-obj="[[stateObj]]"
|
||||||
|
></ha-cover-tilt-controls>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get stateInfoTemplate() {
|
||||||
|
return html`
|
||||||
|
<state-info
|
||||||
|
hass="[[hass]]"
|
||||||
|
state-obj="[[stateObj]]"
|
||||||
|
in-dialog="[[inDialog]]"
|
||||||
|
></state-info>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
hass: Object,
|
||||||
|
stateObj: Object,
|
||||||
|
inDialog: {
|
||||||
|
type: Boolean,
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
entityObj: {
|
||||||
|
type: Object,
|
||||||
|
computed: "computeEntityObj(hass, stateObj)",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
computeEntityObj(hass, stateObj) {
|
||||||
|
const entity = new CoverEntity(hass, stateObj);
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
customElements.define("state-card-cover", StateCardCover);
|
@@ -1,56 +0,0 @@
|
|||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
|
||||||
import { customElement, property } from "lit/decorators";
|
|
||||||
import "../components/entity/state-info";
|
|
||||||
import "../components/ha-cover-controls";
|
|
||||||
import "../components/ha-cover-tilt-controls";
|
|
||||||
import { CoverEntity, isTiltOnly } from "../data/cover";
|
|
||||||
import { haStyle } from "../resources/styles";
|
|
||||||
import { HomeAssistant } from "../types";
|
|
||||||
|
|
||||||
@customElement("state-card-cover")
|
|
||||||
class StateCardCover extends LitElement {
|
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public stateObj!: CoverEntity;
|
|
||||||
|
|
||||||
@property({ type: Boolean }) public inDialog = false;
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
|
||||||
return html`
|
|
||||||
<div class="horizontal justified layout">
|
|
||||||
<state-info
|
|
||||||
.hass=${this.hass}
|
|
||||||
.stateObj=${this.stateObj}
|
|
||||||
.inDialog=${this.inDialog}
|
|
||||||
></state-info>
|
|
||||||
<ha-cover-controls
|
|
||||||
.hass=${this.hass}
|
|
||||||
.hidden=${isTiltOnly(this.stateObj)}
|
|
||||||
.stateObj=${this.stateObj}
|
|
||||||
></ha-cover-controls>
|
|
||||||
<ha-cover-tilt-controls
|
|
||||||
.hass=${this.hass}
|
|
||||||
.hidden=${!isTiltOnly(this.stateObj)}
|
|
||||||
.stateObj=${this.stateObj}
|
|
||||||
></ha-cover-tilt-controls>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return [
|
|
||||||
haStyle,
|
|
||||||
css`
|
|
||||||
:host {
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"state-card-cover": StateCardCover;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -306,11 +306,11 @@
|
|||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
"logbook": {
|
"logbook": {
|
||||||
"entries_not_found": "No logbook events found.",
|
"entries_not_found": "No logbook entries found.",
|
||||||
"by": "by",
|
"by": "by",
|
||||||
"by_service": "by service",
|
"by_service": "by service",
|
||||||
"show_trace": "[%key:ui::panel::config::automation::editor::show_trace%]",
|
"show_trace": "[%key:ui::panel::config::automation::editor::show_trace%]",
|
||||||
"retrieval_error": "Could not load logbook",
|
"retrieval_error": "Error during logbook entry retrieval",
|
||||||
"messages": {
|
"messages": {
|
||||||
"was_away": "was detected away",
|
"was_away": "was detected away",
|
||||||
"was_at_state": "was detected at {state}",
|
"was_at_state": "was detected at {state}",
|
||||||
@@ -356,15 +356,15 @@
|
|||||||
},
|
},
|
||||||
"target-picker": {
|
"target-picker": {
|
||||||
"expand": "Expand",
|
"expand": "Expand",
|
||||||
"expand_area_id": "Split this area into separate devices and entities.",
|
"expand_area_id": "Expand this area into the separate devices and entities that it contains. After expanding, it will not update the devices and entities when the area changes.",
|
||||||
"expand_device_id": "Split this device into separate entities.",
|
"expand_device_id": "Expand this device into the separate entities that it contains. After expanding, it will not update the entities when the device changes.",
|
||||||
"remove": "Remove",
|
"remove": "Remove",
|
||||||
"remove_area_id": "Remove area",
|
"remove_area_id": "Remove area",
|
||||||
"remove_device_id": "Remove device",
|
"remove_device_id": "Remove device",
|
||||||
"remove_entity_id": "Remove entity",
|
"remove_entity_id": "Remove entity",
|
||||||
"add_area_id": "Choose area",
|
"add_area_id": "Pick area",
|
||||||
"add_device_id": "Choose device",
|
"add_device_id": "Pick device",
|
||||||
"add_entity_id": "Choose entity"
|
"add_entity_id": "Pick entity"
|
||||||
},
|
},
|
||||||
"user-picker": {
|
"user-picker": {
|
||||||
"no_user": "No user",
|
"no_user": "No user",
|
||||||
@@ -412,11 +412,11 @@
|
|||||||
"error": {
|
"error": {
|
||||||
"no_supervisor": {
|
"no_supervisor": {
|
||||||
"title": "No Supervisor",
|
"title": "No Supervisor",
|
||||||
"description": "Add-ons are not supported."
|
"description": "No Supervisor found, so add-ons could not be loaded."
|
||||||
},
|
},
|
||||||
"fetch_addons": {
|
"fetch_addons": {
|
||||||
"title": "Error loading add-ons",
|
"title": "Error fetching add-ons",
|
||||||
"description": "There was an error loading add-ons."
|
"description": "Fetching add-ons returned an error."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -694,7 +694,7 @@
|
|||||||
"icon": "Icon",
|
"icon": "Icon",
|
||||||
"icon_error": "Icons should be in the format 'prefix:iconname', e.g. 'mdi:home'",
|
"icon_error": "Icons should be in the format 'prefix:iconname', e.g. 'mdi:home'",
|
||||||
"entity_id": "Entity ID",
|
"entity_id": "Entity ID",
|
||||||
"unavailable": "This entity is unavailable.",
|
"unavailable": "This entity is not currently available.",
|
||||||
"enabled_label": "Enable entity",
|
"enabled_label": "Enable entity",
|
||||||
"enabled_cause": "Disabled by {cause}.",
|
"enabled_cause": "Disabled by {cause}.",
|
||||||
"device_disabled": "The device of this entity is disabled.",
|
"device_disabled": "The device of this entity is disabled.",
|
||||||
@@ -703,7 +703,7 @@
|
|||||||
"enabled_delay_confirm": "The enabled entities will be added to Home Assistant in {delay} seconds",
|
"enabled_delay_confirm": "The enabled entities will be added to Home Assistant in {delay} seconds",
|
||||||
"enabled_restart_confirm": "Restart Home Assistant to finish enabling the entities",
|
"enabled_restart_confirm": "Restart Home Assistant to finish enabling the entities",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"confirm_delete": "Are you sure you want to delete this entity?",
|
"confirm_delete": "Are you sure you want to delete this entry?",
|
||||||
"update": "Update",
|
"update": "Update",
|
||||||
"note": "Note: This might not work yet with all integrations.",
|
"note": "Note: This might not work yet with all integrations.",
|
||||||
"advanced": "Advanced settings",
|
"advanced": "Advanced settings",
|
||||||
@@ -864,8 +864,7 @@
|
|||||||
"key_missing": "Required key ''{key}'' is missing.",
|
"key_missing": "Required key ''{key}'' is missing.",
|
||||||
"key_not_expected": "Key ''{key}'' is not expected or not supported by the visual editor.",
|
"key_not_expected": "Key ''{key}'' is not expected or not supported by the visual editor.",
|
||||||
"key_wrong_type": "The provided value for ''{key}'' is not supported by the visual editor. We support ({type_correct}) but received ({type_wrong}).",
|
"key_wrong_type": "The provided value for ''{key}'' is not supported by the visual editor. We support ({type_correct}) but received ({type_wrong}).",
|
||||||
"no_template_editor_support": "Templates not supported in visual editor",
|
"no_template_editor_support": "Templates not supported in visual editor"
|
||||||
"no_state_array_support": "Multiple state values not supported in visual editor"
|
|
||||||
},
|
},
|
||||||
"supervisor": {
|
"supervisor": {
|
||||||
"title": "Could not load the Supervisor panel!",
|
"title": "Could not load the Supervisor panel!",
|
||||||
@@ -899,6 +898,7 @@
|
|||||||
"dismiss": "Dismiss"
|
"dismiss": "Dismiss"
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
|
"external_app_configuration": "App Configuration",
|
||||||
"sidebar_toggle": "Sidebar Toggle",
|
"sidebar_toggle": "Sidebar Toggle",
|
||||||
"done": "Done",
|
"done": "Done",
|
||||||
"hide_panel": "Hide panel",
|
"hide_panel": "Hide panel",
|
||||||
@@ -927,9 +927,9 @@
|
|||||||
},
|
},
|
||||||
"updates": {
|
"updates": {
|
||||||
"title": "{count} {count, plural,\n one {update}\n other {updates}\n}",
|
"title": "{count} {count, plural,\n one {update}\n other {updates}\n}",
|
||||||
"unable_to_fetch": "Unable to load updates",
|
"unable_to_fetch": "Unable to fetch available updates",
|
||||||
"version_available": "Version {version_available} is available",
|
"version_available": "Version {version_available} is available",
|
||||||
"more_updates": "+ {count} Updates",
|
"show_all_updates": "Show all updates",
|
||||||
"show": "show"
|
"show": "show"
|
||||||
},
|
},
|
||||||
"areas": {
|
"areas": {
|
||||||
@@ -1195,11 +1195,11 @@
|
|||||||
},
|
},
|
||||||
"core": {
|
"core": {
|
||||||
"caption": "General",
|
"caption": "General",
|
||||||
"description": "Location, network and analytics",
|
"description": "Unit system, location, time zone & other general parameters",
|
||||||
"section": {
|
"section": {
|
||||||
"core": {
|
"core": {
|
||||||
"header": "General Configuration",
|
"header": "General Configuration",
|
||||||
"introduction": "Manage your location, network and analytics.",
|
"introduction": "Changing your configuration can be a tiresome process. We know. This section will try to make your life a little bit easier.",
|
||||||
"core_config": {
|
"core_config": {
|
||||||
"edit_requires_storage": "Editor disabled because config stored in configuration.yaml.",
|
"edit_requires_storage": "Editor disabled because config stored in configuration.yaml.",
|
||||||
"location_name": "Name of your Home Assistant installation",
|
"location_name": "Name of your Home Assistant installation",
|
||||||
@@ -1397,7 +1397,7 @@
|
|||||||
},
|
},
|
||||||
"server_management": {
|
"server_management": {
|
||||||
"heading": "Server management",
|
"heading": "Server management",
|
||||||
"introduction": "Control your Home Assistant.",
|
"introduction": "Control your Home Assistant server… from Home Assistant.",
|
||||||
"restart": "Restart",
|
"restart": "Restart",
|
||||||
"confirm_restart": "Are you sure you want to restart Home Assistant?",
|
"confirm_restart": "Are you sure you want to restart Home Assistant?",
|
||||||
"stop": "Stop",
|
"stop": "Stop",
|
||||||
@@ -1900,7 +1900,6 @@
|
|||||||
"unsaved_confirm": "You have unsaved changes. Are you sure you want to leave?",
|
"unsaved_confirm": "You have unsaved changes. Are you sure you want to leave?",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"icon": "Icon",
|
"icon": "Icon",
|
||||||
"area": "Area",
|
|
||||||
"devices": {
|
"devices": {
|
||||||
"header": "Devices",
|
"header": "Devices",
|
||||||
"introduction": "Add the devices that you want to be included in your scene. Set all the devices to the state you want for this scene.",
|
"introduction": "Add the devices that you want to be included in your scene. Set all the devices to the state you want for this scene.",
|
||||||
@@ -2855,18 +2854,11 @@
|
|||||||
},
|
},
|
||||||
"add_node": {
|
"add_node": {
|
||||||
"title": "Add a Z-Wave Device",
|
"title": "Add a Z-Wave Device",
|
||||||
"searching_device": "Searching for device",
|
"cancel_inclusion": "Cancel Inclusion",
|
||||||
|
"controller_in_inclusion_mode": "Your Z-Wave controller is now in inclusion mode.",
|
||||||
"follow_device_instructions": "Follow the directions that came with your device to trigger pairing on the device.",
|
"follow_device_instructions": "Follow the directions that came with your device to trigger pairing on the device.",
|
||||||
"choose_inclusion_strategy": "How do you want to add your device",
|
"inclusion_failed": "The device could not be added. Please check the logs for more information.",
|
||||||
"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",
|
|
||||||
"enter_qr_code": "Enter QR code value",
|
|
||||||
"select_camera": "Select camera",
|
|
||||||
"inclusion_failed": "The device could not be added.",
|
|
||||||
"check_logs": "Please check the logs for more information.",
|
|
||||||
"inclusion_finished": "The device has been added.",
|
"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",
|
"view_device": "View Device",
|
||||||
"interview_started": "The device is being interviewed. This may take some time.",
|
"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."
|
"interview_failed": "The device interview failed. Additional information may be available in the logs."
|
||||||
@@ -3038,7 +3030,6 @@
|
|||||||
"grid_neutrality_not_calculated": "Grid neutrality could not be calculated"
|
"grid_neutrality_not_calculated": "Grid neutrality could not be calculated"
|
||||||
},
|
},
|
||||||
"energy_distribution": {
|
"energy_distribution": {
|
||||||
"title_today": "Energy distribution today",
|
|
||||||
"grid": "Grid",
|
"grid": "Grid",
|
||||||
"gas": "Gas",
|
"gas": "Gas",
|
||||||
"solar": "Solar",
|
"solar": "Solar",
|
||||||
|
149
src/util/cover-model.js
Normal file
149
src/util/cover-model.js
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
import { supportsFeature } from "../common/entity/supports-feature";
|
||||||
|
|
||||||
|
/* eslint-enable no-bitwise */
|
||||||
|
export default class CoverEntity {
|
||||||
|
constructor(hass, stateObj) {
|
||||||
|
this.hass = hass;
|
||||||
|
this.stateObj = stateObj;
|
||||||
|
this._attr = stateObj.attributes;
|
||||||
|
this._feat = this._attr.supported_features;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isFullyOpen() {
|
||||||
|
if (this._attr.current_position !== undefined) {
|
||||||
|
return this._attr.current_position === 100;
|
||||||
|
}
|
||||||
|
return this.stateObj.state === "open";
|
||||||
|
}
|
||||||
|
|
||||||
|
get isFullyClosed() {
|
||||||
|
if (this._attr.current_position !== undefined) {
|
||||||
|
return this._attr.current_position === 0;
|
||||||
|
}
|
||||||
|
return this.stateObj.state === "closed";
|
||||||
|
}
|
||||||
|
|
||||||
|
get isFullyOpenTilt() {
|
||||||
|
return this._attr.current_tilt_position === 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isFullyClosedTilt() {
|
||||||
|
return this._attr.current_tilt_position === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isOpening() {
|
||||||
|
return this.stateObj.state === "opening";
|
||||||
|
}
|
||||||
|
|
||||||
|
get isClosing() {
|
||||||
|
return this.stateObj.state === "closing";
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsOpen() {
|
||||||
|
return supportsFeature(this.stateObj, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsClose() {
|
||||||
|
return supportsFeature(this.stateObj, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsSetPosition() {
|
||||||
|
return supportsFeature(this.stateObj, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsStop() {
|
||||||
|
return supportsFeature(this.stateObj, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsOpenTilt() {
|
||||||
|
return supportsFeature(this.stateObj, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsCloseTilt() {
|
||||||
|
return supportsFeature(this.stateObj, 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsStopTilt() {
|
||||||
|
return supportsFeature(this.stateObj, 64);
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsSetTiltPosition() {
|
||||||
|
return supportsFeature(this.stateObj, 128);
|
||||||
|
}
|
||||||
|
|
||||||
|
get isTiltOnly() {
|
||||||
|
const supportsCover =
|
||||||
|
this.supportsOpen || this.supportsClose || this.supportsStop;
|
||||||
|
const supportsTilt =
|
||||||
|
this.supportsOpenTilt || this.supportsCloseTilt || this.supportsStopTilt;
|
||||||
|
return supportsTilt && !supportsCover;
|
||||||
|
}
|
||||||
|
|
||||||
|
openCover() {
|
||||||
|
this.callService("open_cover");
|
||||||
|
}
|
||||||
|
|
||||||
|
closeCover() {
|
||||||
|
this.callService("close_cover");
|
||||||
|
}
|
||||||
|
|
||||||
|
stopCover() {
|
||||||
|
this.callService("stop_cover");
|
||||||
|
}
|
||||||
|
|
||||||
|
openCoverTilt() {
|
||||||
|
this.callService("open_cover_tilt");
|
||||||
|
}
|
||||||
|
|
||||||
|
closeCoverTilt() {
|
||||||
|
this.callService("close_cover_tilt");
|
||||||
|
}
|
||||||
|
|
||||||
|
stopCoverTilt() {
|
||||||
|
this.callService("stop_cover_tilt");
|
||||||
|
}
|
||||||
|
|
||||||
|
setCoverPosition(position) {
|
||||||
|
this.callService("set_cover_position", { position });
|
||||||
|
}
|
||||||
|
|
||||||
|
setCoverTiltPosition(tiltPosition) {
|
||||||
|
this.callService("set_cover_tilt_position", {
|
||||||
|
tilt_position: tiltPosition,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper method
|
||||||
|
|
||||||
|
callService(service, data = {}) {
|
||||||
|
data.entity_id = this.stateObj.entity_id;
|
||||||
|
this.hass.callService("cover", service, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const supportsOpen = (stateObj) => supportsFeature(stateObj, 1);
|
||||||
|
|
||||||
|
export const supportsClose = (stateObj) => supportsFeature(stateObj, 2);
|
||||||
|
|
||||||
|
export const supportsSetPosition = (stateObj) => supportsFeature(stateObj, 4);
|
||||||
|
|
||||||
|
export const supportsStop = (stateObj) => supportsFeature(stateObj, 8);
|
||||||
|
|
||||||
|
export const supportsOpenTilt = (stateObj) => supportsFeature(stateObj, 16);
|
||||||
|
|
||||||
|
export const supportsCloseTilt = (stateObj) => supportsFeature(stateObj, 32);
|
||||||
|
|
||||||
|
export const supportsStopTilt = (stateObj) => supportsFeature(stateObj, 64);
|
||||||
|
|
||||||
|
export const supportsSetTiltPosition = (stateObj) =>
|
||||||
|
supportsFeature(stateObj, 128);
|
||||||
|
|
||||||
|
export function isTiltOnly(stateObj) {
|
||||||
|
const supportsCover =
|
||||||
|
supportsOpen(stateObj) || supportsClose(stateObj) || supportsStop(stateObj);
|
||||||
|
const supportsTilt =
|
||||||
|
supportsOpenTilt(stateObj) ||
|
||||||
|
supportsCloseTilt(stateObj) ||
|
||||||
|
supportsStopTilt(stateObj);
|
||||||
|
return supportsTilt && !supportsCover;
|
||||||
|
}
|
576
test/data/history.spec.ts
Normal file
576
test/data/history.spec.ts
Normal file
@@ -0,0 +1,576 @@
|
|||||||
|
import { assert } from "chai";
|
||||||
|
|
||||||
|
import { calculateStatisticsSumGrowthWithPercentage } from "../../src/data/history";
|
||||||
|
|
||||||
|
describe("calculateStatisticsSumGrowthWithPercentage", () => {
|
||||||
|
it("Returns null if not enough values", async () => {
|
||||||
|
assert.strictEqual(
|
||||||
|
calculateStatisticsSumGrowthWithPercentage([], []),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Returns null if not enough sum stat values", async () => {
|
||||||
|
assert.strictEqual(
|
||||||
|
calculateStatisticsSumGrowthWithPercentage(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.carbon_intensity",
|
||||||
|
start: "2021-07-28T05:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: 75,
|
||||||
|
mean: 50,
|
||||||
|
min: 25,
|
||||||
|
sum: null,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.carbon_intensity",
|
||||||
|
start: "2021-07-28T07:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: 100,
|
||||||
|
mean: 75,
|
||||||
|
min: 50,
|
||||||
|
sum: null,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Returns null if not enough percentage stat values", async () => {
|
||||||
|
assert.strictEqual(
|
||||||
|
calculateStatisticsSumGrowthWithPercentage(
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
[
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.peak_consumption",
|
||||||
|
start: "2021-07-28T04:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: null,
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
sum: 50,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.peak_consumption",
|
||||||
|
start: "2021-07-28T05:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: null,
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
sum: 100,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.peak_consumption",
|
||||||
|
start: "2021-07-28T07:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: null,
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
sum: 200,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]
|
||||||
|
),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Returns a percentage of the growth", async () => {
|
||||||
|
assert.strictEqual(
|
||||||
|
calculateStatisticsSumGrowthWithPercentage(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.carbon_intensity",
|
||||||
|
start: "2021-07-28T05:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: 75,
|
||||||
|
mean: 50,
|
||||||
|
min: 25,
|
||||||
|
sum: null,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.carbon_intensity",
|
||||||
|
start: "2021-07-28T07:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: 100,
|
||||||
|
mean: 75,
|
||||||
|
min: 50,
|
||||||
|
sum: null,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.peak_consumption",
|
||||||
|
start: "2021-07-28T04:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: null,
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
sum: 50,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.peak_consumption",
|
||||||
|
start: "2021-07-28T05:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: null,
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
sum: 100,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.peak_consumption",
|
||||||
|
start: "2021-07-28T07:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: null,
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
sum: 200,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.off_peak_consumption",
|
||||||
|
start: "2021-07-28T04:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: null,
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
sum: 50,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.off_peak_consumption",
|
||||||
|
start: "2021-07-28T05:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: null,
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
sum: 100,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.off_peak_consumption",
|
||||||
|
start: "2021-07-28T07:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: null,
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
sum: 200,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]
|
||||||
|
),
|
||||||
|
200
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("It ignores sum data that doesnt match start", async () => {
|
||||||
|
assert.strictEqual(
|
||||||
|
calculateStatisticsSumGrowthWithPercentage(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.carbon_intensity",
|
||||||
|
start: "2021-07-28T05:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: 75,
|
||||||
|
mean: 50,
|
||||||
|
min: 25,
|
||||||
|
sum: null,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.carbon_intensity",
|
||||||
|
start: "2021-07-28T07:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: 100,
|
||||||
|
mean: 75,
|
||||||
|
min: 50,
|
||||||
|
sum: null,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.peak_consumption",
|
||||||
|
start: "2021-07-28T04:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: null,
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
sum: 50,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.peak_consumption",
|
||||||
|
start: "2021-07-28T04:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: null,
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
sum: 50,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.peak_consumption",
|
||||||
|
start: "2021-07-28T05:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: null,
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
sum: 100,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.peak_consumption",
|
||||||
|
start: "2021-07-28T07:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: null,
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
sum: 200,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]
|
||||||
|
),
|
||||||
|
100
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("It ignores percentage data that doesnt match start", async () => {
|
||||||
|
assert.strictEqual(
|
||||||
|
calculateStatisticsSumGrowthWithPercentage(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.carbon_intensity",
|
||||||
|
start: "2021-07-28T04:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: 25,
|
||||||
|
mean: 25,
|
||||||
|
min: 25,
|
||||||
|
sum: null,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.carbon_intensity",
|
||||||
|
start: "2021-07-28T05:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: 75,
|
||||||
|
mean: 50,
|
||||||
|
min: 25,
|
||||||
|
sum: null,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.carbon_intensity",
|
||||||
|
start: "2021-07-28T07:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: 100,
|
||||||
|
mean: 75,
|
||||||
|
min: 50,
|
||||||
|
sum: null,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.peak_consumption",
|
||||||
|
start: "2021-07-28T04:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: null,
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
sum: 50,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.peak_consumption",
|
||||||
|
start: "2021-07-28T05:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: null,
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
sum: 100,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.peak_consumption",
|
||||||
|
start: "2021-07-28T07:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: null,
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
sum: 200,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]
|
||||||
|
),
|
||||||
|
100
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Returns a percentage of the growth", async () => {
|
||||||
|
assert.strictEqual(
|
||||||
|
calculateStatisticsSumGrowthWithPercentage(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.grid_fossil_fuel_percentage",
|
||||||
|
start: "2021-08-03T06:00:00.000Z",
|
||||||
|
mean: 10,
|
||||||
|
min: 10,
|
||||||
|
max: 10,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 10,
|
||||||
|
sum: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.grid_fossil_fuel_percentage",
|
||||||
|
start: "2021-08-03T07:00:00.000Z",
|
||||||
|
mean: 20,
|
||||||
|
min: 20,
|
||||||
|
max: 20,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 20,
|
||||||
|
sum: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.grid_fossil_fuel_percentage",
|
||||||
|
start: "2021-08-03T08:00:00.000Z",
|
||||||
|
mean: 30,
|
||||||
|
min: 30,
|
||||||
|
max: 30,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 30,
|
||||||
|
sum: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.grid_fossil_fuel_percentage",
|
||||||
|
start: "2021-08-03T09:00:00.000Z",
|
||||||
|
mean: 40,
|
||||||
|
min: 40,
|
||||||
|
max: 40,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 40,
|
||||||
|
sum: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.grid_fossil_fuel_percentage",
|
||||||
|
start: "2021-08-03T10:00:00.000Z",
|
||||||
|
mean: 50,
|
||||||
|
min: 50,
|
||||||
|
max: 50,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 50,
|
||||||
|
sum: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.grid_fossil_fuel_percentage",
|
||||||
|
start: "2021-08-03T11:00:00.000Z",
|
||||||
|
mean: 60,
|
||||||
|
min: 60,
|
||||||
|
max: 60,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 60,
|
||||||
|
sum: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.grid_fossil_fuel_percentage",
|
||||||
|
start: "2021-08-03T12:00:00.000Z",
|
||||||
|
mean: 70,
|
||||||
|
min: 70,
|
||||||
|
max: 70,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 70,
|
||||||
|
sum: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.grid_fossil_fuel_percentage",
|
||||||
|
start: "2021-08-03T13:00:00.000Z",
|
||||||
|
mean: 80,
|
||||||
|
min: 80,
|
||||||
|
max: 80,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 80,
|
||||||
|
sum: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.grid_fossil_fuel_percentage",
|
||||||
|
start: "2021-08-03T14:00:00.000Z",
|
||||||
|
mean: 90,
|
||||||
|
min: 90,
|
||||||
|
max: 90,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 90,
|
||||||
|
sum: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.grid_fossil_fuel_percentage",
|
||||||
|
start: "2021-08-03T15:00:00.000Z",
|
||||||
|
mean: 100,
|
||||||
|
min: 100,
|
||||||
|
max: 100,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 100,
|
||||||
|
sum: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.grid_fossil_fuel_percentage",
|
||||||
|
start: "2021-08-03T16:00:00.000Z",
|
||||||
|
mean: 110,
|
||||||
|
min: 110,
|
||||||
|
max: 110,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 120,
|
||||||
|
sum: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.energy_consumption_tarif_1",
|
||||||
|
start: "2021-08-03T06:00:00.000Z",
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
max: null,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 10,
|
||||||
|
sum: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.energy_consumption_tarif_1",
|
||||||
|
start: "2021-08-03T07:00:00.000Z",
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
max: null,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 20,
|
||||||
|
sum: 20,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.energy_consumption_tarif_1",
|
||||||
|
start: "2021-08-03T08:00:00.000Z",
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
max: null,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 30,
|
||||||
|
sum: 30,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.energy_consumption_tarif_1",
|
||||||
|
start: "2021-08-03T09:00:00.000Z",
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
max: null,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 40,
|
||||||
|
sum: 40,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.energy_consumption_tarif_1",
|
||||||
|
start: "2021-08-03T10:00:00.000Z",
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
max: null,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 50,
|
||||||
|
sum: 50,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.energy_consumption_tarif_1",
|
||||||
|
start: "2021-08-03T11:00:00.000Z",
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
max: null,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 60,
|
||||||
|
sum: 60,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.energy_consumption_tarif_1",
|
||||||
|
start: "2021-08-03T12:00:00.000Z",
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
max: null,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 70,
|
||||||
|
sum: 70,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.energy_consumption_tarif_1",
|
||||||
|
start: "2021-08-03T13:00:00.000Z",
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
max: null,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 80,
|
||||||
|
sum: 80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.energy_consumption_tarif_1",
|
||||||
|
start: "2021-08-03T14:00:00.000Z",
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
max: null,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 90,
|
||||||
|
sum: 90,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.energy_consumption_tarif_1",
|
||||||
|
start: "2021-08-03T15:00:00.000Z",
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
max: null,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 100,
|
||||||
|
sum: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.energy_consumption_tarif_2",
|
||||||
|
start: "2021-08-03T15:00:00.000Z",
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
max: null,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 10,
|
||||||
|
sum: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.energy_consumption_tarif_2",
|
||||||
|
start: "2021-08-03T16:00:00.000Z",
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
max: null,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 20,
|
||||||
|
sum: 20,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]
|
||||||
|
),
|
||||||
|
65
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
@@ -9139,7 +9139,6 @@ fsevents@^1.2.7:
|
|||||||
prettier: ^2.4.1
|
prettier: ^2.4.1
|
||||||
proxy-polyfill: ^0.3.2
|
proxy-polyfill: ^0.3.2
|
||||||
punycode: ^2.1.1
|
punycode: ^2.1.1
|
||||||
qr-scanner: ^1.3.0
|
|
||||||
qrcode: ^1.4.4
|
qrcode: ^1.4.4
|
||||||
regenerator-runtime: ^0.13.8
|
regenerator-runtime: ^0.13.8
|
||||||
require-dir: ^1.2.0
|
require-dir: ^1.2.0
|
||||||
@@ -13008,13 +13007,6 @@ fsevents@^1.2.7:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"qr-scanner@npm:^1.3.0":
|
|
||||||
version: 1.3.0
|
|
||||||
resolution: "qr-scanner@npm:1.3.0"
|
|
||||||
checksum: 421ff00626252d0f9e50550fb550a463166e4d0438baffb469c9450079f1f802f6df22784509bb571ef50ece81aecaadc00f91d442959f37655ad29710c81c8b
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"qrcode@npm:^1.4.4":
|
"qrcode@npm:^1.4.4":
|
||||||
version: 1.4.4
|
version: 1.4.4
|
||||||
resolution: "qrcode@npm:1.4.4"
|
resolution: "qrcode@npm:1.4.4"
|
||||||
|
Reference in New Issue
Block a user