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 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,

View File

@ -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 已加载');