fix: 修复 Lovelace 卡片和过时常量警告
修复 1: Lovelace 卡片配置错误 - 重写 sigmesh-gateway-panel.js 遵循 HA 卡片规范 - 添加 setConfig 方法 - 添加 getCardSize 方法 - 添加静态方法 (getStubConfig, getConfigElement) - 使用 ha-card 包装器和 shadow DOM - 修复 hass 设置和渲染逻辑 修复 2: 过时常量警告 - light.py: 将 ATTR_COLOR_TEMP 替换为 ATTR_COLOR_TEMP_KELVIN - 修复 HA Core 2026.1 兼容性警告 部署步骤: 1. cp custom_components/sigmesh_gateway/sigmesh-gateway-panel.js ~/.homeassistant/www/community/sigmesh_gateway/ 2. cp -r custom_components/sigmesh_gateway ~/.homeassistant/custom_components/ 3. ha core restart
This commit is contained in:
parent
04e942992b
commit
e140dd81c4
@ -8,7 +8,7 @@ from typing import Any
|
|||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.components.light import (
|
from homeassistant.components.light import (
|
||||||
ATTR_BRIGHTNESS,
|
ATTR_BRIGHTNESS,
|
||||||
ATTR_COLOR_TEMP,
|
ATTR_COLOR_TEMP_KELVIN,
|
||||||
ATTR_RGB_COLOR,
|
ATTR_RGB_COLOR,
|
||||||
ColorMode,
|
ColorMode,
|
||||||
LightEntity,
|
LightEntity,
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* SigMesh Gateway 配网控制面板
|
* SigMesh Gateway 配网控制面板 - Lovelace 自定义卡片
|
||||||
*
|
|
||||||
* 这是一个自定义 Lovelace 卡片,用于管理 SigMesh 网关的配网和分组功能
|
|
||||||
*
|
*
|
||||||
* 使用方法:
|
* 使用方法:
|
||||||
* 1. 将此文件保存到 HA 的 www/community/sigmesh_gateway/ 目录
|
* 1. 将此文件复制到 HA 的 www/community/sigmesh_gateway/ 目录
|
||||||
* 2. 在 HA 的 configuration.yaml 中添加:
|
* 2. 在 HA 的 configuration.yaml 中添加:
|
||||||
* frontend:
|
* frontend:
|
||||||
* extra_module_url:
|
* extra_module_url:
|
||||||
@ -14,15 +12,32 @@
|
|||||||
|
|
||||||
// 配置常量
|
// 配置常量
|
||||||
const API_BASE = '/api/sigmesh_gateway';
|
const API_BASE = '/api/sigmesh_gateway';
|
||||||
const POLL_INTERVAL = 3000; // 3 秒轮询一次
|
const POLL_INTERVAL = 3000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 主面板组件
|
* Lovelace 卡片组件
|
||||||
*/
|
*/
|
||||||
class SigMeshGatewayPanel extends HTMLElement {
|
class SigMeshGatewayCard extends HTMLElement {
|
||||||
|
static get placeholder() {
|
||||||
|
return {
|
||||||
|
type: 'custom:sigmesh-gateway-panel',
|
||||||
|
label: 'SigMesh Gateway 配网管理'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static getConfigElement() {
|
||||||
|
// 返回配置元素(如果需要配置界面)
|
||||||
|
return document.createElement('div');
|
||||||
|
}
|
||||||
|
|
||||||
|
static getStubConfig() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this._hass = null;
|
this._hass = null;
|
||||||
|
this._config = null;
|
||||||
this._state = 'idle';
|
this._state = 'idle';
|
||||||
this._devices = [];
|
this._devices = [];
|
||||||
this._groups = [];
|
this._groups = [];
|
||||||
@ -33,6 +48,19 @@ class SigMeshGatewayPanel extends HTMLElement {
|
|||||||
|
|
||||||
set hass(hass) {
|
set hass(hass) {
|
||||||
this._hass = hass;
|
this._hass = hass;
|
||||||
|
if (!this.hasAttribute('rendered')) {
|
||||||
|
this._render();
|
||||||
|
this.setAttribute('rendered', 'true');
|
||||||
|
}
|
||||||
|
this._updateState();
|
||||||
|
}
|
||||||
|
|
||||||
|
setConfig(config) {
|
||||||
|
this._config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCardSize() {
|
||||||
|
return 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
@ -56,6 +84,8 @@ class SigMeshGatewayPanel extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _fetchStatus() {
|
async _fetchStatus() {
|
||||||
|
if (!this._hass) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE}/status`, {
|
const response = await fetch(`${API_BASE}/status`, {
|
||||||
headers: {
|
headers: {
|
||||||
@ -67,21 +97,52 @@ class SigMeshGatewayPanel extends HTMLElement {
|
|||||||
this._state = data.state;
|
this._state = data.state;
|
||||||
this._devices = data.devices || [];
|
this._devices = data.devices || [];
|
||||||
this._groups = data.groups || [];
|
this._groups = data.groups || [];
|
||||||
this._render();
|
this._updateState();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取状态失败:', error);
|
console.error('获取状态失败:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_updateState() {
|
||||||
|
if (!this.shadowRoot) return;
|
||||||
|
|
||||||
|
// 更新状态徽章
|
||||||
|
const statusBadge = this.shadowRoot.querySelector('.status-badge');
|
||||||
|
if (statusBadge) {
|
||||||
|
statusBadge.textContent = this._state;
|
||||||
|
statusBadge.style.background = this._getStateColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新设备列表
|
||||||
|
this._renderDeviceList();
|
||||||
|
|
||||||
|
// 更新组列表
|
||||||
|
this._renderGroupList();
|
||||||
|
}
|
||||||
|
|
||||||
|
_getStateColor() {
|
||||||
|
const colors = {
|
||||||
|
idle: '#4caf50',
|
||||||
|
scanning: '#2196f3',
|
||||||
|
prov_starting: '#ff9800',
|
||||||
|
prov_in_progress: '#ff9800',
|
||||||
|
prov_completed: '#4caf50',
|
||||||
|
prov_failed: '#f44336',
|
||||||
|
timeout: '#f44336',
|
||||||
|
};
|
||||||
|
return colors[this._state] || '#757575';
|
||||||
|
}
|
||||||
|
|
||||||
_render() {
|
_render() {
|
||||||
this.innerHTML = `
|
this.attachShadow({ mode: 'open' });
|
||||||
|
|
||||||
|
this.shadowRoot.innerHTML = `
|
||||||
<style>
|
<style>
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
|
}
|
||||||
|
ha-card {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
background: var(--card-background-color);
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: var(--ha-card-box-shadow);
|
|
||||||
}
|
}
|
||||||
.header {
|
.header {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -158,21 +219,18 @@ class SigMeshGatewayPanel extends HTMLElement {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
transition: opacity 0.2s;
|
transition: opacity 0.2s;
|
||||||
|
background: var(--primary-color);
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
.button:hover {
|
.button:hover {
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
.button-primary {
|
|
||||||
background: var(--primary-color);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.button-secondary {
|
.button-secondary {
|
||||||
background: var(--secondary-background-color);
|
background: var(--secondary-background-color);
|
||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
}
|
}
|
||||||
.button-danger {
|
.button-danger {
|
||||||
background: var(--error-color);
|
background: var(--error-color, #f44336);
|
||||||
color: white;
|
|
||||||
}
|
}
|
||||||
.button:disabled {
|
.button:disabled {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
@ -210,58 +268,36 @@ class SigMeshGatewayPanel extends HTMLElement {
|
|||||||
padding: 24px;
|
padding: 24px;
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
.log-container {
|
ha-svg-icon {
|
||||||
max-height: 150px;
|
width: 20px;
|
||||||
overflow-y: auto;
|
height: 20px;
|
||||||
background: #1e1e1e;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 8px;
|
|
||||||
font-family: monospace;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
}
|
||||||
.log-entry {
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
.log-info { color: #4caf50; }
|
|
||||||
.log-warning { color: #ff9800; }
|
|
||||||
.log-error { color: #f44336; }
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="header">
|
<ha-card>
|
||||||
<h2>SigMesh Gateway 配网管理</h2>
|
<div class="header">
|
||||||
<span class="status-badge">${this._state}</span>
|
<h2>SigMesh Gateway 配网管理</h2>
|
||||||
</div>
|
<span class="status-badge">${this._state}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
${this._renderScanSection()}
|
${this._renderScanSection()}
|
||||||
${this._renderDeviceList()}
|
${this._renderDeviceList()}
|
||||||
${this._renderProvActions()}
|
${this._renderProvActions()}
|
||||||
${this._renderGroupManagement()}
|
${this._renderGroupManagement()}
|
||||||
${this._renderGroupList()}
|
${this._renderGroupList()}
|
||||||
|
</ha-card>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
this._attachEventListeners();
|
this._attachEventListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
_getStateColor() {
|
|
||||||
const colors = {
|
|
||||||
idle: '#4caf50',
|
|
||||||
scanning: '#2196f3',
|
|
||||||
prov_starting: '#ff9800',
|
|
||||||
prov_in_progress: '#ff9800',
|
|
||||||
prov_completed: '#4caf50',
|
|
||||||
prov_failed: '#f44336',
|
|
||||||
timeout: '#f44336',
|
|
||||||
};
|
|
||||||
return colors[this._state] || '#757575';
|
|
||||||
}
|
|
||||||
|
|
||||||
_renderScanSection() {
|
_renderScanSection() {
|
||||||
const isScanning = this._state === 'scanning';
|
const isScanning = this._state === 'scanning';
|
||||||
return `
|
return `
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<div class="section-title">设备扫描</div>
|
<div class="section-title">设备扫描</div>
|
||||||
<div class="button-group">
|
<div class="button-group">
|
||||||
<button class="button button-primary" id="btn-scan" ${isScanning ? 'disabled' : ''}>
|
<button class="button" id="btn-scan" ${isScanning ? 'disabled' : ''}>
|
||||||
${isScanning ? '扫描中...' : '开始扫描'}
|
${isScanning ? '扫描中...' : '开始扫描'}
|
||||||
</button>
|
</button>
|
||||||
<button class="button button-secondary" id="btn-refresh">
|
<button class="button button-secondary" id="btn-refresh">
|
||||||
@ -318,7 +354,7 @@ class SigMeshGatewayPanel extends HTMLElement {
|
|||||||
value="${device?.mac || ''}" placeholder="设备地址" readonly>
|
value="${device?.mac || ''}" placeholder="设备地址" readonly>
|
||||||
</div>
|
</div>
|
||||||
<div class="button-group">
|
<div class="button-group">
|
||||||
<button class="button button-primary" id="btn-prov-start" ${isProvInProgress ? 'disabled' : ''}>
|
<button class="button" id="btn-prov-start" ${isProvInProgress ? 'disabled' : ''}>
|
||||||
开始配网
|
开始配网
|
||||||
</button>
|
</button>
|
||||||
<button class="button button-danger" id="btn-prov-stop" ${!isProvInProgress ? 'disabled' : ''}>
|
<button class="button button-danger" id="btn-prov-stop" ${!isProvInProgress ? 'disabled' : ''}>
|
||||||
@ -350,7 +386,7 @@ class SigMeshGatewayPanel extends HTMLElement {
|
|||||||
value="4352" placeholder="Model ID" min="0" max="65535">
|
value="4352" placeholder="Model ID" min="0" max="65535">
|
||||||
</div>
|
</div>
|
||||||
<div class="button-group">
|
<div class="button-group">
|
||||||
<button class="button button-primary" id="btn-add-group">
|
<button class="button" id="btn-add-group">
|
||||||
添加到组
|
添加到组
|
||||||
</button>
|
</button>
|
||||||
<button class="button button-danger" id="btn-remove-group">
|
<button class="button button-danger" id="btn-remove-group">
|
||||||
@ -387,13 +423,56 @@ class SigMeshGatewayPanel extends HTMLElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_renderDeviceList() {
|
||||||
|
const deviceListEl = this.shadowRoot.querySelector('.device-list');
|
||||||
|
if (!deviceListEl) return;
|
||||||
|
|
||||||
|
deviceListEl.innerHTML = this._devices.map((device, index) => `
|
||||||
|
<div class="device-item ${this._selectedDevice === index ? 'selected' : ''}" data-index="${index}">
|
||||||
|
<div class="device-info">
|
||||||
|
<div class="device-name">设备 ${device.mac}</div>
|
||||||
|
<div class="device-details">
|
||||||
|
元素数:${device.elements} |
|
||||||
|
地址:${device.address || '未分配'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderGroupList() {
|
||||||
|
const groupListEl = this.shadowRoot.querySelector('.group-list');
|
||||||
|
if (!groupListEl) return;
|
||||||
|
|
||||||
|
if (this._groups.length === 0) {
|
||||||
|
groupListEl.parentElement.querySelector('.empty-state')?.remove();
|
||||||
|
groupListEl.innerHTML = this._groups.map(group => `
|
||||||
|
<div class="group-item">
|
||||||
|
<span>组地址:<strong>${group.address}</strong></span>
|
||||||
|
<span>元素:${group.element_address}</span>
|
||||||
|
<span>Model: ${group.model_id}</span>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
} else {
|
||||||
|
groupListEl.innerHTML = this._groups.map(group => `
|
||||||
|
<div class="group-item">
|
||||||
|
<span>组地址:<strong>${group.address}</strong></span>
|
||||||
|
<span>元素:${group.element_address}</span>
|
||||||
|
<span>Model: ${group.model_id}</span>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_attachEventListeners() {
|
_attachEventListeners() {
|
||||||
|
if (!this.shadowRoot) return;
|
||||||
|
|
||||||
// 扫描按钮
|
// 扫描按钮
|
||||||
this.querySelector('#btn-scan')?.addEventListener('click', () => this._handleScan());
|
this.shadowRoot.querySelector('#btn-scan')?.addEventListener('click', () => this._handleScan());
|
||||||
this.querySelector('#btn-refresh')?.addEventListener('click', () => this._fetchStatus());
|
this.shadowRoot.querySelector('#btn-refresh')?.addEventListener('click', () => this._fetchStatus());
|
||||||
|
|
||||||
// 设备选择
|
// 设备选择
|
||||||
this.querySelectorAll('.device-item').forEach(item => {
|
this.shadowRoot.querySelectorAll('.device-item').forEach(item => {
|
||||||
item.addEventListener('click', () => {
|
item.addEventListener('click', () => {
|
||||||
const index = parseInt(item.dataset.index);
|
const index = parseInt(item.dataset.index);
|
||||||
this._selectedDevice = index === this._selectedDevice ? null : index;
|
this._selectedDevice = index === this._selectedDevice ? null : index;
|
||||||
@ -402,16 +481,17 @@ class SigMeshGatewayPanel extends HTMLElement {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 配网按钮
|
// 配网按钮
|
||||||
this.querySelector('#btn-prov-start')?.addEventListener('click', () => this._handleProvStart());
|
this.shadowRoot.querySelector('#btn-prov-start')?.addEventListener('click', () => this._handleProvStart());
|
||||||
this.querySelector('#btn-prov-stop')?.addEventListener('click', () => this._handleProvStop());
|
this.shadowRoot.querySelector('#btn-prov-stop')?.addEventListener('click', () => this._handleProvStop());
|
||||||
this.querySelector('#btn-bind-key')?.addEventListener('click', () => this._handleBindKey());
|
this.shadowRoot.querySelector('#btn-bind-key')?.addEventListener('click', () => this._handleBindKey());
|
||||||
|
|
||||||
// 分组按钮
|
// 分组按钮
|
||||||
this.querySelector('#btn-add-group')?.addEventListener('click', () => this._handleAddGroup());
|
this.shadowRoot.querySelector('#btn-add-group')?.addEventListener('click', () => this._handleAddGroup());
|
||||||
this.querySelector('#btn-remove-group')?.addEventListener('click', () => this._handleRemoveGroup());
|
this.shadowRoot.querySelector('#btn-remove-group')?.addEventListener('click', () => this._handleRemoveGroup());
|
||||||
}
|
}
|
||||||
|
|
||||||
async _handleScan() {
|
async _handleScan() {
|
||||||
|
if (!this._hass) return;
|
||||||
try {
|
try {
|
||||||
await fetch(`${API_BASE}/scan`, {
|
await fetch(`${API_BASE}/scan`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -428,7 +508,8 @@ class SigMeshGatewayPanel extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _handleProvStart() {
|
async _handleProvStart() {
|
||||||
const deviceAddress = this.querySelector('#device-address')?.value;
|
if (!this._hass) return;
|
||||||
|
const deviceAddress = this.shadowRoot?.querySelector('#device-address')?.value;
|
||||||
try {
|
try {
|
||||||
await fetch(`${API_BASE}/provisioning`, {
|
await fetch(`${API_BASE}/provisioning`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -448,6 +529,7 @@ class SigMeshGatewayPanel extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _handleProvStop() {
|
async _handleProvStop() {
|
||||||
|
if (!this._hass) return;
|
||||||
try {
|
try {
|
||||||
await fetch(`${API_BASE}/provisioning`, {
|
await fetch(`${API_BASE}/provisioning`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -464,7 +546,8 @@ class SigMeshGatewayPanel extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _handleBindKey() {
|
async _handleBindKey() {
|
||||||
const deviceAddress = this.querySelector('#device-address')?.value;
|
if (!this._hass) return;
|
||||||
|
const deviceAddress = this.shadowRoot?.querySelector('#device-address')?.value;
|
||||||
const elementAddress = 0;
|
const elementAddress = 0;
|
||||||
try {
|
try {
|
||||||
await fetch(`${API_BASE}/provisioning`, {
|
await fetch(`${API_BASE}/provisioning`, {
|
||||||
@ -488,9 +571,10 @@ class SigMeshGatewayPanel extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _handleAddGroup() {
|
async _handleAddGroup() {
|
||||||
const targetAddress = this.querySelector('#target-address')?.value;
|
if (!this._hass) return;
|
||||||
const groupAddress = this.querySelector('#group-address')?.value;
|
const targetAddress = this.shadowRoot?.querySelector('#target-address')?.value;
|
||||||
const modelId = parseInt(this.querySelector('#model-id')?.value || '4352');
|
const groupAddress = this.shadowRoot?.querySelector('#group-address')?.value;
|
||||||
|
const modelId = parseInt(this.shadowRoot?.querySelector('#model-id')?.value || '4352');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fetch(`${API_BASE}/group`, {
|
await fetch(`${API_BASE}/group`, {
|
||||||
@ -515,9 +599,10 @@ class SigMeshGatewayPanel extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _handleRemoveGroup() {
|
async _handleRemoveGroup() {
|
||||||
const targetAddress = this.querySelector('#target-address')?.value;
|
if (!this._hass) return;
|
||||||
const groupAddress = this.querySelector('#group-address')?.value;
|
const targetAddress = this.shadowRoot?.querySelector('#target-address')?.value;
|
||||||
const modelId = parseInt(this.querySelector('#model-id')?.value || '4352');
|
const groupAddress = this.shadowRoot?.querySelector('#group-address')?.value;
|
||||||
|
const modelId = parseInt(this.shadowRoot?.querySelector('#model-id')?.value || '4352');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fetch(`${API_BASE}/group`, {
|
await fetch(`${API_BASE}/group`, {
|
||||||
@ -542,9 +627,9 @@ class SigMeshGatewayPanel extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 注册自定义元素
|
// 注册自定义元素
|
||||||
customElements.define('sigmesh-gateway-panel', SigMeshGatewayPanel);
|
customElements.define('sigmesh-gateway-panel', SigMeshGatewayCard);
|
||||||
|
|
||||||
// 导出到 window 供调试使用
|
// 添加到 window 供调试
|
||||||
window.SigMeshGatewayPanel = SigMeshGatewayPanel;
|
window.SigMeshGatewayCard = SigMeshGatewayCard;
|
||||||
|
|
||||||
console.log('SigMesh Gateway Panel 已加载');
|
console.log('SigMesh Gateway Panel 已加载');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user