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:
impressionyang 2026-04-16 14:13:22 +08:00
parent 04e942992b
commit e140dd81c4
2 changed files with 162 additions and 77 deletions

View File

@ -8,7 +8,7 @@ from typing import Any
from homeassistant import config_entries
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP,
ATTR_COLOR_TEMP_KELVIN,
ATTR_RGB_COLOR,
ColorMode,
LightEntity,

View File

@ -1,10 +1,8 @@
/**
* SigMesh Gateway 配网控制面板
*
* 这是一个自定义 Lovelace 卡片用于管理 SigMesh 网关的配网和分组功能
* SigMesh Gateway 配网控制面板 - Lovelace 自定义卡片
*
* 使用方法:
* 1. 将此文件保存HA www/community/sigmesh_gateway/ 目录
* 1. 将此文件复制HA www/community/sigmesh_gateway/ 目录
* 2. HA configuration.yaml 中添加:
* frontend:
* extra_module_url:
@ -14,15 +12,32 @@
// 配置常量
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() {
super();
this._hass = null;
this._config = null;
this._state = 'idle';
this._devices = [];
this._groups = [];
@ -33,6 +48,19 @@ class SigMeshGatewayPanel extends HTMLElement {
set 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() {
@ -56,6 +84,8 @@ class SigMeshGatewayPanel extends HTMLElement {
}
async _fetchStatus() {
if (!this._hass) return;
try {
const response = await fetch(`${API_BASE}/status`, {
headers: {
@ -67,21 +97,52 @@ class SigMeshGatewayPanel extends HTMLElement {
this._state = data.state;
this._devices = data.devices || [];
this._groups = data.groups || [];
this._render();
this._updateState();
} catch (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() {
this.innerHTML = `
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
}
ha-card {
padding: 16px;
background: var(--card-background-color);
border-radius: 8px;
box-shadow: var(--ha-card-box-shadow);
}
.header {
display: flex;
@ -158,21 +219,18 @@ class SigMeshGatewayPanel extends HTMLElement {
cursor: pointer;
font-size: 14px;
transition: opacity 0.2s;
background: var(--primary-color);
color: white;
}
.button:hover {
opacity: 0.8;
}
.button-primary {
background: var(--primary-color);
color: white;
}
.button-secondary {
background: var(--secondary-background-color);
color: var(--primary-text-color);
}
.button-danger {
background: var(--error-color);
color: white;
background: var(--error-color, #f44336);
}
.button:disabled {
opacity: 0.5;
@ -210,23 +268,13 @@ class SigMeshGatewayPanel extends HTMLElement {
padding: 24px;
color: var(--secondary-text-color);
}
.log-container {
max-height: 150px;
overflow-y: auto;
background: #1e1e1e;
border-radius: 4px;
padding: 8px;
font-family: monospace;
font-size: 12px;
ha-svg-icon {
width: 20px;
height: 20px;
}
.log-entry {
margin-bottom: 4px;
}
.log-info { color: #4caf50; }
.log-warning { color: #ff9800; }
.log-error { color: #f44336; }
</style>
<ha-card>
<div class="header">
<h2>SigMesh Gateway 配网管理</h2>
<span class="status-badge">${this._state}</span>
@ -237,31 +285,19 @@ class SigMeshGatewayPanel extends HTMLElement {
${this._renderProvActions()}
${this._renderGroupManagement()}
${this._renderGroupList()}
</ha-card>
`;
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() {
const isScanning = this._state === 'scanning';
return `
<div class="section">
<div class="section-title">设备扫描</div>
<div class="button-group">
<button class="button button-primary" id="btn-scan" ${isScanning ? 'disabled' : ''}>
<button class="button" id="btn-scan" ${isScanning ? 'disabled' : ''}>
${isScanning ? '扫描中...' : '开始扫描'}
</button>
<button class="button button-secondary" id="btn-refresh">
@ -318,7 +354,7 @@ class SigMeshGatewayPanel extends HTMLElement {
value="${device?.mac || ''}" placeholder="设备地址" readonly>
</div>
<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 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">
</div>
<div class="button-group">
<button class="button button-primary" id="btn-add-group">
<button class="button" id="btn-add-group">
添加到组
</button>
<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() {
if (!this.shadowRoot) return;
// 扫描按钮
this.querySelector('#btn-scan')?.addEventListener('click', () => this._handleScan());
this.querySelector('#btn-refresh')?.addEventListener('click', () => this._fetchStatus());
this.shadowRoot.querySelector('#btn-scan')?.addEventListener('click', () => this._handleScan());
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', () => {
const index = parseInt(item.dataset.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.querySelector('#btn-prov-stop')?.addEventListener('click', () => this._handleProvStop());
this.querySelector('#btn-bind-key')?.addEventListener('click', () => this._handleBindKey());
this.shadowRoot.querySelector('#btn-prov-start')?.addEventListener('click', () => this._handleProvStart());
this.shadowRoot.querySelector('#btn-prov-stop')?.addEventListener('click', () => this._handleProvStop());
this.shadowRoot.querySelector('#btn-bind-key')?.addEventListener('click', () => this._handleBindKey());
// 分组按钮
this.querySelector('#btn-add-group')?.addEventListener('click', () => this._handleAddGroup());
this.querySelector('#btn-remove-group')?.addEventListener('click', () => this._handleRemoveGroup());
this.shadowRoot.querySelector('#btn-add-group')?.addEventListener('click', () => this._handleAddGroup());
this.shadowRoot.querySelector('#btn-remove-group')?.addEventListener('click', () => this._handleRemoveGroup());
}
async _handleScan() {
if (!this._hass) return;
try {
await fetch(`${API_BASE}/scan`, {
method: 'POST',
@ -428,7 +508,8 @@ class SigMeshGatewayPanel extends HTMLElement {
}
async _handleProvStart() {
const deviceAddress = this.querySelector('#device-address')?.value;
if (!this._hass) return;
const deviceAddress = this.shadowRoot?.querySelector('#device-address')?.value;
try {
await fetch(`${API_BASE}/provisioning`, {
method: 'POST',
@ -448,6 +529,7 @@ class SigMeshGatewayPanel extends HTMLElement {
}
async _handleProvStop() {
if (!this._hass) return;
try {
await fetch(`${API_BASE}/provisioning`, {
method: 'POST',
@ -464,7 +546,8 @@ class SigMeshGatewayPanel extends HTMLElement {
}
async _handleBindKey() {
const deviceAddress = this.querySelector('#device-address')?.value;
if (!this._hass) return;
const deviceAddress = this.shadowRoot?.querySelector('#device-address')?.value;
const elementAddress = 0;
try {
await fetch(`${API_BASE}/provisioning`, {
@ -488,9 +571,10 @@ class SigMeshGatewayPanel extends HTMLElement {
}
async _handleAddGroup() {
const targetAddress = this.querySelector('#target-address')?.value;
const groupAddress = this.querySelector('#group-address')?.value;
const modelId = parseInt(this.querySelector('#model-id')?.value || '4352');
if (!this._hass) return;
const targetAddress = this.shadowRoot?.querySelector('#target-address')?.value;
const groupAddress = this.shadowRoot?.querySelector('#group-address')?.value;
const modelId = parseInt(this.shadowRoot?.querySelector('#model-id')?.value || '4352');
try {
await fetch(`${API_BASE}/group`, {
@ -515,9 +599,10 @@ class SigMeshGatewayPanel extends HTMLElement {
}
async _handleRemoveGroup() {
const targetAddress = this.querySelector('#target-address')?.value;
const groupAddress = this.querySelector('#group-address')?.value;
const modelId = parseInt(this.querySelector('#model-id')?.value || '4352');
if (!this._hass) return;
const targetAddress = this.shadowRoot?.querySelector('#target-address')?.value;
const groupAddress = this.shadowRoot?.querySelector('#group-address')?.value;
const modelId = parseInt(this.shadowRoot?.querySelector('#model-id')?.value || '4352');
try {
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.SigMeshGatewayPanel = SigMeshGatewayPanel;
// 添加到 window 供调试
window.SigMeshGatewayCard = SigMeshGatewayCard;
console.log('SigMesh Gateway Panel 已加载');