diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000..3387332 --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,18 @@ +name: Validate + +on: + push: + pull_request: + schedule: + - cron: "0 0 * * *" + workflow_dispatch: + +jobs: + validate-hacs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: HACS Validation + uses: hacs/action@main + with: + category: plugin diff --git a/README.md b/README.md index 0f246b8..c2cc01a 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ temperature_entity: sensor.air_quality_temperature air_quality_entity: sensor.air_quality_index recommendation_entity: sensor.air_quality_recommendation hours_to_show: 24 +temperature_unit: C ``` ### Configuration Options @@ -69,6 +70,7 @@ hours_to_show: 24 | `air_quality_entity` | string | No | - | Overall air quality index entity | | `recommendation_entity` | string | No | - | Recommendation template sensor | | `hours_to_show` | number | No | 24 | Hours of history to display (1-168) | +| `temperature_unit` | string | No | "F" | Temperature unit: "F" (Fahrenheit) or "C" (Celsius) | ## Recommendation Sensor diff --git a/air-quality-card.js b/air-quality-card.js index 1492551..81b4ba0 100644 --- a/air-quality-card.js +++ b/air-quality-card.js @@ -6,7 +6,7 @@ * https://github.com/KadenThomp36/air-quality-card */ -const CARD_VERSION = '2.1.0'; +const CARD_VERSION = '2.2.0'; class AirQualityCard extends HTMLElement { // Visual editor using getConfigForm (preferred modern approach) @@ -21,6 +21,13 @@ class AirQualityCard extends HTMLElement { { name: 'pm25_entity', selector: { entity: { domain: 'sensor' } } }, ] }, + { + type: 'grid', + schema: [ + { name: 'hcho_entity', selector: { entity: { domain: 'sensor' } } }, + { name: 'tvoc_entity', selector: { entity: { domain: 'sensor' } } }, + ] + }, { type: 'grid', schema: [ @@ -35,6 +42,7 @@ class AirQualityCard extends HTMLElement { { name: 'air_quality_entity', selector: { entity: { domain: 'sensor' } } }, { name: 'recommendation_entity', selector: { entity: { domain: 'sensor' } } }, { name: 'hours_to_show', selector: { number: { min: 1, max: 168, mode: 'box', unit_of_measurement: 'hours' } } }, + { name: 'temperature_unit', selector: { select: { options: [{ value: 'F', label: 'Fahrenheit (°F)' }, { value: 'C', label: 'Celsius (°C)' }], mode: 'dropdown' } } }, ] } ], @@ -46,8 +54,11 @@ class AirQualityCard extends HTMLElement { humidity_entity: 'Humidity Sensor (optional)', temperature_entity: 'Temperature Sensor (optional)', air_quality_entity: 'Air Quality Index (optional)', + hcho_entity: 'Formaldehyde (HCHO; CH2O) Sensor (optional)', + tvoc_entity: 'Volatile Organic Compounds (tVOC) Sensor (optional)', recommendation_entity: 'Recommendation Sensor (optional)', - hours_to_show: 'Graph History' + hours_to_show: 'Graph History', + temperature_unit: 'Temperature Unit' }; return labels[schema.name] || schema.name; } @@ -89,6 +100,7 @@ class AirQualityCard extends HTMLElement { this._config = { name: 'Air Quality', hours_to_show: 24, + temperature_unit: 'F', ...config }; this._rendered = false; @@ -109,6 +121,8 @@ class AirQualityCard extends HTMLElement { let size = 3; // Base size for header and recommendation if (this._config.co2_entity) size += 1; if (this._config.pm25_entity) size += 1; + if (this._config.hcho_entity) size += 1; + if (this._config.tvoc_entity) size += 1; if (this._config.humidity_entity) size += 1; if (this._config.temperature_entity) size += 1; return size; @@ -132,6 +146,14 @@ class AirQualityCard extends HTMLElement { promises.push(this._fetchHistory(this._config.pm25_entity, startTime, endTime)); keys.push('pm25'); } + if (this._config.hcho_entity) { + promises.push(this._fetchHistory(this._config.hcho_entity, startTime, endTime)); + keys.push('hcho'); + } + if (this._config.tvoc_entity) { + promises.push(this._fetchHistory(this._config.tvoc_entity, startTime, endTime)); + keys.push('voc'); + } if (this._config.humidity_entity) { promises.push(this._fetchHistory(this._config.humidity_entity, startTime, endTime)); keys.push('humidity'); @@ -196,6 +218,22 @@ class AirQualityCard extends HTMLElement { return '#f44336'; } + _getHCHOColor(value) { + if (value < 20) return '#4caf50'; + if (value < 50) return '#8bc34a'; + if (value < 100) return '#ffc107'; + if (value < 200) return '#ff9800'; + return '#f44336'; + } + + _getTVOCColor(value) { + if (value < 100) return '#4caf50'; + if (value < 300) return '#8bc34a'; + if (value < 500) return '#ffc107'; + if (value < 1000) return '#ff9800'; + return '#f44336'; + } + _getHumidityColor(value) { if (value < 30) return '#ff9800'; if (value < 40) return '#8bc34a'; @@ -204,7 +242,22 @@ class AirQualityCard extends HTMLElement { return '#ff9800'; } + _isCelsius() { + return this._config.temperature_unit === 'C'; + } + + _getTempUnit() { + return this._isCelsius() ? '°C' : '°F'; + } + _getTempColor(value) { + if (this._isCelsius()) { + if (value < 18) return '#2196f3'; + if (value < 20) return '#03a9f4'; + if (value < 22) return '#4caf50'; + if (value < 24) return '#ff9800'; + return '#f44336'; + } if (value < 65) return '#2196f3'; if (value < 68) return '#03a9f4'; if (value < 72) return '#4caf50'; @@ -284,6 +337,8 @@ class AirQualityCard extends HTMLElement { _initialRender() { const showCO2 = !!this._config.co2_entity; const showPM25 = !!this._config.pm25_entity; + const showHCHO = !!this._config.hcho_entity; + const showTVOC = !!this._config.tvoc_entity; const showHumidity = !!this._config.humidity_entity; const showTemp = !!this._config.temperature_entity; @@ -546,6 +601,45 @@ class AirQualityCard extends HTMLElement {
` : ''} + ${showHCHO ? ` +