From b9aa9aeb94db858417c17e2dec9c0c507ea3dbea Mon Sep 17 00:00:00 2001 From: Emmanuel Berthier Date: Tue, 2 Jan 2024 19:21:25 +0100 Subject: [PATCH] Use async_forecast and manage hourly&daily forecasts on same card. Rework Card Editor: French translation, paper-input deprecated, reordering, all fields defaulted Signed-off-by: Emmanuel Berthier --- README.md | 59 +++++----- dist/meteofrance-weather-card-editor.js | 149 ++++++++++++++++-------- dist/meteofrance-weather-card.js | 137 +++++++++++++++++++--- meteofrance-weather-card-editor.png | Bin 91887 -> 462631 bytes meteofrance-weather-card.png | Bin 31604 -> 277430 bytes 5 files changed, 250 insertions(+), 95 deletions(-) mode change 100644 => 100755 dist/meteofrance-weather-card-editor.js mode change 100644 => 100755 dist/meteofrance-weather-card.js diff --git a/README.md b/README.md index 82ceba58..de9acd12 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Les informations affichées : - informations météorologiques détaillées, - pluviométrie dans l'heure (prévisions à 5 puis 10 minutes), - alertes météos en cours (inondations, vents violents, etc) en rapport à votre département, -- prévisions météo quotidienne de 1 à 15 jours maximum (réglable) ou des prévisions horaires de 1 à x heures (réglable), +- prévisions météo quotidiennes de 1 à 15 jours maximum (réglable) et des prévisions horaires de 1 à x heures (réglable), - sélection des informations à afficher pour personnaliser votre carte. Un exemple de rendu : @@ -59,19 +59,15 @@ Vous trouverez la carte dans la liste des cartes personnalisées (en fin de list Une fois choisi, sa configuration est la suivante : -1. **Définir un nom** pour la carte (généralement la ville, comme pour l'intégration). +1. **Sélectionner l'entité météo** que vous avez défini avec l'intégration (par défaut la carte en choisit une mais ce n'est pas forcément l'entité météo france que vous avez configuré). -2. **Sélectionner l'entité météo** que vous avez définit avec l'intégration (par défaut la carte en choisit une mais ce n'est pas forcément l'entité météo france que vous avez configuré). +2. Toutes les autres entités **sont automatiquement définies** mais vous pouvez les redéfinir ou les supprimer à votre guise. -3. Toutes les autres entités **sont automatiquement définies** mais vous pouvez les redéfinir ou les supprimer à votre guise. +3. **Sélectionner les éléments** de la carte **à afficher** (vous pouvez ainsi avoir plusieurs cartes avec des affichages différents). -4. Seule l'entité pour **les alertes est à préciser manuellement**. +4. **Préciser les nombres d'heures et de jours de prévision** à afficher. -5. **Sélectionner les parties** de la carte **à afficher** (vous pouvez ainsi avoir plusieurs cartes avec des affichages différents). - -6. **Préciser le nombre de jours de prévision** à afficher en bas de carte, maximum 5. - -7. `Enregistrer` votre configuration. +5. `Enregistrer` votre configuration. ![Weather Card Configuration](https://github.com/hacf-fr/lovelace-meteofrance-weather-card/blob/Meteo-France/meteofrance-weather-card-editor.png) @@ -117,23 +113,32 @@ Ci-dessous les éléments de configuration avec pour exemple l'usage d'une inté view: cards: - type: "custom:meteofrance-weather-card" - name: Nantes # nom de la carte, peut être différent du nom de l'intégration - entity: weather.nantes # Entité météo principale - # Les entités annexes de météo france - cloudCoverEntity: sensor.nantes_cloud_cover - rainChanceEntity: sensor.nantes_rain_chance - freezeChanceEntity: sensor.nantes_freeze_chance - snowChanceEntity: sensor.nantes_snow_chance - uvEntity: sensor.nantes_uv - rainForecastEntity: sensor.nantes_next_rain - alertEntity: sensor.44_weather_alert - number_of_forecasts: "5" - # Les switches pour afficher ou non les différentes zones. - current: true - details: true - one_hour_forecast: true - alert_forecast: true - forecast: true + entity: weather.nantes # Entité météo principale + name: Nantes # nom de la carte, peut être différent du nom de l'intégration + # Les switches pour afficher ou non les différentes zones. + current: true + details: true + alert_forecast: true + one_hour_forecast: true + daily_forecast: true + hourly_forecast: true + humidity_forecast: true + wind_forecast_icons: true + animated_icons: true + # Les curseurs + number_of_hourly_forecasts: "5" + number_of_daily_forecasts: "5" + # Les entités annexes de météo france + detailEntity: sensor.nantes_daily_precipitation + cloudCoverEntity: sensor.nantes_cloud_cover + rainChanceEntity: sensor.nantes_rain_chance + freezeChanceEntity: sensor.nantes_freeze_chance + snowChanceEntity: sensor.nantes_snow_chance + uvEntity: sensor.nantes_uv + rainForecastEntity: sensor.nantes_next_rain + alertEntity: sensor.44_weather_alert + # Chemin + icons: /local/community/lovelace-meteofrance-weather-card/icons/ ``` #### options avancées via YAML diff --git a/dist/meteofrance-weather-card-editor.js b/dist/meteofrance-weather-card-editor.js old mode 100644 new mode 100755 index 54f0d1fa..a27b5b77 --- a/dist/meteofrance-weather-card-editor.js +++ b/dist/meteofrance-weather-card-editor.js @@ -31,6 +31,7 @@ const css = LitElement.prototype.css; const HELPERS = window.loadCardHelpers(); const DefaultSensors = new Map([ + ["detailEntity", "_rain_chance"], ["cloudCoverEntity", "_cloud_cover"], ["rainChanceEntity", "_rain_chance"], ["freezeChanceEntity", "_freeze_chance"], @@ -42,6 +43,12 @@ const DefaultSensors = new Map([ export class MeteofranceWeatherCardEditor extends LitElement { setConfig(config) { this._config = { ...config }; + + // Set default sub-entities at first Init (when there are only "entity" & "type" in config) + if (Object.keys(config).length === 2 && config.entity !== undefined) { + this._weatherEntityChanged(config.entity.split(".")[1]); + fireEvent(this, "config-changed", { config: this._config }); + } } static get properties() { @@ -68,12 +75,20 @@ export class MeteofranceWeatherCardEditor extends LitElement { return this._config.details !== false; } - get _forecast() { - return this._config.forecast !== false; + get _daily_forecast() { + return this._config.daily_forecast !== false; } - get _number_of_forecasts() { - return this._config.number_of_forecasts || 5; + get _number_of_daily_forecasts() { + return this._config.number_of_daily_forecasts || 5; + } + + get _hourly_forecast() { + return this._config.hourly_forecast !== false; + } + + get _number_of_hourly_forecasts() { + return this._config.number_of_hourly_forecasts || 5; } // Météo France @@ -97,7 +112,7 @@ export class MeteofranceWeatherCardEditor extends LitElement { get _humidity_forecast() { return this._config.humidity_forecast !== false; } - // Config value + get _alertEntity() { return this._config.alertEntity || ""; } @@ -146,70 +161,57 @@ export class MeteofranceWeatherCardEditor extends LitElement { return html`
- + + ${this.renderWeatherPicker("Entité", this._entity, "entity")} + ${this.renderTextField("Nom", this._name, "name")} ${this.renderSensorPicker( "Détail", this._detailEntity, "detailEntity" )} - - - ${this.renderWeatherPicker("Entity", this._entity, "entity")}
    - ${this.renderSwitchOption("Show current", this._current, "current")} - ${this.renderSwitchOption("Show details", this._details, "details")} + ${this.renderSwitchOption("Météo actuelle", this._current, "current")} + ${this.renderSwitchOption("Détails", this._details, "details")} ${this.renderSwitchOption( - "Show one hour forecast", + "Alertes", + this._alert_forecast, + "alert_forecast" + )} + ${this.renderSwitchOption( + "Pluie dans l'heure", this._one_hour_forecast, "one_hour_forecast" )} ${this.renderSwitchOption( - "Show alert", - this._alert_forecast, - "alert_forecast" + "Prévisions par heure", + this._hourly_forecast, + "hourly_forecast" )} ${this.renderSwitchOption( - "Show forecast", - this._forecast, - "forecast" + "Prévisions par jour", + this._daily_forecast, + "daily_forecast" )} ${this.renderSwitchOption( - "Use animated icons", - this._animated_icons, - "animated_icons" + "Humidité", + this._humidity_forecast, + "humidity_forecast" )} ${this.renderSwitchOption( - "Show wind icons", + "Girouette", this._wind_forecast_icons, "wind_forecast_icons" - )} + )} ${this.renderSwitchOption( - "Show humidity forecast", - this._humidity_forecast, - "humidity_forecast" - )} + "Icones animées", + this._animated_icons, + "animated_icons" + )}
