diff --git a/src/events-praser.ts b/src/events-praser.ts index 4862411..12a19b6 100644 --- a/src/events-praser.ts +++ b/src/events-praser.ts @@ -10,6 +10,7 @@ import { MeteoalarmIntegration, MeteoalarmIntegrationEntityType } from './types'; +import { HomeAssistant } from 'custom-card-helpers'; /** * This is the class that stands between integration and rendering code. @@ -24,20 +25,21 @@ class EventsParser { * to integration specified in constructors. The result is additionally processed and * verified then, packed to array of MeteoalarmAlertParsed objects */ - public getEvents( + public async getEvents( + hass: HomeAssistant, entities: HassEntity[], disableSweeper = false, overrideHeadline = false, hideCaption = false, ignoredLevels: string[] = [], ignoredEvents: string[] = [] - ): MeteoalarmAlertParsed[] { + ): Promise { if(this.isAnyEntityUnavailable(entities)) { return [ PredefinedCards.unavailableCard() ]; } - this.checkIfIntegrationSupportsEntities(entities); + await this.checkIfIntegrationSupportsEntities(hass, entities); - let alerts = this.sortAlerts(this.graterAllAlerts(entities)); + let alerts = this.sortAlerts(await this.graterAllAlerts(hass, entities)); this.validateAlert(alerts); alerts = this.filterAlerts(alerts, ignoredLevels, ignoredEvents); @@ -85,13 +87,13 @@ class EventsParser { /** * Call integration for each of the entities and put all alerts in array */ - private graterAllAlerts(entities: HassEntity[]): MeteoalarmAlert[] { + private async graterAllAlerts(hass: HomeAssistant, entities: HassEntity[]): Promise { const alerts: MeteoalarmAlert[] = []; for(const entity of entities) { const active = this.integration.alertActive(entity); if(!active) continue; - let entityAlerts = this.integration.getAlerts(entity); + let entityAlerts = await this.integration.getAlerts(hass, entity); if(!Array.isArray(entityAlerts)) { entityAlerts = [ entityAlerts ]; } @@ -204,16 +206,20 @@ class EventsParser { }); } - private checkIfIntegrationSupportsEntities(entities: HassEntity[]): void { - if(!entities.every(e => this.integration.supports(e))) { + private async checkIfIntegrationSupportsEntities(hass: HomeAssistant, entities: HassEntity[]): Promise { + const supportData = await Promise.all(entities.map(async e => { + const supports = await this.integration.supports(hass, e); + return {entity: e, supports}; + })); + if(!supportData.every(e => e.supports)) { if(entities.length == 1) { throw new Error(localize('error.entity_invalid.single')); } else { - const unsupportedEntities = entities.filter(e => !this.integration.supports(e)); + const unsupportedEntities = supportData.filter(e => !e.supports); throw new Error( localize('error.entity_invalid.multiple') - .replace('{entity}', unsupportedEntities.map(x => x.entity_id).join(', ')) + .replace('{entity}', unsupportedEntities.map(x => x.entity.entity_id).join(', ')) ); } } diff --git a/src/integrations/burze_dzis_net.ts b/src/integrations/burze_dzis_net.ts index ced9c8c..f5a1cdf 100644 --- a/src/integrations/burze_dzis_net.ts +++ b/src/integrations/burze_dzis_net.ts @@ -6,6 +6,8 @@ import { MeteoalarmIntegrationEntityType, MeteoalarmIntegrationMetadata } from '../types'; +import { HomeAssistant } from 'custom-card-helpers'; +import { Utils } from '../utils'; type BurzeDzisNetEntity = HassEntity & { attributes: { @@ -35,18 +37,18 @@ export default class BurzeDzisNet implements MeteoalarmIntegration { }; } - public supports(entity: BurzeDzisNetEntity): boolean { + public async supports(hass: HomeAssistant, entity: BurzeDzisNetEntity): Promise { return ( entity.attributes.attribution == 'Information provided by Burze.dzis.net.' && - this.getEventType(entity) !== undefined); + await this.getEventType(hass, entity) !== undefined); } public alertActive(entity: BurzeDzisNetEntity): boolean { return entity.state === 'on'; } - public getAlerts(entity: BurzeDzisNetEntity): MeteoalarmAlert { - const event = this.getEventType(entity)!; + public async getAlerts(hass: HomeAssistant, entity: BurzeDzisNetEntity): Promise { + const event = (await this.getEventType(hass, entity))!; return { event: event, level: entity.attributes.level!, @@ -54,23 +56,25 @@ export default class BurzeDzisNet implements MeteoalarmIntegration { }; } - private getEventType(entity: HassEntity): MeteoalarmEventType | undefined { - if(entity.entity_id.endsWith('frost_warning') && entity.attributes.friendly_name?.endsWith('Ostrzeżenie - Mróz')) { + private async getEventType(hass: HomeAssistant, entity: HassEntity): Promise { + const entityInfo = await Utils.getEntityInfo(hass, entity); + + if(entityInfo.unique_id.match('binary_sensor_warning_(present|active)_frost_warning')) { return MeteoalarmEventType.LowTemperature; } - else if(entity.entity_id.endsWith('heat_warning') && entity.attributes.friendly_name?.endsWith('Ostrzeżenie - Upał')) { + else if(entityInfo.unique_id.match('binary_sensor_warning_(present|active)_heat_warning')) { return MeteoalarmEventType.HighTemperature; } - else if(entity.entity_id.endsWith('precipitation_warning') && entity.attributes.friendly_name?.endsWith('Ostrzeżenie - Opad')) { + else if(entityInfo.unique_id.match('binary_sensor_warning_(present|active)_precipitation_warning')) { return MeteoalarmEventType.Rain; } - else if(entity.entity_id.endsWith('storm_warning') && entity.attributes.friendly_name?.endsWith('Ostrzeżenie - Burza')) { + else if(entityInfo.unique_id.match('binary_sensor_warning_(present|active)_storm_warning')) { return MeteoalarmEventType.Thunderstorms; } - else if(entity.entity_id.endsWith('tornado_warning') && entity.attributes.friendly_name?.endsWith('Ostrzeżenie - Trąba')) { + else if(entityInfo.unique_id.match('binary_sensor_warning_(present|active)_tornado_warning')) { return MeteoalarmEventType.Tornado; } - else if(entity.entity_id.endsWith('wind_warning') && entity.attributes.friendly_name?.endsWith(' Ostrzeżenie - Wiatr')) { + else if(entityInfo.unique_id.match('binary_sensor_warning_(present|active)_wind_warning')) { return MeteoalarmEventType.Wind; } return undefined; diff --git a/src/integrations/dwd.ts b/src/integrations/dwd.ts index 985026a..26be5ec 100644 --- a/src/integrations/dwd.ts +++ b/src/integrations/dwd.ts @@ -9,6 +9,7 @@ import { MeteoalarmLevelType } from '../types'; import { Utils } from '../utils'; +import { HomeAssistant } from 'custom-card-helpers'; type DWDEntity = HassEntity & { attributes: { @@ -30,7 +31,7 @@ export default class DWD implements MeteoalarmIntegration { }; } - public supports(entity: DWDEntity): boolean { + public async supports(_hass: HomeAssistant, entity: DWDEntity): Promise { return entity.attributes.attribution == 'Data provided by DWD' && this.getEntityKind(entity) !== undefined; } @@ -105,7 +106,7 @@ export default class DWD implements MeteoalarmIntegration { }; } - public getAlerts(entity: HassEntity): MeteoalarmAlert[] { + public async getAlerts(_hass: HomeAssistant, entity: HassEntity): Promise { const { warning_count: warningCount } = entity.attributes; const result: MeteoalarmAlert[] = []; diff --git a/src/integrations/env_canada.ts b/src/integrations/env_canada.ts index 59f4fed..25f44cf 100644 --- a/src/integrations/env_canada.ts +++ b/src/integrations/env_canada.ts @@ -7,6 +7,7 @@ import { MeteoalarmIntegrationMetadata, MeteoalarmLevelType } from '../types'; +import { HomeAssistant } from 'custom-card-helpers'; type EnvCanadaEntity = HassEntity & { attributes: { @@ -37,7 +38,7 @@ export default class EnvironmentCanada implements MeteoalarmIntegration { }; } - public supports(entity: EnvCanadaEntity): boolean { + public async supports(_hass: HomeAssistant, entity: EnvCanadaEntity): Promise { const isStateNumber = !Number.isNaN(Number(entity.state)); return ( [ATTRIBUTION_EN, ATTRIBUTION_FR].includes(entity.attributes.attribution) && @@ -231,7 +232,7 @@ export default class EnvironmentCanada implements MeteoalarmIntegration { }); } - public getAlerts(entity: EnvCanadaEntity): MeteoalarmAlert[] { + public async getAlerts(_hass: HomeAssistant, entity: EnvCanadaEntity): Promise { const warningCount = Number(entity.state); const result: MeteoalarmAlert[] = []; diff --git a/src/integrations/meteoalarm.ts b/src/integrations/meteoalarm.ts index 8d1a0bc..a839096 100644 --- a/src/integrations/meteoalarm.ts +++ b/src/integrations/meteoalarm.ts @@ -8,6 +8,7 @@ import { MeteoalarmLevelType } from '../types'; import { Utils } from '../utils'; +import { HomeAssistant } from 'custom-card-helpers'; type MeteoalarmEntity = HassEntity & { attributes: { @@ -39,7 +40,7 @@ export default class Meteoalarm implements MeteoalarmIntegration { }; } - public supports(entity: MeteoalarmEntity): boolean { + public async supports(_hass: HomeAssistant, entity: MeteoalarmEntity): Promise { return entity.attributes.attribution == 'Information provided by MeteoAlarm'; } @@ -65,7 +66,7 @@ export default class Meteoalarm implements MeteoalarmIntegration { ]; } - public getAlerts(entity: MeteoalarmEntity): MeteoalarmAlert[] { + public async getAlerts(_hass: HomeAssistant, entity: MeteoalarmEntity): Promise { const { event: eventHeadline, headline, diff --git a/src/integrations/meteofrance.ts b/src/integrations/meteofrance.ts index 4c7b468..e834707 100644 --- a/src/integrations/meteofrance.ts +++ b/src/integrations/meteofrance.ts @@ -7,6 +7,7 @@ import { MeteoalarmIntegrationMetadata, MeteoalarmLevelType } from '../types'; +import { HomeAssistant } from 'custom-card-helpers'; const STATE_GREEN = 'Vert'; const STATE_YELLOW = 'Jaune'; @@ -67,7 +68,7 @@ export default class MeteoFrance implements MeteoalarmIntegration { }; } - public supports(entity: HassEntity): boolean { + public async supports(_hass: HomeAssistant, entity: HassEntity): Promise { return entity.attributes.attribution == 'Data provided by Météo-France' && entity.attributes[EVENT_WIND] != undefined; } @@ -75,7 +76,7 @@ export default class MeteoFrance implements MeteoalarmIntegration { return entity.state !== STATE_GREEN; } - public getAlerts(entity: HassEntity): MeteoalarmAlert[] { + public async getAlerts(_hass: HomeAssistant, entity: HassEntity): Promise { const result: MeteoalarmAlert[] = []; for(const [eventName, event] of Object.entries(EVENT_TYPES)) { diff --git a/src/integrations/nina.ts b/src/integrations/nina.ts index 37db358..3873fa9 100644 --- a/src/integrations/nina.ts +++ b/src/integrations/nina.ts @@ -8,6 +8,7 @@ import { MeteoalarmLevelType } from '../types'; import { Utils } from '../utils'; +import { HomeAssistant } from 'custom-card-helpers'; type NinaEntity = HassEntity & { attributes: { @@ -31,7 +32,7 @@ export default class NINA implements MeteoalarmIntegration { }; } - public supports(entity: NinaEntity): boolean { + public async supports(_hass: HomeAssistant, entity: NinaEntity): Promise { // Nina doesn't really provide a good way of verification return ['on', 'off'].includes(entity.state); } @@ -40,7 +41,7 @@ export default class NINA implements MeteoalarmIntegration { return entity.state == 'on'; } - public getAlerts(entity: NinaEntity): MeteoalarmAlert[] { + public async getAlerts(_hass: HomeAssistant, entity: NinaEntity): Promise { const { severity, headline } = entity.attributes; return [{ diff --git a/src/integrations/weatheralerts.ts b/src/integrations/weatheralerts.ts index f4c5233..ba11879 100644 --- a/src/integrations/weatheralerts.ts +++ b/src/integrations/weatheralerts.ts @@ -8,6 +8,7 @@ import { MeteoalarmLevelType } from '../types'; import { Utils } from '../utils'; +import { HomeAssistant } from 'custom-card-helpers'; type WeatheralertsAlert = { event: string, @@ -35,7 +36,7 @@ export default class Weatheralerts implements MeteoalarmIntegration { }; } - public supports(entity: WeatheralertsEntity): boolean { + public async supports(_hass: HomeAssistant, entity: WeatheralertsEntity): Promise { return entity.attributes.integration == 'weatheralerts'; } @@ -95,7 +96,7 @@ export default class Weatheralerts implements MeteoalarmIntegration { }; } - public getAlerts(entity: WeatheralertsEntity): MeteoalarmAlert[] { + public async getAlerts(_hass: HomeAssistant, entity: WeatheralertsEntity): Promise { const { alerts } = entity.attributes; const result: MeteoalarmAlert[] = []; diff --git a/src/meteoalarm-card.ts b/src/meteoalarm-card.ts index 6cd2e61..f53fff1 100644 --- a/src/meteoalarm-card.ts +++ b/src/meteoalarm-card.ts @@ -17,7 +17,13 @@ import INTEGRATIONS from './integrations/integrations'; import { localize } from './localize/localize'; import { getCanvasFont, getTextWidth } from './measure-text'; import styles from './styles'; -import { MeteoalarmCardConfig, MeteoalarmIntegration, MeteoalarmIntegrationEntityType, MeteoalarmScalingMode } from './types'; +import { + MeteoalarmAlertParsed, + MeteoalarmCardConfig, + MeteoalarmIntegration, + MeteoalarmIntegrationEntityType, + MeteoalarmScalingMode +} from './types'; // eslint-disable-next-line no-console console.info( @@ -49,6 +55,7 @@ export class MeteoalarmCard extends LitElement { // Entity of which alert is displayed on currently selected slide // Used to display correct entity on click private currentEntity?: string; + private events: MeteoalarmAlertParsed[] | undefined = undefined; static get integrations(): MeteoalarmIntegration[] { return INTEGRATIONS.map(i => new i()); @@ -59,7 +66,7 @@ export class MeteoalarmCard extends LitElement { return document.createElement('meteoalarm-card-editor'); } - public static getStubConfig(hass: HomeAssistant, entities: string[]): Record { + public static async getStubConfig(hass: HomeAssistant, entities: string[]): Promise> { // Find fist entity that is supported by any integration const ALLOWED_INTEGRATION_TYPES = [ MeteoalarmIntegrationEntityType.SingleEntity, @@ -71,7 +78,7 @@ export class MeteoalarmCard extends LitElement { ALLOWED_INTEGRATION_TYPES.includes(x.metadata.type) ); for(const integration of integrations) { - if(integration.supports(hass.states[entity])) { + if(await integration.supports(hass, hass.states[entity])) { return { entities: { entity }, integration: integration.metadata.key @@ -265,17 +272,26 @@ export class MeteoalarmCard extends LitElement { return modeString as MeteoalarmScalingMode; } + protected override async scheduleUpdate(): Promise { + const parser = new EventsParser(this.integration); + this.events = await parser.getEvents( + this.hass, + this.entities, + this.config.disable_swiper, + this.config.override_headline, + this.config.hide_caption, + this.config.ignored_levels, + this.config.ignored_events + ); + await super.scheduleUpdate(); + } + protected render(): TemplateResult | void { try { - const parser = new EventsParser(this.integration); - const events = parser.getEvents( - this.entities, - this.config.disable_swiper, - this.config.override_headline, - this.config.hide_caption, - this.config.ignored_levels, - this.config.ignored_events - ); + const events = this.events; + if(!events) { + return; + } // Handle hide_when_no_warning if(events.every(e => !e.isActive) && this.config.hide_when_no_warning) { diff --git a/src/types.ts b/src/types.ts index 7df5d93..47e8025 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,4 @@ -import { ActionConfig, EntityConfig, LovelaceCard, LovelaceCardConfig, LovelaceCardEditor } from 'custom-card-helpers'; +import { ActionConfig, EntityConfig, HomeAssistant, LovelaceCard, LovelaceCardConfig, LovelaceCardEditor } from 'custom-card-helpers'; import { HassEntity } from 'home-assistant-js-websocket'; declare global { @@ -27,9 +27,9 @@ export interface MeteoalarmCardConfig extends LovelaceCardConfig { export interface MeteoalarmIntegration { metadata: MeteoalarmIntegrationMetadata, - supports(entity: HassEntity): boolean, + supports(hass: HomeAssistant, entity: HassEntity): Promise, alertActive(entity: HassEntity): boolean, - getAlerts(entity: HassEntity): MeteoalarmAlert[] | MeteoalarmAlert, + getAlerts(hass: HomeAssistant, entity: HassEntity): Promise | Promise, } export interface MeteoalarmIntegrationMetadata { @@ -129,3 +129,11 @@ export enum MeteoalarmLevelType { Yellow = 1, None = 0 } + +export type EntityRegistryEntry = { + entity_id: string; + original_icon: string; + icon?: string; + unique_id: string; + disabled_by?: string; +} diff --git a/src/utils.ts b/src/utils.ts index d4141b6..530c5ae 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,4 +1,6 @@ -import { MeteoalarmEventType, MeteoalarmLevelType } from './types'; +import { EntityRegistryEntry, MeteoalarmEventType, MeteoalarmLevelType } from './types'; +import { HomeAssistant } from 'custom-card-helpers'; +import { HassEntity } from 'home-assistant-js-websocket'; export class Utils { /** @@ -54,4 +56,11 @@ export class Utils { public static convertEventTypesForMetadata(eventTypes: { [key: number | string]: MeteoalarmEventType }): MeteoalarmEventType[] { return [...new Set(Object.values(eventTypes))]; } + + public static async getEntityInfo(hass: HomeAssistant, entity: HassEntity): Promise { + return await hass.callWS({ + type: 'config/entity_registry/get', + entity_id: entity.entity_id + }); + } }