mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-14 12:56:37 +00:00
working version
This commit is contained in:
parent
4151ce5dba
commit
45df2d977c
@ -210,10 +210,8 @@ export class HaNetworkGraph extends SubscribeMixin(LitElement) {
|
|||||||
// set the position of the node at polarDistance from the center in a random direction
|
// set the position of the node at polarDistance from the center in a random direction
|
||||||
const angle = Math.random() * 2 * Math.PI;
|
const angle = Math.random() * 2 * Math.PI;
|
||||||
echartsNode.x =
|
echartsNode.x =
|
||||||
containerWidth / 2 +
|
|
||||||
((Math.cos(angle) * containerWidth) / 2) * node.polarDistance;
|
((Math.cos(angle) * containerWidth) / 2) * node.polarDistance;
|
||||||
echartsNode.y =
|
echartsNode.y =
|
||||||
containerHeight / 2 +
|
|
||||||
((Math.sin(angle) * containerHeight) / 2) * node.polarDistance;
|
((Math.sin(angle) * containerHeight) / 2) * node.polarDistance;
|
||||||
this._nodePositions[node.id] = {
|
this._nodePositions[node.id] = {
|
||||||
x: echartsNode.x,
|
x: echartsNode.x,
|
||||||
|
@ -226,6 +226,7 @@ export class ZHANetworkVisualizationPage extends LitElement {
|
|||||||
: offlineColor,
|
: offlineColor,
|
||||||
},
|
},
|
||||||
polarDistance: category === 0 ? 0 : category === 1 ? 0.5 : 0.9,
|
polarDistance: category === 0 ? 0 : category === 1 ? 0.5 : 0.9,
|
||||||
|
fixed: isCoordinator,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create links (edges)
|
// Create links (edges)
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { css, html, LitElement } from "lit";
|
import { css, html, LitElement } from "lit";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
|
import { mdiUpdate } from "@mdi/js";
|
||||||
|
import type {
|
||||||
|
CallbackDataParams,
|
||||||
|
TopLevelFormatterParams,
|
||||||
|
} from "echarts/types/dist/shared";
|
||||||
import type { HomeAssistant, Route } from "../../../../../types";
|
import type { HomeAssistant, Route } from "../../../../../types";
|
||||||
import { configTabs } from "./zwave_js-config-router";
|
import { configTabs } from "./zwave_js-config-router";
|
||||||
import { SubscribeMixin } from "../../../../../mixins/subscribe-mixin";
|
import { SubscribeMixin } from "../../../../../mixins/subscribe-mixin";
|
||||||
@ -23,6 +28,7 @@ import type {
|
|||||||
import { colorVariables } from "../../../../../resources/theme/color.globals";
|
import { colorVariables } from "../../../../../resources/theme/color.globals";
|
||||||
import type { DeviceRegistryEntry } from "../../../../../data/device_registry";
|
import type { DeviceRegistryEntry } from "../../../../../data/device_registry";
|
||||||
import { debounce } from "../../../../../common/util/debounce";
|
import { debounce } from "../../../../../common/util/debounce";
|
||||||
|
import { navigate } from "../../../../../common/navigate";
|
||||||
|
|
||||||
@customElement("zwave_js-network-visualization")
|
@customElement("zwave_js-network-visualization")
|
||||||
export class ZWaveJSNetworkVisualization extends SubscribeMixin(LitElement) {
|
export class ZWaveJSNetworkVisualization extends SubscribeMixin(LitElement) {
|
||||||
@ -45,6 +51,8 @@ export class ZWaveJSNetworkVisualization extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@state() private _devices: Record<string, DeviceRegistryEntry> = {};
|
@state() private _devices: Record<string, DeviceRegistryEntry> = {};
|
||||||
|
|
||||||
|
@state() private _live = false;
|
||||||
|
|
||||||
public hassSubscribe() {
|
public hassSubscribe() {
|
||||||
const devices = Object.values(this.hass.devices).filter((device) =>
|
const devices = Object.values(this.hass.devices).filter((device) =>
|
||||||
device.config_entries.some((entry) => entry === this.configEntryId)
|
device.config_entries.some((entry) => entry === this.configEntryId)
|
||||||
@ -54,8 +62,11 @@ export class ZWaveJSNetworkVisualization extends SubscribeMixin(LitElement) {
|
|||||||
subscribeZwaveNodeStatistics(this.hass!, device.id, (message) => {
|
subscribeZwaveNodeStatistics(this.hass!, device.id, (message) => {
|
||||||
const nodeId = message.nodeId ?? message.node_id;
|
const nodeId = message.nodeId ?? message.node_id;
|
||||||
this._devices[nodeId!] = device;
|
this._devices[nodeId!] = device;
|
||||||
|
const isNew = !this._nodeStatistics[nodeId!];
|
||||||
this._nodeStatistics[nodeId!] = message;
|
this._nodeStatistics[nodeId!] = message;
|
||||||
|
if (this._live || isNew) {
|
||||||
this._handleUpdatedNodeStatistics();
|
this._handleUpdatedNodeStatistics();
|
||||||
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -79,8 +90,18 @@ export class ZWaveJSNetworkVisualization extends SubscribeMixin(LitElement) {
|
|||||||
this._nodeStatuses,
|
this._nodeStatuses,
|
||||||
this._nodeStatistics
|
this._nodeStatistics
|
||||||
)}
|
)}
|
||||||
|
.tooltipFormatter=${this._tooltipFormatter}
|
||||||
@chart-click=${this._handleChartClick}
|
@chart-click=${this._handleChartClick}
|
||||||
></ha-network-graph
|
>
|
||||||
|
<ha-icon-button
|
||||||
|
slot="button"
|
||||||
|
class=${this._live ? "active" : "inactive"}
|
||||||
|
.path=${mdiUpdate}
|
||||||
|
@click=${this._toggleLive}
|
||||||
|
label=${this.hass.localize(
|
||||||
|
"ui.panel.config.zwave_js.visualization.toggle_live"
|
||||||
|
)}
|
||||||
|
></ha-icon-button> </ha-network-graph
|
||||||
></hass-tabs-subpage>
|
></hass-tabs-subpage>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -100,6 +121,45 @@ export class ZWaveJSNetworkVisualization extends SubscribeMixin(LitElement) {
|
|||||||
// console.log("neighbors", neighbors);
|
// console.log("neighbors", neighbors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _tooltipFormatter = (params: TopLevelFormatterParams): string => {
|
||||||
|
const { dataType, data } = params as CallbackDataParams;
|
||||||
|
if (dataType === "edge") {
|
||||||
|
const { source, target } = data as any;
|
||||||
|
const sourceDevice = this._devices[source];
|
||||||
|
const targetDevice = this._devices[target];
|
||||||
|
const sourceName =
|
||||||
|
sourceDevice?.name_by_user ?? sourceDevice?.name ?? source;
|
||||||
|
const targetName =
|
||||||
|
targetDevice?.name_by_user ?? targetDevice?.name ?? target;
|
||||||
|
let tip = `${sourceName} → ${targetName}`;
|
||||||
|
const route =
|
||||||
|
this._nodeStatistics[source]?.lwr || this._nodeStatistics[source]?.nlwr;
|
||||||
|
if (route?.protocol_data_rate) {
|
||||||
|
tip += `<br><b>${this.hass.localize("ui.panel.config.zwave_js.visualization.data_rate")}:</b> ${this.hass.localize(`ui.panel.config.zwave_js.protocol_data_rate.${route.protocol_data_rate}`)}`;
|
||||||
|
}
|
||||||
|
if (route?.rssi) {
|
||||||
|
tip += `<br><b>RSSI:</b> ${route.rssi}`;
|
||||||
|
}
|
||||||
|
return tip;
|
||||||
|
}
|
||||||
|
const { id, name } = data as any;
|
||||||
|
const device = this._devices[id];
|
||||||
|
const nodeStatus = this._nodeStatuses[id];
|
||||||
|
let tip = `${(params as any).marker} ${name}`;
|
||||||
|
tip += `<br><b>${this.hass.localize("ui.panel.config.zwave_js.visualization.node_id")}:</b> ${id}`;
|
||||||
|
if (device) {
|
||||||
|
tip += `<br><b>${this.hass.localize("ui.panel.config.zwave_js.visualization.manufacturer")}:</b> ${device.manufacturer || "-"}`;
|
||||||
|
tip += `<br><b>${this.hass.localize("ui.panel.config.zwave_js.visualization.model")}:</b> ${device.model || "-"}`;
|
||||||
|
}
|
||||||
|
if (nodeStatus) {
|
||||||
|
tip += `<br><b>${this.hass.localize("ui.panel.config.zwave_js.visualization.status")}:</b> ${this.hass.localize(`ui.panel.config.zwave_js.node_status.${nodeStatus.status}`)}`;
|
||||||
|
if (nodeStatus.zwave_plus_version) {
|
||||||
|
tip += `<br><b>Z-Wave Plus:</b> ${this.hass.localize("ui.panel.config.zwave_js.visualization.version")} ${nodeStatus.zwave_plus_version}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tip;
|
||||||
|
};
|
||||||
|
|
||||||
private _getNetworkData = memoizeOne(
|
private _getNetworkData = memoizeOne(
|
||||||
(
|
(
|
||||||
nodeStatuses: Record<number, ZWaveJSNodeStatus>,
|
nodeStatuses: Record<number, ZWaveJSNodeStatus>,
|
||||||
@ -158,12 +218,7 @@ export class ZWaveJSNetworkVisualization extends SubscribeMixin(LitElement) {
|
|||||||
nodes.push({
|
nodes.push({
|
||||||
id: String(node.node_id),
|
id: String(node.node_id),
|
||||||
name: device?.name_by_user ?? device?.name ?? String(node.node_id),
|
name: device?.name_by_user ?? device?.name ?? String(node.node_id),
|
||||||
fixed: node.is_controller_node,
|
value: node.is_controller_node ? 3 : node.is_routing ? 2 : 1,
|
||||||
polarDistance: node.is_controller_node
|
|
||||||
? 0
|
|
||||||
: node.status === NodeStatus.Dead
|
|
||||||
? 1
|
|
||||||
: 0.5,
|
|
||||||
category:
|
category:
|
||||||
node.status === NodeStatus.Dead
|
node.status === NodeStatus.Dead
|
||||||
? 3
|
? 3
|
||||||
@ -172,6 +227,7 @@ export class ZWaveJSNetworkVisualization extends SubscribeMixin(LitElement) {
|
|||||||
: node.is_controller_node
|
: node.is_controller_node
|
||||||
? 0
|
? 0
|
||||||
: 1,
|
: 1,
|
||||||
|
symbolSize: node.is_controller_node ? 40 : node.is_routing ? 30 : 20,
|
||||||
symbol: node.is_controller_node ? "roundRect" : "circle",
|
symbol: node.is_controller_node ? "roundRect" : "circle",
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color:
|
color:
|
||||||
@ -183,6 +239,12 @@ export class ZWaveJSNetworkVisualization extends SubscribeMixin(LitElement) {
|
|||||||
? colorVariables["primary-color"]
|
? colorVariables["primary-color"]
|
||||||
: colorVariables["cyan-color"],
|
: colorVariables["cyan-color"],
|
||||||
},
|
},
|
||||||
|
polarDistance: node.is_controller_node
|
||||||
|
? 0
|
||||||
|
: node.status === NodeStatus.Dead
|
||||||
|
? 0.9
|
||||||
|
: 0.5,
|
||||||
|
fixed: node.is_controller_node,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -190,22 +252,52 @@ export class ZWaveJSNetworkVisualization extends SubscribeMixin(LitElement) {
|
|||||||
const route = stats.lwr || stats.nlwr;
|
const route = stats.lwr || stats.nlwr;
|
||||||
if (route) {
|
if (route) {
|
||||||
const hops = [
|
const hops = [
|
||||||
...route.repeaters
|
...route.repeaters.map((id, i) => [
|
||||||
.map(
|
Object.keys(this._devices).find(
|
||||||
(id) =>
|
(_nodeId) => this._devices[_nodeId]?.id === id
|
||||||
Object.entries(this._devices).find(
|
)?.[0],
|
||||||
([_nodeId, d]) => d.id === id
|
route.repeater_rssi[i],
|
||||||
)?.[0]
|
]),
|
||||||
)
|
[controllerNode!, route.rssi],
|
||||||
.filter(Boolean),
|
|
||||||
controllerNode!,
|
|
||||||
];
|
];
|
||||||
let sourceNode = nodeId;
|
let sourceNode: string = nodeId;
|
||||||
hops.forEach((repeater) => {
|
hops.forEach(([repeater, rssi]) => {
|
||||||
|
const RSSI = typeof rssi === "number" && rssi <= 0 ? rssi : -100;
|
||||||
|
const existingLink = links.find(
|
||||||
|
(link) =>
|
||||||
|
link.source === sourceNode && link.target === String(repeater)
|
||||||
|
);
|
||||||
|
const width = this._getLineWidth(RSSI);
|
||||||
|
if (existingLink) {
|
||||||
|
existingLink.value = Math.max(existingLink.value!, RSSI);
|
||||||
|
existingLink.lineStyle = {
|
||||||
|
...existingLink.lineStyle,
|
||||||
|
width: Math.max(existingLink.lineStyle!.width!, width),
|
||||||
|
color:
|
||||||
|
route.protocol_data_rate && RSSI > -100
|
||||||
|
? colorVariables["primary-color"]
|
||||||
|
: existingLink.lineStyle!.color,
|
||||||
|
type:
|
||||||
|
route.protocol_data_rate > 1
|
||||||
|
? "solid"
|
||||||
|
: existingLink.lineStyle!.type,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
links.push({
|
links.push({
|
||||||
source: String(sourceNode),
|
source: sourceNode,
|
||||||
target: String(repeater),
|
target: String(repeater),
|
||||||
|
value: RSSI,
|
||||||
|
lineStyle: {
|
||||||
|
width,
|
||||||
|
color:
|
||||||
|
route.protocol_data_rate && RSSI > -100
|
||||||
|
? colorVariables["primary-color"]
|
||||||
|
: colorVariables["disabled-color"],
|
||||||
|
type: route.protocol_data_rate > 1 ? "solid" : "dotted",
|
||||||
|
},
|
||||||
|
symbolSize: width * 3,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
sourceNode = String(repeater);
|
sourceNode = String(repeater);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -221,8 +313,31 @@ export class ZWaveJSNetworkVisualization extends SubscribeMixin(LitElement) {
|
|||||||
this._nodeStatistics = { ...this._nodeStatistics };
|
this._nodeStatistics = { ...this._nodeStatistics };
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
private _handleChartClick(_e: CustomEvent) {
|
private _handleChartClick(e: CustomEvent) {
|
||||||
// @TODO
|
if (
|
||||||
|
e.detail.dataType === "node" &&
|
||||||
|
e.detail.event.target.cursor === "pointer"
|
||||||
|
) {
|
||||||
|
const { id } = e.detail.data;
|
||||||
|
const device = this._devices[id];
|
||||||
|
if (device) {
|
||||||
|
navigate(`/config/devices/device/${device.id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _toggleLive() {
|
||||||
|
this._live = !this._live;
|
||||||
|
if (this._live) {
|
||||||
|
this._fetchNetworkStatus();
|
||||||
|
this._handleUpdatedNodeStatistics();
|
||||||
|
} else {
|
||||||
|
this._handleUpdatedNodeStatistics.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getLineWidth(rssi: number): number {
|
||||||
|
return rssi > -33 ? 3 : rssi > -66 ? 2 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles() {
|
static get styles() {
|
||||||
|
@ -6319,7 +6319,28 @@
|
|||||||
"controller": "Controller",
|
"controller": "Controller",
|
||||||
"node": "Node",
|
"node": "Node",
|
||||||
"asleep_node": "Asleep Node",
|
"asleep_node": "Asleep Node",
|
||||||
"dead_node": "Dead Node"
|
"dead_node": "Dead Node",
|
||||||
|
"toggle_live": "Toggle live updates",
|
||||||
|
"node_id": "Node ID",
|
||||||
|
"manufacturer": "Manufacturer",
|
||||||
|
"model": "Model",
|
||||||
|
"status": "Status",
|
||||||
|
"version": "Version",
|
||||||
|
"data_rate": "Data rate"
|
||||||
|
},
|
||||||
|
"node_status": {
|
||||||
|
"0": "Unknown",
|
||||||
|
"1": "Asleep",
|
||||||
|
"2": "Awake",
|
||||||
|
"3": "Dead",
|
||||||
|
"4": "Alive"
|
||||||
|
},
|
||||||
|
"protocol_data_rate": {
|
||||||
|
"0": "Unspecified",
|
||||||
|
"1": "Z-Wave 9.6 kbps",
|
||||||
|
"2": "Z-Wave 40 kbps",
|
||||||
|
"3": "Z-Wave 100 kbps",
|
||||||
|
"4": "Long Range 100 kbps"
|
||||||
},
|
},
|
||||||
"node_installer": {
|
"node_installer": {
|
||||||
"header": "Installer settings",
|
"header": "Installer settings",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user