/** * SigMesh Gateway 配网控制面板 - Lovelace 自定义卡片 * * 使用方法: * 1. 将此文件复制到 HA 的 www/community/sigmesh_gateway/ 目录 * 2. 在 HA 的 configuration.yaml 中添加: * frontend: * extra_module_url: * - /local/community/sigmesh_gateway/sigmesh-gateway-panel.js * 3. 在 Lovelace Dashboard 中添加自定义卡片 */ // 配置常量 const API_BASE = '/api/sigmesh_gateway'; const POLL_INTERVAL = 3000; /** * Lovelace 卡片组件 */ 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 = []; this._pollTimer = null; this._selectedDevice = null; this._groupAddress = '0xC001'; } 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() { this._startPolling(); } disconnectedCallback() { this._stopPolling(); } _startPolling() { this._fetchStatus(); this._pollTimer = setInterval(() => this._fetchStatus(), POLL_INTERVAL); } _stopPolling() { if (this._pollTimer) { clearInterval(this._pollTimer); this._pollTimer = null; } } async _fetchStatus() { if (!this._hass) return; try { const response = await fetch(`${API_BASE}/status`, { headers: { 'Authorization': `Bearer ${this._hass.auth.data.access_token}`, 'Content-Type': 'application/json', }, }); const data = await response.json(); this._state = data.state; this._devices = data.devices || []; this._groups = data.groups || []; 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.attachShadow({ mode: 'open' }); this.shadowRoot.innerHTML = `

SigMesh Gateway 配网管理

${this._state}
${this._renderScanSection()} ${this._renderDeviceList()} ${this._renderProvActions()} ${this._renderGroupManagement()} ${this._renderGroupList()}
`; this._attachEventListeners(); } _renderScanSection() { const isScanning = this._state === 'scanning'; return `
设备扫描
`; } _renderDeviceList() { if (this._devices.length === 0) { return `
已发现设备
暂无设备,请先扫描
`; } return `
已发现设备 (${this._devices.length})
${this._devices.map((device, index) => `
设备 ${device.mac}
元素数:${device.elements} | 地址:${device.address || '未分配'}
`).join('')}
`; } _renderProvActions() { if (!this._selectedDevice && this._selectedDevice !== 0) { return ''; } const device = this._devices[this._selectedDevice]; const isProvInProgress = this._state === 'prov_in_progress' || this._state === 'prov_starting'; return `
配网操作 - ${device?.mac}
`; } _renderGroupManagement() { if (!this._selectedDevice && this._selectedDevice !== 0) { return ''; } return `
分组管理
`; } _renderGroupList() { if (this._groups.length === 0) { return `
组配置
暂无组配置
`; } return `
组配置 (${this._groups.length})
${this._groups.map(group => `
组地址:${group.address} 元素:${group.element_address} Model: ${group.model_id}
`).join('')}
`; } _renderDeviceList() { const deviceListEl = this.shadowRoot.querySelector('.device-list'); if (!deviceListEl) return; deviceListEl.innerHTML = this._devices.map((device, index) => `
设备 ${device.mac}
元素数:${device.elements} | 地址:${device.address || '未分配'}
`).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 => `
组地址:${group.address} 元素:${group.element_address} Model: ${group.model_id}
`).join(''); } else { groupListEl.innerHTML = this._groups.map(group => `
组地址:${group.address} 元素:${group.element_address} Model: ${group.model_id}
`).join(''); } } _attachEventListeners() { if (!this.shadowRoot) return; // 扫描按钮 this.shadowRoot.querySelector('#btn-scan')?.addEventListener('click', () => this._handleScan()); this.shadowRoot.querySelector('#btn-refresh')?.addEventListener('click', () => this._fetchStatus()); // 设备选择 this.shadowRoot.querySelectorAll('.device-item').forEach(item => { item.addEventListener('click', () => { const index = parseInt(item.dataset.index); this._selectedDevice = index === this._selectedDevice ? null : index; this._render(); }); }); // 配网按钮 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.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', headers: { 'Authorization': `Bearer ${this._hass.auth.data.access_token}`, 'Content-Type': 'application/json', }, body: JSON.stringify({}), }); this._fetchStatus(); } catch (error) { console.error('扫描失败:', error); } } async _handleProvStart() { if (!this._hass) return; const deviceAddress = this.shadowRoot?.querySelector('#device-address')?.value; try { await fetch(`${API_BASE}/provisioning`, { method: 'POST', headers: { 'Authorization': `Bearer ${this._hass.auth.data.access_token}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ action: 'start', device_address: deviceAddress, }), }); this._fetchStatus(); } catch (error) { console.error('配网失败:', error); } } async _handleProvStop() { if (!this._hass) return; try { await fetch(`${API_BASE}/provisioning`, { method: 'POST', headers: { 'Authorization': `Bearer ${this._hass.auth.data.access_token}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ action: 'stop' }), }); this._fetchStatus(); } catch (error) { console.error('停止配网失败:', error); } } async _handleBindKey() { if (!this._hass) return; const deviceAddress = this.shadowRoot?.querySelector('#device-address')?.value; const elementAddress = 0; try { await fetch(`${API_BASE}/provisioning`, { method: 'POST', headers: { 'Authorization': `Bearer ${this._hass.auth.data.access_token}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ action: 'bind', device_address: deviceAddress, element_address: elementAddress, }), }); alert('App Key 绑定成功'); this._fetchStatus(); } catch (error) { console.error('绑定 App Key 失败:', error); alert('绑定失败:' + error.message); } } async _handleAddGroup() { 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`, { method: 'POST', headers: { 'Authorization': `Bearer ${this._hass.auth.data.access_token}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ action: 'add', target_address: targetAddress, group_address: groupAddress, model_id: modelId, is_sig: true, }), }); this._groupAddress = groupAddress; this._fetchStatus(); } catch (error) { console.error('添加分组失败:', error); } } async _handleRemoveGroup() { 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`, { method: 'POST', headers: { 'Authorization': `Bearer ${this._hass.auth.data.access_token}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ action: 'remove', target_address: targetAddress, group_address: groupAddress, model_id: modelId, is_sig: true, }), }); this._fetchStatus(); } catch (error) { console.error('移除分组失败:', error); } } } // 注册自定义元素 customElements.define('sigmesh-gateway-panel', SigMeshGatewayCard); // 添加到 window 供调试 window.SigMeshGatewayCard = SigMeshGatewayCard; console.log('SigMesh Gateway Panel 已加载');