diff --git a/xiaoshi-pad-climate-card.js b/xiaoshi-pad-climate-card.js
new file mode 100644
index 0000000..abc03f1
--- /dev/null
+++ b/xiaoshi-pad-climate-card.js
@@ -0,0 +1,2437 @@
+import { LitElement, html, css } from "https://unpkg.com/lit-element@2.4.0/lit-element.js?module";
+
+class XiaoshiPadClimateCardEditor extends LitElement {
+ static get properties() {
+ return {
+ hass: { type: Object },
+ config: { type: Object },
+ _searchTerm: { type: String },
+ _filteredEntities: { type: Array },
+ _showEntityList: { type: Boolean },
+ _temperatureSearchTerm: { type: String },
+ _filteredTemperatureEntities: { type: Array },
+ _showTemperatureList: { type: Boolean },
+ _timerSearchTerm: { type: String },
+ _filteredTimerEntities: { type: Array },
+ _showTimerList: { type: Boolean },
+ _buttonSearchTerms: { type: Object },
+ _filteredButtonEntities: { type: Object },
+ _showButtonLists: { type: Object },
+ _button2SearchTerms: { type: Object },
+ _filteredButton2Entities: { type: Object },
+ _showButton2Lists: { type: Object },
+ _availableModes: { type: Object }
+ };
+ }
+
+ setConfig(config) {
+ this.config = config || {};
+
+ // 如果已经有选择的实体,则自动识别其可用模式
+ if (this.config.entity && this.hass && this.hass.states[this.config.entity]) {
+ this._detectAvailableModes(this.config.entity);
+ }
+
+ // 如果没有选择实体且有 hass 数据,自动选择第一个实体
+ if (!this.config.entity && this.hass) {
+ this._autoSelectFirstEntity();
+ }
+ }
+
+ _autoSelectFirstEntity() {
+ if (!this.hass) return;
+
+ const allEntities = Object.values(this.hass.states);
+ const firstEntity = allEntities.find(entity => {
+ const entityId = entity.entity_id;
+ return entityId.startsWith('climate.') || entityId.startsWith('water_heater.');
+ });
+
+ if (firstEntity) {
+ this._selectMainEntity(firstEntity.entity_id);
+ }
+ }
+
+ firstUpdated() {
+ // 点击外部关闭下拉列表
+ document.addEventListener('click', (e) => {
+ if (!e.target.closest('.entity-selector')) {
+ this._showEntityList = false;
+ this._showTemperatureList = false;
+ this._showTimerList = false;
+
+ // 关闭所有按钮的下拉列表
+ if (this._showButtonLists) {
+ Object.keys(this._showButtonLists).forEach(key => {
+ this._showButtonLists[key] = false;
+ });
+ }
+ if (this._showButton2Lists) {
+ Object.keys(this._showButton2Lists).forEach(key => {
+ this._showButton2Lists[key] = false;
+ });
+ }
+
+ this.requestUpdate();
+ }
+ });
+ }
+
+ _onMainEntitySearch(e) {
+ const searchTerm = e.target.value.toLowerCase();
+ this._searchTerm = searchTerm;
+ this._showEntityList = true;
+
+ if (!this.hass) return;
+
+ // 获取所有实体
+ const allEntities = Object.values(this.hass.states);
+
+ // 过滤实体,只显示 climate 和 water_heater 开头的实体
+ this._filteredEntities = allEntities.filter(entity => {
+ const entityId = entity.entity_id.toLowerCase();
+ const friendlyName = (entity.attributes.friendly_name || '').toLowerCase();
+
+ // 只显示 climate. 和 water_heater. 开头的实体
+ const isClimateEntity = entityId.startsWith('climate.') || entityId.startsWith('water_heater.');
+ const matchesSearch = entityId.includes(searchTerm) || friendlyName.includes(searchTerm);
+
+ return isClimateEntity && matchesSearch;
+ }).slice(0, 50); // 限制显示数量
+
+ // 如果没有选择实体且搜索为空且有过滤结果,自动选择第一个
+ if (!this.config.entity && searchTerm === '' && this._filteredEntities.length > 0) {
+ this._selectMainEntity(this._filteredEntities[0].entity_id);
+ return;
+ }
+
+ this.requestUpdate();
+ }
+
+ _selectMainEntity(entityId) {
+ this.config = {
+ ...this.config,
+ entity: entityId
+ };
+
+ this._searchTerm = ''; // 清空搜索词
+ this._showEntityList = false; // 关闭下拉列表
+
+ // 自动识别实体的可用模式
+ this._detectAvailableModes(entityId);
+
+ this._fireEvent();
+ this.requestUpdate();
+ }
+
+ _detectAvailableModes(entityId) {
+ if (!this.hass || !this.hass.states[entityId]) return;
+
+ const attrs = this.hass.states[entityId].attributes;
+ this._availableModes = {
+ hasHvacModes: attrs.hvac_modes && attrs.hvac_modes.length > 0,
+ hasFanModes: attrs.fan_modes && attrs.fan_modes.length > 0,
+ hasSwingModes: attrs.swing_modes && attrs.swing_modes.length > 0,
+ hasPresetModes: attrs.preset_modes && attrs.preset_modes.length > 0,
+ hasWaterModes: attrs.operation_list && attrs.operation_list.length > 0
+ };
+
+ // 如果用户没有手动配置显示选项,则根据自动识别结果设置默认值
+ if (this.config.show_hvac_modes === undefined) {
+ this.config.show_hvac_modes = this._availableModes.hasHvacModes;
+ }
+ if (this.config.show_fan_modes === undefined) {
+ this.config.show_fan_modes = this._availableModes.hasFanModes;
+ }
+ if (this.config.show_swing_modes === undefined) {
+ this.config.show_swing_modes = this._availableModes.hasSwingModes;
+ }
+ if (this.config.show_preset_modes === undefined) {
+ this.config.show_preset_modes = this._availableModes.hasPresetModes;
+ }
+ if (this.config.show_water_modes === undefined) {
+ this.config.show_water_modes = this._availableModes.hasWaterModes;
+ }
+ }
+
+ _onTemperatureSearch(e) {
+ const searchTerm = e.target.value.toLowerCase();
+ this._temperatureSearchTerm = searchTerm;
+ this._showTemperatureList = true;
+
+ if (!this.hass) return;
+
+ // 获取所有实体
+ const allEntities = Object.values(this.hass.states);
+
+ // 过滤实体,只显示 sensor 开头的实体
+ this._filteredTemperatureEntities = allEntities.filter(entity => {
+ const entityId = entity.entity_id.toLowerCase();
+ const friendlyName = (entity.attributes.friendly_name || '').toLowerCase();
+
+ // 只显示 sensor. 开头的实体
+ const isSensorEntity = entityId.startsWith('sensor.');
+ const matchesSearch = entityId.includes(searchTerm) || friendlyName.includes(searchTerm);
+
+ return isSensorEntity && matchesSearch;
+ }).slice(0, 50); // 限制显示数量
+
+ this.requestUpdate();
+ }
+
+ _selectTemperature(entityId) {
+ this.config = {
+ ...this.config,
+ temperature: entityId
+ };
+
+ this._temperatureSearchTerm = ''; // 清空搜索词
+ this._showTemperatureList = false; // 关闭下拉列表
+
+ this._fireEvent();
+ this.requestUpdate();
+ }
+
+ _onTimerSearch(e) {
+ const searchTerm = e.target.value.toLowerCase();
+ this._timerSearchTerm = searchTerm;
+ this._showTimerList = true;
+
+ if (!this.hass) return;
+
+ // 获取所有实体
+ const allEntities = Object.values(this.hass.states);
+
+ // 过滤实体,只显示 timer 开头的实体
+ this._filteredTimerEntities = allEntities.filter(entity => {
+ const entityId = entity.entity_id.toLowerCase();
+ const friendlyName = (entity.attributes.friendly_name || '').toLowerCase();
+
+ // 只显示 timer. 开头的实体
+ const isTimerEntity = entityId.startsWith('timer.');
+ const matchesSearch = entityId.includes(searchTerm) || friendlyName.includes(searchTerm);
+
+ return isTimerEntity && matchesSearch;
+ }).slice(0, 50); // 限制显示数量
+
+ this.requestUpdate();
+ }
+
+ _selectTimer(entityId) {
+ this.config = {
+ ...this.config,
+ timer: entityId
+ };
+
+ this._timerSearchTerm = ''; // 清空搜索词
+ this._showTimerList = false; // 关闭下拉列表
+
+ this._fireEvent();
+ this.requestUpdate();
+ }
+
+ _onButtonSearch(e, index) {
+ const searchTerm = e.target.value.toLowerCase();
+
+ if (!this._buttonSearchTerms) this._buttonSearchTerms = {};
+ if (!this._filteredButtonEntities) this._filteredButtonEntities = {};
+ if (!this._showButtonLists) this._showButtonLists = {};
+
+ this._buttonSearchTerms[index] = searchTerm;
+ this._showButtonLists[index] = true;
+
+ if (!this.hass) return;
+
+ const allEntities = Object.values(this.hass.states);
+
+ this._filteredButtonEntities[index] = allEntities.filter(entity => {
+ const entityId = entity.entity_id.toLowerCase();
+ const friendlyName = (entity.attributes.friendly_name || '').toLowerCase();
+
+ // 支持多种实体类型
+ const isButtonType = entityId.startsWith('switch.') ||
+ entityId.startsWith('light.') ||
+ entityId.startsWith('button.') ||
+ entityId.startsWith('sensor.') ||
+ entityId.startsWith('select.') ||
+ entityId.startsWith('input_button.') ||
+ entityId.startsWith('script.');
+ const matchesSearch = entityId.includes(searchTerm) || friendlyName.includes(searchTerm);
+
+ return isButtonType && matchesSearch;
+ }).slice(0, 50);
+
+ this.requestUpdate();
+ }
+
+ _selectButton(entityId, index) {
+ const buttons = [...(this.config.buttons || [])];
+ buttons[index] = entityId;
+
+ this.config = {
+ ...this.config,
+ buttons
+ };
+
+ if (!this._buttonSearchTerms) this._buttonSearchTerms = {};
+ if (!this._showButtonLists) this._showButtonLists = {};
+
+ this._buttonSearchTerms[index] = '';
+ this._showButtonLists[index] = false;
+
+ this._fireEvent();
+ this.requestUpdate();
+ }
+
+ _onButton2Search(e, index2) {
+ const searchTerm = e.target.value.toLowerCase();
+
+ if (!this._button2SearchTerms) this._button2SearchTerms = {};
+ if (!this._filteredButton2Entities) this._filteredButton2Entities = {};
+ if (!this._showButton2Lists) this._showButton2Lists = {};
+
+ this._button2SearchTerms[index2] = searchTerm;
+ this._showButton2Lists[index2] = true;
+
+ if (!this.hass) return;
+
+ const allEntities = Object.values(this.hass.states);
+
+ this._filteredButton2Entities[index2] = allEntities.filter(entity => {
+ const entityId = entity.entity_id.toLowerCase();
+ const friendlyName = (entity.attributes.friendly_name || '').toLowerCase();
+
+ // 支持多种实体类型
+ const isButtonType = entityId.startsWith('switch.') ||
+ entityId.startsWith('light.') ||
+ entityId.startsWith('button.') ||
+ entityId.startsWith('sensor.') ||
+ entityId.startsWith('select.') ||
+ entityId.startsWith('input_button.') ||
+ entityId.startsWith('script.');
+ const matchesSearch = entityId.includes(searchTerm) || friendlyName.includes(searchTerm);
+
+ return isButtonType && matchesSearch;
+ }).slice(0, 50);
+
+ this.requestUpdate();
+ }
+
+ _selectButton2(entityId, index2) {
+ const buttons2 = [...(this.config.buttons2 || [])];
+ buttons2[index2] = entityId;
+
+ this.config = {
+ ...this.config,
+ buttons2
+ };
+
+ if (!this._button2SearchTerms) this._button2SearchTerms = {};
+ if (!this._showButton2Lists) this._showButton2Lists = {};
+
+ this._button2SearchTerms[index2] = '';
+ this._showButton2Lists[index2] = false;
+
+ this._fireEvent();
+ this.requestUpdate();
+ }
+
+ static get styles() {
+ return css`
+ .form {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ min-height: 500px;
+ }
+ .form-group {
+ display: flex;
+ flex-direction: column;
+ gap: 5px;
+ }
+ label {
+ font-weight: bold;
+ }
+ select, input, textarea {
+ padding: 8px;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ }
+ textarea {
+ min-height: 80px;
+ resize: vertical;
+ }
+ .help-text {
+ font-size: 0.85em;
+ color: #666;
+ margin-top: 4px;
+ }
+
+ .entity-selector {
+ position: relative;
+ }
+
+ .entity-search-input {
+ width: 100%;
+ padding: 8px;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ box-sizing: border-box;
+ }
+
+ .entity-dropdown {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ right: 0;
+ height: 300px;
+ overflow-y: auto;
+ background: white;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+ z-index: 1000;
+ margin-top: 2px;
+ }
+
+ .entity-option {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 8px 12px;
+ cursor: pointer;
+ border-bottom: 1px solid #eee;
+ }
+
+ .entity-option:hover {
+ background: #f5f5f5;
+ }
+
+ .entity-option.selected {
+ background: #e3f2fd;
+ }
+
+ .entity-info {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ flex: 1;
+ }
+
+ .entity-details {
+ flex: 1;
+ }
+
+ .entity-name {
+ font-weight: 500;
+ font-size: 14px;
+ color: #000;
+ }
+
+ .entity-id {
+ font-size: 12px;
+ color: #000;
+ font-family: monospace;
+ }
+
+ .check-icon {
+ color: #4CAF50;
+ }
+
+ .no-results {
+ padding: 12px;
+ text-align: center;
+ color: #666;
+ font-style: italic;
+ }
+
+ .entity-selector-with-remove {
+ display: flex;
+ align-items: flex-start;
+ gap: 8px;
+ margin-bottom: 8px;
+ }
+
+ .entity-selector-with-remove .entity-selector {
+ flex: 1;
+ }
+
+ .remove-button {
+ background: #f44336;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ width: 30px;
+ height: 30px;
+ min-width: 30px;
+ padding: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ flex-shrink: 0;
+ margin-top: 0;
+ }
+
+ .remove-button:hover {
+ background: #d32f2f;
+ }
+
+ .remove-button ha-icon {
+ --mdc-icon-size: 20px;
+ }
+
+ .buttons-row {
+ display: flex;
+ align-items: center;
+ margin-top: 8px;
+ }
+ .add-button {
+ margin-left: 8px;
+ border: 1px solid red;
+ border-radius: 4px;
+ padding: 8px;
+ transition: all 0.2s ease;
+ }
+ .add-button:hover {
+ background-color: rgba(255, 0, 0, 0.1);
+ transform: translateY(-1px);
+ box-shadow: 0 2px 4px rgba(255, 0, 0, 0.2);
+ }
+ .hint {
+ font-size: 0.85em;
+ color: #888;
+ margin-top: 4px;
+ }
+
+ .refresh-button {
+ transition: all 0.2s ease;
+ }
+
+ .refresh-button:hover {
+ background-color: rgba(33, 150, 243, 0.1);
+ transform: translateY(-1px);
+ box-shadow: 0 2px 4px rgba(33, 150, 243, 0.2);
+ }
+
+ .mode-indicator {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ margin-bottom: 8px;
+ }
+
+ .mode-badge {
+ padding: 4px 8px;
+ border-radius: 4px;
+ font-size: 11px;
+ font-weight: 500;
+ }
+
+ .mode-badge.has-mode {
+ background-color: #e8f5e9;
+ color: #2e7d32;
+ }
+
+ .mode-badge.no-mode {
+ background-color: #ffebee;
+ color: #c62828;
+ }
+ `;
+ }
+
+ render() {
+ if (!this.hass) return html``;
+
+ return html`
+
+ `;
+ }
+
+ _valueChanged(ev) {
+ if (!this.config) return; // 移除了 !ev.detail.value 检查,允许空值
+ const configValue = ev.target.configValue;
+ const value = ev.detail.value;
+
+ // 如果值为空,则删除该配置项
+ if (!value) {
+ const newConfig = { ...this.config };
+ delete newConfig[configValue];
+ this.config = newConfig;
+ } else {
+ this.config = {
+ ...this.config,
+ [configValue]: value
+ };
+ }
+ this._fireEvent();
+ }
+
+ _buttonChanged(ev, index) {
+ // 此方法已弃用,改用 _selectButton 方法
+ if (!this.config) return;
+ const buttons = [...(this.config.buttons || [])];
+
+ // 如果值为空,则删除该按钮
+ if (!ev.detail.value) {
+ buttons.splice(index, 1);
+ } else {
+ buttons[index] = ev.detail.value;
+ }
+
+ this.config = {
+ ...this.config,
+ buttons: buttons.length > 0 ? buttons : undefined
+ };
+ this._fireEvent();
+ }
+
+ _buttonChanged2(ev2, index2) {
+ // 此方法已弃用,改用 _selectButton2 方法
+ if (!this.config) return;
+ const buttons2 = [...(this.config.buttons2 || [])];
+
+ // 如果值为空,则删除该按钮
+ if (!ev2.detail.value) {
+ buttons2.splice(index2, 1);
+ } else {
+ buttons2[index2] = ev2.detail.value;
+ }
+
+ this.config = {
+ ...this.config,
+ buttons2: buttons2.length > 0 ? buttons2 : undefined
+ };
+ this._fireEvent();
+ }
+
+ _addButton() {
+ const buttons = [...(this.config.buttons || [])];
+ if (buttons.length >= 7) return;
+ buttons.push('');
+
+ // 重置该按钮的搜索状态
+ const newIndex = buttons.length - 1;
+ if (!this._buttonSearchTerms) this._buttonSearchTerms = {};
+ if (!this._filteredButtonEntities) this._filteredButtonEntities = {};
+ if (!this._showButtonLists) this._showButtonLists = {};
+
+ this._buttonSearchTerms[newIndex] = '';
+ this._filteredButtonEntities[newIndex] = [];
+ this._showButtonLists[newIndex] = false;
+
+ this.config = {
+ ...this.config,
+ buttons
+ };
+ this._fireEvent();
+ }
+
+ _addButton2() {
+ const buttons2 = [...(this.config.buttons2 || [])];
+ if (buttons2.length >= 7) return;
+ buttons2.push('');
+
+ // 重置该按钮的搜索状态
+ const newIndex = buttons2.length - 1;
+ if (!this._button2SearchTerms) this._button2SearchTerms = {};
+ if (!this._filteredButton2Entities) this._filteredButton2Entities = {};
+ if (!this._showButton2Lists) this._showButton2Lists = {};
+
+ this._button2SearchTerms[newIndex] = '';
+ this._filteredButton2Entities[newIndex] = [];
+ this._showButton2Lists[newIndex] = false;
+
+ this.config = {
+ ...this.config,
+ buttons2
+ };
+ this._fireEvent();
+ }
+
+ _removeButton(index) {
+ const buttons = [...(this.config.buttons || [])];
+ buttons.splice(index, 1);
+
+ // 清理该按钮的搜索状态
+ if (this._buttonSearchTerms) {
+ delete this._buttonSearchTerms[index];
+ }
+ if (this._filteredButtonEntities) {
+ delete this._filteredButtonEntities[index];
+ }
+ if (this._showButtonLists) {
+ delete this._showButtonLists[index];
+ }
+
+ this.config = {
+ ...this.config,
+ buttons: buttons.length > 0 ? buttons : undefined
+ };
+ this._fireEvent();
+ this.requestUpdate();
+ }
+
+ _removeButton2(index2) {
+ const buttons2 = [...(this.config.buttons2 || [])];
+ buttons2.splice(index2, 1);
+
+ // 清理该按钮的搜索状态
+ if (this._button2SearchTerms) {
+ delete this._button2SearchTerms[index2];
+ }
+ if (this._filteredButton2Entities) {
+ delete this._filteredButton2Entities[index2];
+ }
+ if (this._showButton2Lists) {
+ delete this._showButton2Lists[index2];
+ }
+
+ this.config = {
+ ...this.config,
+ buttons2: buttons2.length > 0 ? buttons2 : undefined
+ };
+ this._fireEvent();
+ this.requestUpdate();
+ }
+
+ _removeTemperature() {
+ if (!this.config) return;
+
+ this._temperatureSearchTerm = '';
+ this._showTemperatureList = false;
+ this._filteredTemperatureEntities = [];
+
+ this.config = {
+ ...this.config,
+ temperature: undefined
+ };
+ this._fireEvent();
+ this.requestUpdate();
+ }
+
+ _removeTimer() {
+ if (!this.config) return;
+
+ this._timerSearchTerm = '';
+ this._showTimerList = false;
+ this._filteredTimerEntities = [];
+
+ this.config = {
+ ...this.config,
+ timer: undefined
+ };
+ this._fireEvent();
+ this.requestUpdate();
+ }
+
+ _themeSelectChanged(e) {
+ if (!this.config) return;
+ const theme = e.target.value;
+
+ this.config = {
+ ...this.config,
+ theme
+ };
+ this._fireEvent();
+ }
+
+ _themeSwitchChanged(ev) {
+ if (!this.config) return;
+ const theme = ev.target.checked ? 'on' : 'off';
+
+ this.config = {
+ ...this.config,
+ theme
+ };
+ this._fireEvent();
+ }
+
+ _widthChanged(e) {
+ if (!this.config) return;
+ const { name, value } = e.target;
+
+ let finalValue = value;
+
+ // 处理默认值
+ if (name === 'width') {
+ finalValue = value || '300px';
+ }
+
+ this.config = {
+ ...this.config,
+ [name]: finalValue
+ };
+ this._fireEvent();
+ }
+
+ _heightChanged(e) {
+ if (!this.config) return;
+ const { name, value } = e.target;
+
+ let finalValue = value;
+
+ // 处理默认值
+ if (name === 'height') {
+ finalValue = value || '300px';
+ }
+
+ this.config = {
+ ...this.config,
+ [name]: finalValue
+ };
+ this._fireEvent();
+ }
+
+ _showHvacModesChanged(ev) {
+ if (!this.config) return;
+ this.config = {
+ ...this.config,
+ show_hvac_modes: ev.target.checked
+ };
+ this._fireEvent();
+ }
+
+ _showFanModesChanged(ev) {
+ if (!this.config) return;
+ this.config = {
+ ...this.config,
+ show_fan_modes: ev.target.checked
+ };
+ this._fireEvent();
+ }
+
+ _showSwingModesChanged(ev) {
+ if (!this.config) return;
+ this.config = {
+ ...this.config,
+ show_swing_modes: ev.target.checked
+ };
+ this._fireEvent();
+ }
+
+ _showPresetModesChanged(ev) {
+ if (!this.config) return;
+ this.config = {
+ ...this.config,
+ show_preset_modes: ev.target.checked
+ };
+ this._fireEvent();
+ }
+
+ _showWaterModesChanged(ev) {
+ if (!this.config) return;
+ this.config = {
+ ...this.config,
+ show_water_modes: ev.target.checked
+ };
+ this._fireEvent();
+ }
+
+ _buttonPositionChanged(e) {
+ if (!this.config) return;
+ const buttonPosition = e.target.value;
+ this.config = {
+ ...this.config,
+ button_position: buttonPosition
+ };
+ this._fireEvent();
+ }
+
+ _button2PositionChanged(e) {
+ if (!this.config) return;
+ const button2Position = e.target.value;
+ this.config = {
+ ...this.config,
+ button2_position: button2Position
+ };
+ this._fireEvent();
+ }
+
+ _timerPositionChanged(e) {
+ if (!this.config) return;
+ const timerPosition = e.target.value;
+ this.config = {
+ ...this.config,
+ timer_position: timerPosition
+ };
+ this._fireEvent();
+ }
+
+ _fireEvent() {
+ this.dispatchEvent(new CustomEvent('config-changed', {
+ detail: { config: this.config }
+ }));
+ }
+
+ constructor() {
+ super();
+ this._searchTerm = '';
+ this._filteredEntities = [];
+ this._showEntityList = false;
+ this._temperatureSearchTerm = '';
+ this._filteredTemperatureEntities = [];
+ this._showTemperatureList = false;
+ this._timerSearchTerm = '';
+ this._filteredTimerEntities = [];
+ this._showTimerList = false;
+ this._buttonSearchTerms = {};
+ this._filteredButtonEntities = {};
+ this._showButtonLists = {};
+ this._button2SearchTerms = {};
+ this._filteredButton2Entities = {};
+ this._showButton2Lists = {};
+ this._availableModes = {};
+ }
+
+ updated(changedProperties) {
+ super.updated(changedProperties);
+
+ // 当 hass 第一次加载时,如果没有配置实体,自动选择第一个
+ if (changedProperties.has('hass') && this.hass && !this.config.entity) {
+ this._autoSelectFirstEntity();
+ }
+ }
+}
+customElements.define('xiaoshi-pad-climate-card-editor', XiaoshiPadClimateCardEditor);
+
+class XiaoshiPadClimateCard extends LitElement {
+ static get properties() {
+ return {
+ hass: { type: Object },
+ width: { type: String, attribute: true },
+ height: { type: String, attribute: true },
+ config: { type: Object },
+ buttons: { type: Array },
+ theme: { type: String },
+ _timerInterval: { state: true },
+ temperatureData: { type: Array },
+ _externalTempSensor: { type: String },
+ entity: { type: Object },
+ _variables: { type: Object }
+ };
+ }
+
+ static getConfigElement() {
+ return document.createElement("xiaoshi-pad-climate-card-editor");
+ }
+
+ static getStubConfig() {
+ return {
+ entity: 'climate.demo_ac', // 使用 Home Assistant 的演示实体
+ theme: 'on'
+ };
+ }
+
+ setConfig(config) {
+ this.config = config;
+ this.buttons = config.buttons || [];
+ this.buttons2 = config.buttons2 || [];
+ this._externalTempSensor = config.temperature || null;
+ if (config.width !== undefined) this.width = config.width;
+ if (config.height !== undefined) this.height = config.height;
+ this.requestUpdate();
+ }
+
+ static get styles() {
+ return css`
+ :host {
+ display: flex;
+ align-items: stretch;
+ gap: 8px;
+ }
+
+ .main-card {
+ display: block;
+ position: relative;
+ background-color: var(--bg-color);
+ border-radius: 15px;
+ width: 300px;
+ }
+
+ .side-button-wrapper {
+ display: flex;
+ }
+
+ .side-button-bar {
+ width: 60px;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-end;
+ border-radius: 15px;
+ }
+
+ .thermostat-card {
+ position: relative;
+ width: 300px;
+ height: 265px;
+ display: flex;
+ flex-direction: column;
+ }
+
+ .thermostat-container {
+ flex: 1;
+ width: 300px;
+ height: 265px;
+ position: relative;
+ }
+
+ .error {
+ padding: 16px;
+ color: #dc2626;
+ text-align: center;
+ }
+
+ .theme-on {
+ --ha-card-background: rgb(255,255,255,0); /*背景色*/
+ --primary-text-color:rgb(0,0,0); /*文字颜色*/
+ --disabled-color: rgb(150,150,150); /*圆环背景色*/
+ --_icon-color: rgb(0,0,0); /*图标颜色*/
+ --secondary-text-color: rgb(0,0,0); /*图标边框颜色*/
+ --area-bg: rgb(230,230,230); /*按钮区域背景色*/
+ }
+ .theme-off {
+ --ha-card-background: rgb(50,50,50,0);
+ --primary-text-color:rgb(255,255,255);
+ --disabled-color: rgb(220,220,220);
+ --_icon-color: rgb(255,255,255);
+ --secondary-text-color: rgb(255,255,255);
+ --area-bg: rgb(80,80,80); /*按钮区域背景色*/
+ }
+
+ .modes-area, .fan-area, .preset-area, .swing-area, .water-area {
+ display: flex;
+ flex-wrap: nowrap;
+ gap: 2px;
+ }
+
+ .mode-button {
+ flex: 1;
+ min-width: auto;
+ height: 40px;
+ border: none;
+ border-radius: 10px;
+ background: rgb(0,0,0,0);
+ cursor: pointer;
+ padding: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ --mdc-icon-size: 20px;
+ }
+
+ .mode-button .icon {
+ width: 20px;
+ height: 20px;
+ color: var(--fg-color);
+ }
+
+ .mode-button.active-mode {
+ background: var(--active-color);
+ }
+
+ .fan-button, .swing-button, .preset-button, .water-button {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0px;
+ width: 100%;
+ height: 100%;
+ color: var(--fg-color);
+ position: relative;
+ --mdc-icon-size: 18px;
+ }
+
+ .swing-text, .preset-text, .water-text {
+ font-size: 12px;
+ color: var(--fg-color);
+ }
+
+ .fan-text {
+ font-size: 10px;
+ color: var(--fg-color);
+ position: absolute;
+ bottom: 6px;
+ right: 6px;
+ transform: translate(25%, 25%);
+ }
+
+ .area-bg-wrapper {
+ background: var(--area-bg);
+ border-radius: 10px;
+ width: calc(100% - 20px);
+ margin: 0px 10px;
+ margin-bottom: 8px;
+ box-sizing: border-box;
+ }
+
+ .side-extra-button {
+ width: 40px;
+ height: 40px;
+ border: none;
+ border-radius: 8px;
+ background: var(--bg-color);
+ cursor: pointer;
+ padding: 0px;
+ margin: 0px 10px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ margin-bottom: 8px;
+ }
+
+ .side-extra-button.active-extra {
+ }
+
+ .side-extra-button .side-icon {
+ width: 16px;
+ height: 16px;
+ --mdc-icon-size: 16px;
+ }
+
+ .side-extra-button .side-text {
+ font-size: 10px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ `;
+ }
+
+ constructor() {
+ super();
+ this.hass = {};
+ this.config = {};
+ this.buttons = [];
+ this.buttons2 = [];
+ this.theme = 'on';
+ this.width = '300px';
+ this.height = '300px';
+ this._timerInterval = null;
+ this.temperatureData = [];
+ this.canvas = null;
+ this.ctx = null;
+ this._variables = {};
+ }
+
+ _evaluateTheme() {
+ try {
+ if (!this.config || !this.config.theme) return 'on';
+ if (typeof this.config.theme === 'function') {
+ return this.config.theme();
+ }
+ if (typeof this.config.theme === 'string' &&
+ (this.config.theme.includes('return') || this.config.theme.includes('=>'))) {
+ return (new Function(`return ${this.config.theme}`))();
+ }
+ return this.config.theme;
+ } catch(e) {
+ console.error('计算主题时出错:', e);
+ return 'on';
+ }
+ }
+
+ firstUpdated() {
+ super.firstUpdated();
+ this._loadOfficialThermostat();
+ }
+
+ updated(changedProperties) {
+ super.updated(changedProperties);
+
+ // 当 hass 第一次加载时,如果没有配置实体或配置的实体不存在,自动选择第一个
+ if (changedProperties.has('hass') && this.hass) {
+ if (!this.config.entity || !this.hass.states[this.config.entity]) {
+ const firstEntity = this._findFirstEntity();
+ if (firstEntity) {
+ this.config.entity = firstEntity;
+ this.requestUpdate();
+ }
+ }
+ }
+
+ // 每次组件更新后,确保 thermostat-card 也更新
+ this.updateFromHass();
+ }
+
+ _findFirstEntity() {
+ if (!this.hass) return null;
+ const allEntities = Object.values(this.hass.states);
+ const firstEntity = allEntities.find(entity => {
+ const entityId = entity.entity_id;
+ return entityId.startsWith('climate.') || entityId.startsWith('water_heater.');
+ });
+ return firstEntity ? firstEntity.entity_id : null;
+ }
+
+ _loadOfficialThermostat() {
+ this.updateComplete;
+
+ const container = this.shadowRoot?.querySelector('#thermostatContainer');
+ if (!container) return;
+
+ // 创建 hui-thermostat-card 元素
+ const thermostatCard = document.createElement('hui-thermostat-card');
+ thermostatCard.hass = this.hass;
+ thermostatCard.setConfig({
+ type: 'thermostat',
+ entity: this.config.entity
+ });
+
+ // 清空容器并添加元素
+ container.innerHTML = '';
+ container.appendChild(thermostatCard);
+ }
+
+ disconnectedCallback() {
+ super.disconnectedCallback();
+ }
+
+ connectedCallback() {
+ super.connectedCallback();
+ this.updateFromHass();
+ }
+
+ updateFromHass() {
+ if (this.hass && this.config?.entity) {
+ this.entity = this.hass.states[this.config.entity];
+ }
+ // 更新 thermostat-card 的 hass
+ const container = this.shadowRoot?.querySelector('#thermostatContainer');
+ if (container?.firstChild) {
+ container.firstChild.hass = this.hass;
+ // 强制触发 thermostat-card 的更新
+ if (container.firstChild.requestUpdate) {
+ container.firstChild.requestUpdate();
+ }
+ }
+ }
+
+ render() {
+ if (!this.hass || !this.config.entity) {
+ return html``;
+ }
+
+ if (!this.entity) {
+ return html`实体未找到: ${this.config.entity}
`;
+ }
+
+ const entity = this.hass.states[this.config.entity];
+ if (!entity) {
+ return html`实体未找到: ${this.config.entity}
`;
+ }
+ const state = entity.state;
+
+ const attrs = entity.attributes;
+
+ let current_temperature = '';
+ if (this._externalTempSensor) {
+ const tempEntity = this.hass.states[this._externalTempSensor];
+ if (tempEntity && !isNaN(parseFloat(tempEntity.state))) {
+ current_temperature = `室温: ${parseFloat(tempEntity.state).toFixed(1)}°C`;
+ }
+ } else if (typeof entity.attributes.current_temperature === 'number') {
+ current_temperature = `室温: ${entity.attributes.current_temperature.toFixed(1)}°C`;
+ }
+
+
+ const theme = this._evaluateTheme();
+ const themeClass = theme === 'on' ? 'theme-on' : 'theme-off';
+
+ const fgColor = theme === 'on' ? 'rgb(0, 0, 0)' : 'rgb(255, 255, 255)';
+ const bgColor = theme === 'on' ? 'rgb(255, 255, 255)' : 'rgb(50, 50, 50)';
+ const buttonBg = theme === 'on' ? 'rgb(50,50,50)' : 'rgb(120,120,120)';
+ const buttonFg = 'rgb(250,250,250)';
+
+ let statusColor = 'rgb(250,250,250)';
+ if (state === 'cool') statusColor = 'rgb(33,150,243)';
+ else if (state === 'heat') statusColor = 'rgb(254,111,33)';
+ else if (state === '自定义') statusColor = 'rgb(254,111,33)';
+ else if (state === 'AI控温') statusColor = 'rgb(254,111,33)';
+ else if (state === '婴童洗') statusColor = 'rgb(254,111,33)';
+ else if (state === '舒适洗') statusColor = 'rgb(254,111,33)';
+ else if (state === '宠物洗') statusColor = 'rgb(254,111,33)';
+ else if (state === '厨房用') statusColor = 'rgb(254,111,33)';
+ else if (state === 'dry') statusColor = 'rgb(255,151,0)';
+ else if (state === 'fan' || state === 'fan_only') statusColor = 'rgb(0,188,213)';
+ else if (state === 'auto') statusColor = 'rgb(147,112,219)'
+ else if (state === 'off') statusColor = 'rgb(250,250,250)';
+
+ const hasHvacModes = attrs.hvac_modes && attrs.hvac_modes.length > 0;
+ const hasFanModes = attrs.fan_modes && attrs.fan_modes.length > 0;
+ const hasSwingModes = attrs.swing_modes && attrs.swing_modes.length > 0;
+ const hasPresetModes = attrs.preset_modes && attrs.preset_modes.length > 0;
+ const hasWaterModes = attrs.operation_list && attrs.operation_list.length > 0;
+
+ // 使用配置中的显示选项(如果存在),否则使用自动识别结果
+ const showHvacModes = this.config.show_hvac_modes !== false && hasHvacModes;
+ const showFanModes = this.config.show_fan_modes !== false && hasFanModes;
+ const showSwingModes = this.config.show_swing_modes !== false && hasSwingModes;
+ const showPresetModes = this.config.show_preset_modes !== false && hasPresetModes;
+ const showWaterModes = this.config.show_water_modes !== false && hasWaterModes;
+
+ const fanModes = attrs.fan_modes || [];
+ const modeCount = fanModes.length;
+ const currentFanMode = attrs.fan_mode;
+ let fanSpeed = '2s';
+
+ if (modeCount > 0 && currentFanMode) {
+ const minSpeed = 2;
+ const maxSpeed = 0.5;
+ const speedStep = modeCount > 1 ? (minSpeed - maxSpeed) / (modeCount - 1) : 0;
+ const currentIndex = fanModes.indexOf(currentFanMode);
+ if (currentIndex >= 0) {
+ fanSpeed = (minSpeed - (currentIndex * speedStep)).toFixed(1) + 's';
+ }
+ }
+
+ // 动态计算总高度:基础高度310px(包含thermostat容器265px),每个启用模式区域增加48px
+ const activeModeCount = (showHvacModes ? 1 : 0) +
+ (showFanModes ? 1 : 0) +
+ (showPresetModes ? 1 : 0) +
+ (showSwingModes ? 1 : 0) +
+ (showWaterModes ? 1 : 0);
+ const cardHeight = 300 + (activeModeCount * 48);
+
+ // 判断是否有定时器和附加按钮
+ const hasTimer = this.config.timer;
+ const hasButtons = this.buttons && this.buttons.length > 0;
+ const hasButtons2 = this.buttons2 && this.buttons2.length > 0;
+ const timerPosition = this.config.timer_position || 'left';
+ const buttonPosition = this.config.button_position || 'left';
+ const button2Position = this.config.button2_position || 'left';
+
+ return html`
+ ${hasTimer && timerPosition === 'left' ? html`
+
+ ` : ''}
+
+ ${hasButtons2 && button2Position === 'left' ? html`
+
+ ` : ''}
+
+ ${hasButtons && buttonPosition === 'left' ? html`
+
+ ` : ''}
+
+
+
+
+
+ ${showHvacModes ? html`
+
+
+ ${this._renderModeButtons(attrs.hvac_modes, state)}
+
+
+ ` : ''}
+
+ ${showFanModes ? html`
+
+
+ ${this._renderFanButtons(attrs.fan_modes, attrs.fan_mode)}
+
+
+ ` : ''}
+
+ ${showPresetModes ? html`
+
+
+ ${this._renderPresetButtons(attrs.preset_modes, attrs.preset_mode)}
+
+
+ ` : ''}
+
+ ${showSwingModes ? html`
+
+
+ ${this._renderSwingButtons(attrs.swing_modes, attrs.swing_mode)}
+
+
+ ` : ''}
+
+ ${showWaterModes ? html`
+
+
+ ${this._renderWaterButtons(attrs.operation_list, attrs.operation_mode)}
+
+
+ ` : ''}
+
+
+
+ ${hasButtons && buttonPosition === 'right' ? html`
+
+ ` : ''}
+
+ ${hasButtons2 && button2Position === 'right' ? html`
+
+ ` : ''}
+
+ ${hasTimer && timerPosition === 'right' ? html`
+
+ ` : ''}
+ `;
+ }
+
+ _renderTimerButton() {
+ const timerEntity = this.hass.states[this.config.timer];
+ if (!timerEntity) return html``;
+
+ const now = new Date();
+ const finishesAt = new Date(timerEntity.attributes.finishes_at || 0);
+ let remainingSeconds = Math.max(0, Math.floor((finishesAt - now) / 1000));
+
+ const state = timerEntity.state;
+ if (state !== 'active') {
+ remainingSeconds = 0;
+ }
+
+ const hours = Math.floor(remainingSeconds / 3600);
+ const minutes = Math.floor((remainingSeconds % 3600) / 60);
+
+ const theme = this._evaluateTheme();
+ const fgColor = theme === 'on' ? 'rgb(0, 0, 0)' : 'rgb(255, 255, 255)';
+ const bgColor = theme === 'on' ? 'rgb(230, 230, 230)' : 'rgb(80, 80, 80)';
+
+ const climateEntity = this.hass.states[this.config.entity];
+ const climateState = climateEntity ? climateEntity.state : 'off';
+
+ let activeColor = bgColor;
+ if (remainingSeconds > 0) {
+ if (climateState === 'cool') activeColor = 'rgb(33,150,243)';
+ else if (climateState === 'heat') activeColor = 'rgb(254,111,33)';
+ else if (climateState === '自定义' || climateState === 'AI控温' ||
+ climateState === '婴童洗' || climateState === '舒适洗' ||
+ climateState === '宠物洗' || climateState === '厨房用') activeColor = 'rgb(254,111,33)';
+ else if (climateState === 'dry') activeColor = 'rgb(255,151,0)';
+ else if (climateState === 'fan' || climateState === 'fan_only') activeColor = 'rgb(0,188,213)';
+ else if (climateState === 'auto') activeColor = 'rgb(147,112,219)';
+ }
+
+ return html`
+
+
+
+
+
+
+
+ `;
+ }
+
+ _handleClick() {
+ navigator.vibrate(50);
+ }
+
+ _formatSeconds(totalSeconds) {
+ const hours = Math.floor(totalSeconds / 3600);
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
+ const seconds = totalSeconds % 60;
+
+ return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
+ }
+
+ _getTimerAdjustAmount(currentSeconds, direction) {
+ const currentMinutes = Math.ceil(currentSeconds / 60);
+
+ if (direction === -1) {
+ if (currentMinutes > 30) return '30分';
+ if (currentMinutes > 10) return '10分';
+ return '取消';
+ } else {
+ if (currentSeconds === 0) return '10分';
+ if (currentMinutes < 30) return '10分';
+ if (currentMinutes < 180) return '30分';
+ return '1小时';
+ }
+ }
+
+ _adjustTimer(direction, currentSeconds) {
+ if (!this.config.timer) return;
+
+ const currentMinutes = Math.ceil(currentSeconds / 60);
+ let newSeconds = 0;
+
+ if (direction === -1) {
+ if (currentMinutes > 30) {
+ newSeconds = currentSeconds - (30 * 60);
+ } else if (currentMinutes > 10) {
+ newSeconds = currentSeconds - (10 * 60);
+ } else {
+ this._cancelTimer();
+ return;
+ }
+ } else {
+ if (currentSeconds === 0) {
+ newSeconds = 10 * 60;
+ } else if (currentMinutes < 30) {
+ newSeconds = currentSeconds + (10 * 60);
+ } else if (currentMinutes < 180) {
+ newSeconds = currentSeconds + (30 * 60);
+ } else {
+ newSeconds = currentSeconds + (60 * 60);
+ }
+ }
+
+ this._setTimer(newSeconds);
+ }
+
+ _cancelTimer() {
+ if (!this.config.timer) return;
+ this._callService('timer', 'cancel', {
+ entity_id: this.config.timer
+ });
+ }
+
+ _setTimer(totalSeconds) {
+ if (!this.config.timer) return;
+ const now = new Date();
+ const finishesAt = new Date(now.getTime() + totalSeconds * 1000);
+ if (this.hass.states[this.config.timer].state === 'active') {
+ this._callService('timer', 'cancel', {
+ entity_id: this.config.timer
+ });
+ }
+ this._callService('timer', 'start', {
+ entity_id: this.config.timer,
+ duration: this._formatSeconds(totalSeconds)
+ });
+ }
+
+
+_renderExtraButtons(buttonType = 1) {
+ const buttonArray = buttonType === 1 ? this.buttons : this.buttons2;
+ if (!buttonArray || buttonArray.length === 0) return html``;
+
+ const buttonsToShow = buttonArray.slice(0, 7);
+ const entity = this.hass.states[this.config.entity];
+ if (!entity) {
+ return html`实体未找到: ${this.config.entity}
`;
+ }
+
+ const state = entity?.state || 'off';
+ const theme = this._evaluateTheme();
+ const fgColor = theme === 'on' ? 'rgb(0, 0, 0)' : 'rgb(255, 255, 255)';
+ const bgColor = theme === 'on' ? 'rgb(230, 230, 230)' : 'rgb(80, 80, 80)';
+ let activeColor = theme === 'on' ? 'rgba(0, 188, 213)' : 'rgba(0, 188, 213)';
+ if (state === 'cool') activeColor = 'rgb(33,150,243)';
+ else if (state === 'heat') activeColor = 'rgb(254,111,33)';
+ else if (state === '自定义' || state === 'AI控温' || state === '婴童洗' || state === '舒适洗' || state === '宠物洗' || state === '厨房用') activeColor = 'rgb(254,111,33)';
+ else if (state === 'dry') activeColor = 'rgb(255,151,0)';
+ else if (state === 'fan' || state === 'fan_only') activeColor = 'rgb(0,188,213)';
+ else if (state === 'auto') activeColor = 'rgb(147,112,219)';
+
+ return buttonsToShow.map(buttonEntityId => {
+ const entity = this.hass.states[buttonEntityId];
+ if (!entity) return html``;
+
+ const domain = buttonEntityId.split('.')[0];
+ const friendlyName = entity.attributes.friendly_name || '';
+ const displayName = friendlyName.slice(0, 4);
+ const displayValueColor = entity.state === '低' ? 'red' : fgColor;
+
+ // 根据名称自定义图标
+ const _getCustomIcon = (name, isActive) => {
+ if (name.includes('辅热')) return 'mdi:heating-coil';
+ if (name.includes('干燥')) return 'mdi:heat-wave';
+ if (name.includes('节能')) return isActive ? 'mdi:leaf' : 'mdi:leaf-off';
+ if (name.includes('睡眠')) return isActive ? 'mdi:sleep' : 'mdi:sleep-off';
+ if (name.includes('指示灯')) return isActive ? 'mdi:lightbulb-on' : 'mdi:lightbulb-off';
+ if (name.includes('提示音')) return isActive ? 'mdi:volume-high' : 'mdi:volume-mute';
+ return null;
+ };
+
+ switch(domain) {
+ case 'switch':
+ case 'light':
+ const isActive = entity.state === 'on';
+ const customIcon = _getCustomIcon(friendlyName, isActive);
+ const icon = customIcon || (isActive ? 'mdi:toggle-switch' : 'mdi:toggle-switch-off');
+ const buttonColor = isActive ? activeColor : fgColor;
+
+ return html`
+
+ `;
+
+ case 'sensor':
+ const unit = entity.attributes.unit_of_measurement || '';
+ const displayValue = `${entity.state}${unit}`.slice(0, 4);
+
+ return html`
+
+ `;
+
+ case 'button':
+ const buttonIcon = 'mdi:button-pointer';
+
+ return html`
+
+ `;
+
+ case 'select':
+ const options = entity.attributes.options || [];
+ const firstOption = options[0] || '';
+ const selectDisplayValue = firstOption.slice(0, 4);
+
+ return html`
+
+ `;
+
+ default:
+ return html``;
+ }
+ });
+}
+
+ _handleExtraButtonClick(entityId, domain) {
+ const entity = this.hass.states[entityId];
+ if (!entity) return;
+
+ switch(domain) {
+ case 'switch':
+ case 'light':
+ const service = entity.state === 'on' ? 'turn_off' : 'turn_on';
+ this._callService(domain, service, { entity_id: entityId });
+ break;
+
+ case 'button':
+ this._callService('button', 'press', { entity_id: entityId });
+ break;
+
+ case 'select':
+ this._callService('select', 'select_next', { entity_id: entityId });
+ break;
+ }
+
+ this._handleClick();
+ }
+
+ _getSwingIcon(mode) {
+ const swingIcons = {
+ 'off': 'mdi:arrow-oscillating-off',
+ 'vertical': 'mdi:arrow-up-down',
+ 'horizontal': 'mdi:arrow-left-right',
+ 'both': 'mdi:arrow-all',
+ '🔄': 'mdi:autorenew',
+ '⬅️': 'mdi:arrow-left',
+ '⬆️': 'mdi:arrow-up',
+ '➡️': 'mdi:arrow-right',
+ '⬇️': 'mdi:arrow-down',
+ '↖️': 'mdi:arrow-top-left',
+ '↗️': 'mdi:arrow-top-right',
+ '↘️': 'mdi:arrow-bottom-right',
+ '↙️': 'mdi:arrow-bottom-left',
+ '↔️': 'mdi:arrow-left-right',
+ '↕️': 'mdi:arrow-up-down',
+ '←': 'mdi:arrow-left',
+ '↑': 'mdi:arrow-up',
+ '→': 'mdi:arrow-right',
+ '↓': 'mdi:arrow-down',
+ '↖': 'mdi:arrow-top-left',
+ '↗': 'mdi:arrow-top-right',
+ '↘': 'mdi:arrow-bottom-right',
+ '↙': 'mdi:arrow-bottom-left',
+ '↔': 'mdi:arrow-left-right',
+ '↕': 'mdi:arrow-up-down'
+ };
+ return swingIcons[mode] || '';
+ }
+
+ _getPresetIcon(mode) {
+ const presetIcons = {
+ '普通': 'mdi:radiator',
+ '除螨': 'mdi:radiator',
+ 'none': 'mdi:thermostat',
+ 'comfort': 'mdi:home-heart',
+ 'eco': 'mdi:leaf',
+ 'boost': 'mdi:rocket',
+ 'sleep': 'mdi:power-sleep',
+ 'away': 'mdi:home-export-outline'
+ };
+ return presetIcons[mode] || '';
+ }
+
+ _getWaterIcon(mode) {
+ const waterIcons = {
+ '自定义': 'mdi:pencil',
+ 'AI控温': 'mdi:water-boiler-auto',
+ '婴童洗': 'mdi:human-baby-changing-table',
+ '舒适洗': 'mdi:hand-heart',
+ '宠物洗': 'mdi:cat',
+ '厨房用': 'mdi:countertop'
+ };
+ return '';
+ }
+
+ _renderModeButtons(modes, currentMode) {
+ if (!modes) return html``;
+
+ const entity = this.hass.states[this.config.entity];
+ const state = entity ? entity.state : 'off';
+ const theme = this._evaluateTheme();
+
+ const modeIcons = {
+ 'auto': 'mdi:thermostat-auto',
+ 'heat': 'mdi:fire',
+ 'cool': 'mdi:snowflake',
+ 'dry': 'mdi:water-percent',
+ 'fan_only': 'mdi:fan',
+ 'fan': 'mdi:fan',
+ 'off': 'mdi:power'
+ };
+
+ return modes.map(mode => {
+ const isActive = mode === currentMode;
+ let bgColor = 'rgb(0,0,0,0)';
+ const fgColor = theme === 'on' ? 'rgb(0, 0, 0)' : 'rgb(255, 255, 255)';
+ if (isActive) {
+ if (state === 'cool' && mode === 'cool') bgColor = 'rgb(33,150,243)';
+ else if (state === 'heat' && mode === 'heat') bgColor = 'rgb(254,111,33)';
+ else if (state === 'dry' && mode === 'dry') bgColor = 'rgb(255,151,0)';
+ else if (state === 'fan_only' && mode === 'fan_only') bgColor = 'rgb(0,188,213)';
+ else if (state === 'off' && mode === 'off') bgColor = theme === 'on' ? 'rgb(180,180,180)' : 'rgb(150,150,150)';
+ }
+
+ return html`
+
+ `;
+ });
+ }
+
+ _renderFanButtons(fanModes, currentFanMode) {
+ if (!fanModes || fanModes.length === 0) return html``;
+
+ const entity = this.hass.states[this.config.entity];
+ const state = entity ? entity.state : 'off';
+ const theme = this._evaluateTheme();
+
+ return fanModes.map((mode, index) => {
+ const isActive = mode === currentFanMode;
+ let bgColor = 'rgb(0,0,0)';
+
+ if (isActive) {
+ if (state === 'cool') bgColor = 'rgb(33,150,243)';
+ else if (state === 'heat') bgColor = 'rgb(254,111,33)';
+ else if (state === 'dry') bgColor = 'rgb(255,151,0)';
+ else if (state === 'fan_only') bgColor = 'rgb(0,188,213)';
+ else if (state === 'auto') bgColor = 'rgb(147,112,219)';
+ else if (state === 'off') bgColor = theme === 'on' ? 'rgb(180,180,180)' : 'rgb(150,150,150)';
+ }
+
+ return html`
+
+ `;
+ });
+ }
+
+ _renderSwingButtons(swingModes, currentSwingMode) {
+ if (!swingModes) return html``;
+
+ const entity = this.hass.states[this.config.entity];
+ const state = entity ? entity.state : 'off';
+ const theme = this._evaluateTheme();
+
+ return swingModes.map(mode => {
+ const isActive = mode === currentSwingMode;
+ let bgColor = 'rgb(0,0,0,0)';
+
+ if (isActive) {
+ if (state === 'cool') bgColor = 'rgb(33,150,243)';
+ else if (state === 'heat') bgColor = 'rgb(254,111,33)';
+ else if (state === 'dry') bgColor = 'rgb(255,151,0)';
+ else if (state === 'fan_only') bgColor = 'rgb(0,188,213)';
+ else if (state === 'off') bgColor = theme === 'on' ? 'rgb(180,180,180)' : 'rgb(150,150,150)';
+ }
+
+ return html`
+
+ `;
+ });
+ }
+
+ _renderPresetButtons(presetModes, currentPresetMode) {
+ if (!presetModes) return html``;
+
+ const entity = this.hass.states[this.config.entity];
+ const state = entity ? entity.state : 'off';
+ const theme = this._evaluateTheme();
+
+ return presetModes.map(mode => {
+ const isActive = mode === currentPresetMode;
+ let bgColor = 'rgb(0,0,0,0)';
+
+ if (isActive) {
+ if (state === 'cool') bgColor = 'rgb(33,150,243)';
+ else if (state === 'heat') bgColor = 'rgb(254,111,33)';
+ else if (state === 'dry') bgColor = 'rgb(255,151,0)';
+ else if (state === 'fan_only') bgColor = 'rgb(0,188,213)';
+ else if (state === 'off') bgColor = theme === 'on' ? 'rgb(180,180,180)' : 'rgb(150,150,150)';
+ }
+
+ return html`
+
+ `;
+ });
+ }
+
+ _renderWaterButtons(operation_list, operation_mode) {
+ if (!operation_list) return html``;
+
+ const entity = this.hass.states[this.config.entity];
+ const state = entity ? entity.state : 'off';
+ const theme = this._evaluateTheme();
+
+ return operation_list.map(mode => {
+ const isActive = mode === operation_mode;
+ let bgColor = 'rgb(0,0,0,0)';
+
+ if (isActive) {
+ if (state === 'cool') bgColor = 'rgb(33,150,243)';
+ else if (state === 'heat') bgColor = 'rgb(254,111,33)';
+ else if (state === 'dry') bgColor = 'rgb(255,151,0)';
+ else if (state === 'fan_only') bgColor = 'rgb(0,188,213)';
+ else if (state === 'off') bgColor = theme === 'on' ? 'rgb(180,180,180)' : 'rgb(150,150,150)';
+ }
+
+ return html`
+
+ `;
+ });
+ }
+
+ _translateMode(mode) {
+ const translations = {
+ 'cool': '制冷',
+ 'heat': '制热',
+ 'dry': '除湿',
+ 'fan_only': '吹风',
+ 'fan': '吹风',
+ 'auto': '自动',
+ 'off': '关闭'
+ };
+ return translations[mode] || mode;
+ }
+
+ _translateFanMode(mode) {
+ if (mode.includes('自动') || mode.includes('auto')) return 'A';
+ if (mode.includes('一') || mode.includes('1')) return '1';
+ if (mode.includes('二') || mode.includes('2')) return '2';
+ if (mode.includes('三') || mode.includes('3')) return '3';
+ if (mode.includes('四') || mode.includes('4')) return '4';
+ if (mode.includes('五') || mode.includes('5')) return '5';
+ if (mode.includes('六') || mode.includes('6')) return '6';
+ if (mode.includes('七') || mode.includes('7')) return '7';
+ if (mode.includes('silent') || mode.includes('静')) return '静';
+ if (mode.includes('low') || mode.includes('低')) return '低';
+ if (mode.includes('稍弱')) return '弱';
+ if (mode.includes('稍强')) return '强';
+ if (mode.includes('medium') || mode.includes('中')) return '中';
+ if (mode.includes('high') || mode.includes('高')) return '高';
+ if (mode.includes('full') || mode.includes('全')) return '全';
+ if (mode.includes('最大') || mode.includes('max')|| mode.includes('Max')) return 'M';
+ return mode;
+ }
+
+ _translateSwingMode(mode) {
+ const arrowSymbols = new Set([
+ '🔄', '⬅️', '⬆️', '➡️', '⬇️','↔️','↕️','↖️', '↗️', '↘️', '↙️',
+ '←', '↑', '→', '↓', '↔', '↕','↖', '↗', '↘', '↙'
+ ]);
+ if (arrowSymbols.has(mode)) return '';
+
+ const translations = {
+ 'off': '\u00A0\u00A0关闭',
+ 'vertical': '\u00A0\u00A0垂直',
+ 'horizontal': '\u00A0\u00A0水平',
+ 'both': '\u00A0\u00A0立体',
+ };
+ return translations[mode] || mode;
+ }
+
+ _translatePresetMode(mode) {
+ const translations = {
+ '普通': '\u00A0\u00A0普通',
+ '除螨': '\u00A0\u00A0除螨',
+ 'none': '\u00A0基础',
+ 'comfort': '\u00A0舒适',
+ 'eco': '\u00A0节能',
+ 'boost': '\u00A0强力',
+ 'sleep': '\u00A0睡眠',
+ 'away': '\u00A0离家',
+ };
+ return translations[mode] || mode;
+ }
+
+ _setHvacMode(mode) {
+ this._callService('climate', 'set_hvac_mode', {
+ entity_id: this.config.entity,
+ hvac_mode: mode
+ });
+ this._handleClick();
+ }
+
+ _setFanMode(mode) {
+ this._callService('climate', 'set_fan_mode', {
+ entity_id: this.config.entity,
+ fan_mode: mode
+ });
+ this._handleClick();
+ }
+
+ _setSwingMode(mode) {
+ this._callService('climate', 'set_swing_mode', {
+ entity_id: this.config.entity,
+ swing_mode: mode
+ });
+ this._handleClick();
+ }
+
+ _setPresetMode(mode) {
+ this._callService('climate', 'set_preset_mode', {
+ entity_id: this.config.entity,
+ preset_mode: mode
+ });
+ this._handleClick();
+ }
+
+ _setWaterMode(mode) {
+ this._callService('water_heater', 'set_operation_mode', {
+ entity_id: this.config.entity,
+ operation_mode: mode
+ });
+ this._handleClick();
+ }
+
+ _callService(domain, service, data) {
+ this.hass.callService(domain, service, data);
+ this._handleClick();
+ }
+}
+
+customElements.define('xiaoshi-pad-climate-card', XiaoshiPadClimateCard);
\ No newline at end of file