- + ${this.renderNumberField("Nombres d'heures", this._number_of_hourly_forecasts, "number_of_hourly_forecasts")} + ${this.renderNumberField("Nombres de jours", this._number_of_daily_forecasts, "number_of_daily_forecasts")} ${this.renderSensorPicker( "Risque de pluie", @@ -242,11 +244,32 @@ export class MeteofranceWeatherCardEditor extends LitElement { this._rainForecastEntity, "rainForecastEntity" )} + ${this.renderTextField("Répertoire des icones", this._icons, "icons")}
`; } + renderTextField(label, state, configAttr) { + return this.renderField(label, state, configAttr, "text"); + } + + renderNumberField(label, state, configAttr) { + return this.renderField(label, state, configAttr, "number"); + } + + renderField(label, state, configAttr, type) { + return html` + + `; + } + renderWeatherPicker(label, entity, configAttr) { return this.renderPicker(label, entity, configAttr, "weather"); } @@ -283,14 +306,42 @@ export class MeteofranceWeatherCardEditor extends LitElement { `; } - _weatherEntityChanged(entityName) { + _weatherEntityChanged(weatherEntityName) { + const weatherEntityNameFull = "weather." + weatherEntityName; + const state = this.hass.states[weatherEntityNameFull]; + if (state !== undefined) { + // Set default Name + const friendly_name = state.attributes.friendly_name; + this._config = { + ...this._config, + ["name"]: friendly_name ? friendly_name : "", + }; + + // Set default Alert sensor + // Find Alert Sensor related to its parent device + const entity = this.hass.entities[weatherEntityNameFull]; + const parent_device_id = entity.device_id; + Object.keys(this.hass.entities).forEach(entityName => { + const entity = this.hass.entities[entityName]; + if (entity !== undefined && entity.device_id === parent_device_id && entityName.split(".")[1].includes("_weather_alert")) { + this._config = { + ...this._config, + ["alertEntity"]: entityName, + }; + return; + } + }); + }; + + // Set default Sensors DefaultSensors.forEach((sensorSuffix, configAttribute) => { - const entity = "sensor." + entityName + sensorSuffix; - if (this.hass.states[entity] !== undefined) + const entity = "sensor." + weatherEntityName + sensorSuffix; + if (this.hass.states[entity] !== undefined) { this._config = { ...this._config, [configAttribute]: entity, }; + }; }); } diff --git a/dist/meteofrance-weather-card.js b/dist/meteofrance-weather-card.js old mode 100644 new mode 100755 index 0c432781..501f0aca --- a/dist/meteofrance-weather-card.js +++ b/dist/meteofrance-weather-card.js @@ -24,6 +24,7 @@ const weatherIconsDay = { }; const DefaultSensors = [ + ["detailEntity", "_rain_chance"], ["cloudCoverEntity", "_cloud_cover"], ["rainChanceEntity", "_rain_chance"], ["freezeChanceEntity", "_freeze_chance"], @@ -140,6 +141,8 @@ class MeteofranceWeatherCard extends LitElement { static get properties() { return { _config: {}, + _dailyForecastEvent: {}, + _hourlyForecastEvent: {}, hass: {}, }; } @@ -192,11 +195,24 @@ class MeteofranceWeatherCard extends LitElement { return entities; } + // Upgrade config fields if necessary + upgradeConfig(config) { + const upgradedConfig = { ...config } + // Deduce "daily_forecast" from deprecated "forecast" + if (config["forecast"] !== undefined && config["daily_forecast"] === undefined) { + upgradedConfig["daily_forecast"] = config["forecast"]; + } + if (config["number_of_forecasts"] !== undefined && config["number_of_daily_forecasts"] === undefined) { + upgradedConfig["number_of_daily_forecasts"] = config["number_of_forecasts"]; + } + return upgradedConfig; + } + setConfig(config) { if (!config.entity) { throw new Error("Please define a weather entity"); } - this._config = config; + this._config = this.upgradeConfig(config); } shouldUpdate(changedProps) { @@ -207,6 +223,92 @@ class MeteofranceWeatherCard extends LitElement { return option === undefined || option === true; } + _unsubscribeDailyForecastEvents() { + if (this._daily_subscribed) { + this._daily_subscribed.then((unsub) => unsub()); + this._daily_subscribed = undefined; + } + } + + _unsubscribeHourlyForecastEvents() { + if (this._hourly_subscribed) { + this._hourly_subscribed.then((unsub) => unsub()); + this._hourly_subscribed = undefined; + } + } + + async _subscribeDailyForecastEvents() { + this._unsubscribeDailyForecastEvents(); + if ( + !this.isConnected || + !this.hass || + !this._config || + !this.isSelected(this._config.daily_forecast) + ) { + return; + } + + this._daily_subscribed = this.hass.connection.subscribeMessage( + (event) => { + this._dailyForecastEvent = event; + }, + { + type: "weather/subscribe_forecast", + forecast_type: "daily", + entity_id: this._config.entity, + } + ); + } + + async _subscribeHourlyForecastEvents() { + this._unsubscribeHourlyForecastEvents(); + if ( + !this.isConnected || + !this.hass || + !this._config || + !this.isSelected(this._config.hourly_forecast) + ) { + return; + } + + this._hourly_subscribed = this.hass.connection.subscribeMessage( + (event) => { + this._hourlyForecastEvent = event; + }, + { + type: "weather/subscribe_forecast", + forecast_type: "hourly", + entity_id: this._config.entity, + } + ); + } + + connectedCallback() { + super.connectedCallback(); + if (this.hasUpdated && this._config && this.hass) { + this._subscribeDailyForecastEvents(); + this._subscribeHourlyForecastEvents(); + } + } + + disconnectedCallback() { + super.disconnectedCallback(); + this._unsubscribeDailyForecastEvents(); + this._unsubscribeHourlyForecastEvents(); + } + + updated(changedProps) { + if (!this.hass || !this._config) { + return; + } + if (changedProps.has("_config") || !this._daily_subscribed) { + this._subscribeDailyForecastEvents(); + } + if (changedProps.has("_config") || !this._hourly_subscribed) { + this._subscribeHourlyForecastEvents(); + } + } + render() { if (!this._config || !this.hass) { return html``; @@ -247,8 +349,11 @@ class MeteofranceWeatherCard extends LitElement { ${this.isSelected(this._config.one_hour_forecast) ? this.renderOneHourForecast() : ""} - ${this.isSelected(this._config.forecast) - ? this.renderForecast(stateObj.attributes.forecast) + ${this.isSelected(this._config.hourly_forecast) + ? this.renderForecast(this._hourlyForecastEvent, this._config.number_of_hourly_forecasts) + : ""} + ${this.isSelected(this._config.daily_forecast) + ? this.renderForecast(this._dailyForecastEvent, this._config.number_of_daily_forecasts) : ""} `; @@ -266,7 +371,7 @@ class MeteofranceWeatherCard extends LitElement { >
  • ${this.getPhenomenaText(stateObj.state, this.isNightTime())} - ${this._config.name ? html`
    ${this._config.name}
    ` : ""} +
    ${this._config.name !== undefined ? this._config.name : ""}
  • ${this.getUnit("temperature") == "°F" @@ -450,23 +555,23 @@ class MeteofranceWeatherCard extends LitElement { `; } - renderForecast(forecast) { - if (!forecast || forecast.length === 0) { + renderForecast(forecast, number_of_forecasts) { + if (!forecast || !forecast.forecast || forecast.forecast.length === 0) { return html``; } const lang = this.hass.selectedLanguage || this.hass.language; - const isDaily = this.isDailyForecast(forecast); + const isDaily = forecast.type === "daily" ; this.numberElements++; return html`