Home Assistant Git Exporter

This commit is contained in:
root
2024-05-31 13:07:35 +02:00
parent 64a0536537
commit 60abdd866c
275 changed files with 71113 additions and 1 deletions

View File

@@ -0,0 +1,3 @@
# Pollens-Async
Custom component for pollens

View File

@@ -0,0 +1,191 @@
"""Pollens Allergy component."""
from __future__ import annotations
from datetime import timedelta
import logging
from os import error
from re import I
from typing import Any
from aiohttp.client_exceptions import ClientError
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_SCAN_INTERVAL, CONF_SENSORS, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import aiohttp_client
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.device_registry import DeviceEntryType
from homeassistant.helpers.typing import ConfigType
from homeassistant.helpers.update_coordinator import (
DataUpdateCoordinator,
CoordinatorEntity,
UpdateFailed,
)
from .const import (
ATTRIBUTION,
CONF_LITERAL,
CONF_POLLENSLIST,
CONF_VERSION,
DOMAIN,
COORDINATOR,
UNDO_LISTENER,
CONF_COUNTRYCODE,
CONF_SCAN_INTERVAL,
KEY_TO_ATTR,
)
from .pollensasync import PollensClient
# List of platforms to support. There should be a matching .py file for each,
# eg <cover.py> and <sensor.py>
PLATFORMS: list[str] = [Platform.SENSOR]
_LOGGER = logging.getLogger(__name__)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up pollens integation"""
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up pollens from a config entry."""
conf = entry.data
session = aiohttp_client.async_get_clientsession(hass)
api = PollensClient(session)
county = conf[CONF_COUNTRYCODE]
scan_interval = entry.options.get(CONF_SCAN_INTERVAL, 3)
await api.Get(county)
name = f"Pollens {api.county_name}"
coordinator = PollensUpdateCoordinator(
hass=hass,
name=name,
scan_interval=scan_interval,
county=county,
api=api,
)
await coordinator.async_config_entry_first_refresh()
# Add and update listener
undo_listener = entry.add_update_listener(_async_update_listener)
# Setup coordinator
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = {
COORDINATOR: coordinator,
UNDO_LISTENER: undo_listener,
"pollens_api": api,
}
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
_LOGGER.debug("Setup of %s successful", entry.title)
return True
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Migrate the config entry upon new versions."""
version = entry.version
data = {**entry.data}
_LOGGER.debug("Migrating from version %s", version)
# 1 -> 2: Remove unused condition data:
if version == 1:
data.pop(CONF_SENSORS, None)
data[CONF_POLLENSLIST] = [pollen for pollen in KEY_TO_ATTR]
data[CONF_LITERAL] = True
version = entry.version = CONF_VERSION
hass.config_entries.async_update_entry(entry, data=data)
_LOGGER.debug("Migration to version %s successful", version)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry):
"""Update when config_entry options update"""
await hass.config_entries.async_reload(entry.entry_id)
class PollensUpdateCoordinator(DataUpdateCoordinator):
"""Class to manage fetching Pollens data API"""
def __init__(
self,
hass: HomeAssistant,
name: str,
scan_interval: int,
api: str,
county: str,
# level_filter: int,
) -> None:
super().__init__(
hass=hass,
logger=_LOGGER,
name=name,
update_interval=timedelta(hours=scan_interval),
)
self.api = api
self.name = name
self.county = county
async def _async_update_data(self):
_LOGGER.info("Update data from web site for %s", self.name)
try:
return await self.api.Get(self.county)
except ClientError as error:
raise UpdateFailed(f"Error updating from RSSA : {error}") from error
class PollensEntity(CoordinatorEntity):
"""Implementation of the base pollens Entity"""
_attr_extra_state_attributes = {"attribution": ATTRIBUTION}
def __init__(
self,
coordinator: PollensUpdateCoordinator,
name: str,
icon: str,
entry: ConfigEntry,
) -> None:
"""Initialize"""
super().__init__(coordinator=coordinator)
# self._attr_unique_id = f"{entry.entry_id}_{KEY_TO_ATTR[name.lower()][0]}"
# self._attr_icon = icon
# self._unique_id = f"pollens_{entry.entry_id}"
@property
def device_info(self) -> DeviceInfo:
return DeviceInfo(
entry_type=DeviceEntryType.SERVICE,
identifiers={
(
DOMAIN,
str(
f"{self.platform.config_entry.unique_id}{self.platform.config_entry.data['county']}"
),
)
},
manufacturer="RNSA",
model="Pollens sensor",
name=self.coordinator.name,
)

View File

@@ -0,0 +1,175 @@
"""Config flow for Pollens integration."""
from __future__ import annotations
import logging
from typing import Any
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant import config_entries, exceptions
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers import aiohttp_client
from homeassistant.helpers import selector
from .const import (
CONF_VERSION,
DOMAIN,
KEY_TO_ATTR,
CONF_COUNTRYCODE,
CONF_SCAN_INTERVAL,
CONF_POLLENSLIST,
CONF_LITERAL,
)
from .dept import DEPARTMENTS
from .pollensasync import PollensClient
_LOGGER = logging.getLogger(__name__)
async def validate_input(hass: HomeAssistant, data: dict) -> dict[str, Any]:
"""Validate the user input allows us to connect.
Data has the keys from DATA_SCHEMA with values provided by the user.
"""
# Validate the data can be used to set up a connection.
if len(data[CONF_COUNTRYCODE]) != 2:
raise InvalidCounty
session = aiohttp_client.async_get_clientsession(hass)
client = PollensClient(session)
result = await client.Get(data[CONF_COUNTRYCODE])
if not result:
# If there is an error, raise an exception to notify HA that there was a
# problem. The UI will also show there was a problem
raise CannotConnect
title = f"Pollens {client.county_name}"
return {"title": title}
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Pollens."""
VERSION = CONF_VERSION
# Pick one of the available connection classes in homeassistant/config_entries.py
# This tells HA if it should be asking for updates, or it'll be notified of updates
# automatically. This example uses PUSH, as the dummy hub will notify HA of
# changes.
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
def __init__(self):
"""Initialize"""
self.data = None
self._init_info = {}
async def async_step_user(self, user_input=None):
"""Handle the initial step."""
errors = {}
if user_input is not None:
try:
self._init_info["data"] = user_input
self._init_info["info"] = await validate_input(self.hass, user_input)
return await self.async_step_select_pollens()
except CannotConnect:
errors["base"] = "cannot_connect"
except InvalidCounty:
errors["base"] = "invalid_county"
_LOGGER.exception("Invalid county selected")
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
entries = self._async_current_entries()
# Remove county from the list (already configured)
for entry in entries:
if entry.data[CONF_COUNTRYCODE] in DEPARTMENTS:
DEPARTMENTS.pop(entry.data[CONF_COUNTRYCODE])
# If there is no user input or there were errors, show the form again, including any errors that were found with the input.
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{
vol.Required(CONF_COUNTRYCODE, default=["60"]): vol.In(DEPARTMENTS),
vol.Required(CONF_LITERAL, default=True): cv.boolean,
}
),
description_placeholders={"docs_url": "pollens.fr"},
errors=errors,
)
async def async_step_select_pollens(self, user_input=None):
"""Select pollens step 2"""
if user_input is not None:
_LOGGER.info("Select pollens step")
self._init_info["data"][CONF_POLLENSLIST] = user_input[CONF_POLLENSLIST]
return self.async_create_entry(
title=self._init_info["info"]["title"], data=self._init_info["data"]
)
pollens = [pollen for pollen in KEY_TO_ATTR]
return self.async_show_form(
step_id="select_pollens",
data_schema=vol.Schema(
{vol.Optional(CONF_POLLENSLIST): cv.multi_select(pollens)}
),
)
@staticmethod
@callback
def async_get_options_flow(
config_entry: ConfigEntry,
) -> OptionsFlowHandler:
"""Options callback for Pollens."""
return OptionsFlowHandler(config_entry)
class CannotConnect(exceptions.HomeAssistantError):
"""Error to indicate we cannot connect."""
class InvalidCounty(exceptions.HomeAssistantError):
"""Error to invalid county."""
class InvalidScanInterval(exceptions.HomeAssistantError):
"""Error to invalid scan interval ."""
class OptionsFlowHandler(config_entries.OptionsFlow):
"""Config flow options for Pollens."""
def __init__(self, entry: ConfigEntry) -> None:
"""Initialize Pollens options flow."""
self.config_entry = entry
async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle a flow initialized by the user."""
_LOGGER.info("Changing options of pollens integration")
errors = {}
if user_input is not None:
# Validate the data can be used to set up a connection.
_LOGGER.info(
"Change option of %s to %s",
self.config_entry.title,
user_input[CONF_SCAN_INTERVAL],
)
return self.async_create_entry(title="", data=user_input)
return self.async_show_form(
step_id="init",
data_schema=vol.Schema(
{
# Configuration of scan interval mini 3 h max 24h
vol.Optional(
CONF_SCAN_INTERVAL,
default=self.config_entry.options.get(CONF_SCAN_INTERVAL, 1),
): selector.NumberSelector(selector.NumberSelectorConfig(min=3, max=24, mode=selector.NumberSelectorMode.BOX)),
}
),
errors=errors,
)

View File

@@ -0,0 +1,67 @@
"""Constants for the Pollens integration."""
DOMAIN = "pollens"
ATTRIBUTION = "Data from Reseau National de Surveillance Aerobiologique "
CONF_VERSION = 2
COORDINATOR = "coordinator"
UNDO_LISTENER = "undo_listener"
CONF_COUNTRYCODE = "county"
CONF_SCAN_INTERVAL = "scan_interval"
CONF_SCANINTERVAL = "scaninterval"
CONF_POLLENSLIST = "pollens_list"
CONF_LITERAL = "literal_states"
ATTR_TILLEUL = "tilleul"
ATTR_AMBROISIES = "ambroisies"
ATTR_OLIVIER = "olivier"
ATTR_PLANTAIN = "plantain"
ATTR_NOISETIER = "noisetier"
ATTR_AULNE = "aulne"
ATTR_ARMOISE = "armoise"
ATTR_CHATAIGNIER = "chataignier"
ATTR_URTICACEES = "urticacees"
ATTR_OSEILLE = "oseille"
ATTR_GRAMINEES = "graminees"
ATTR_CHENE = "chene"
ATTR_PLATANE = "platane"
ATTR_BOULEAU = "bouleau"
ATTR_CHARME = "charme"
ATTR_PEUPLIER = "peuplier"
ATTR_FRENE = "frene"
ATTR_SAULE = "saule"
ATTR_CYPRES = "cypres"
ATTR_CUPRESSASEES = "cupressacees"
ATTR_LITERAL_STATE = "literal_state"
ATTR_POLLEN_NAME = "pollen_name"
ICON_FLOWER = "mdi:flower"
ICON_TREE = "mdi:tree"
ICON_GRASS = "mdi:grass"
KEY_TO_ATTR = {
"tilleul": [ATTR_TILLEUL, ICON_TREE],
"ambroisies": [ATTR_AMBROISIES, ICON_GRASS],
"olivier": [ATTR_OLIVIER, ICON_TREE],
"plantain": [ATTR_PLANTAIN, ICON_GRASS],
"noisetier": [ATTR_NOISETIER, ICON_TREE],
"aulne": [ATTR_AULNE, ICON_TREE],
"armoise": [ATTR_ARMOISE, ICON_GRASS],
"châtaignier": [ATTR_CHATAIGNIER, ICON_TREE],
"urticacées": [ATTR_URTICACEES, ICON_GRASS],
"oseille": [ATTR_OSEILLE, ICON_GRASS],
"graminées": [ATTR_GRAMINEES, ICON_GRASS],
"chêne": [ATTR_CHENE, ICON_TREE],
"platane": [ATTR_PLATANE, ICON_TREE],
"bouleau": [ATTR_BOULEAU, ICON_TREE],
"charme": [ATTR_CHARME, ICON_TREE],
"peuplier": [ATTR_PEUPLIER, ICON_TREE],
"frêne": [ATTR_FRENE, ICON_TREE],
"saule": [ATTR_SAULE, ICON_TREE],
"cyprès": [ATTR_CYPRES, ICON_TREE],
"cupressacées": [ATTR_CUPRESSASEES, ICON_GRASS],
}
ATTR_COUNTY_NAME = "departement"
ATTR_URL = "url"
LIST_RISK = ["nul", "faible", "moyen", "élevé"]

View File

@@ -0,0 +1,105 @@
""" Dictionnaire des départements Français """
DEPARTMENTS = {
"01": "Ain",
"02": "Aisne",
"03": "Allier",
"04": "Alpes-de-Haute-Provence",
"05": "Hautes-Alpes",
"06": "Alpes-Maritimes",
"07": "Ardèche",
"08": "Ardennes",
"09": "Ariège",
"10": "Aube",
"11": "Aude",
"12": "Aveyron",
"13": "Bouches-du-Rhône",
"14": "Calvados",
"15": "Cantal",
"16": "Charente",
"17": "Charente-Maritime",
"18": "Cher",
"19": "Corrèze",
"20": "Corse-du-Sud",
"20": "Haute-Corse",
"21": "Côte-d'Or",
"22": "Côtes-d'Armor",
"23": "Creuse",
"24": "Dordogne",
"25": "Doubs",
"26": "Drôme",
"27": "Eure",
"28": "Eure-et-Loir",
"29": "Finistère",
"30": "Gard",
"31": "Haute-Garonne",
"32": "Gers",
"33": "Gironde",
"34": "Hérault",
"35": "Ille-et-Vilaine",
"36": "Indre",
"37": "Indre-et-Loire",
"38": "Isère",
"39": "Jura",
"40": "Landes",
"41": "Loir-et-Cher",
"42": "Loire",
"43": "Haute-Loire",
"44": "Loire-Atlantique",
"45": "Loiret",
"46": "Lot",
"47": "Lot-et-Garonne",
"48": "Lozère",
"49": "Maine-et-Loire",
"50": "Manche",
"51": "Marne",
"52": "Haute-Marne",
"53": "Mayenne",
"54": "Meurthe-et-Moselle",
"55": "Meuse",
"56": "Morbihan",
"57": "Moselle",
"58": "Nièvre",
"59": "Nord",
"60": "Oise",
"61": "Orne",
"62": "Pas-de-Calais",
"63": "Puy-de-Dôme",
"64": "Pyrénées-Atlantiques",
"65": "Hautes-Pyrénées",
"66": "Pyrénées-Orientales",
"67": "Bas-Rhin",
"68": "Haut-Rhin",
"69": "Rhône",
"70": "Haute-Saône",
"71": "Saône-et-Loire",
"72": "Sarthe",
"73": "Savoie",
"74": "Haute-Savoie",
"75": "Paris",
"76": "Seine-Maritime",
"77": "Seine-et-Marne",
"78": "Yvelines",
"79": "Deux-Sèvres",
"80": "Somme",
"81": "Tarn",
"82": "Tarn-et-Garonne",
"83": "Var",
"84": "Vaucluse",
"85": "Vendée",
"86": "Vienne",
"87": "Haute-Vienne",
"88": "Vosges",
"89": "Yonne",
"90": "Territoire de Belfort",
"91": "Essonne",
"92": "Hauts-de-Seine",
"93": "Seine-Saint-Denis",
"94": "Val-de-Marne",
"95": "Val-d'Oise",
"971": "Guadeloupe",
"972": "Martinique",
"973": "Guyane",
"974": "La Réunion",
"976": "Mayotte",
}

View File

@@ -0,0 +1,22 @@
[![](https://img.shields.io/badge/MAINTAINER-%40chris60600-red)](https://github.com/chris60600)
[![hacs_badge](https://img.shields.io/badge/HACS-Custom-orange.svg)](https://github.com/custom-components/hacs)
# HomeAssistant - Pollens Custom Component
This module show pollen concentration inside [Homeassistant](https://home-assistant.io):
Datas provided by 'Réseau National de Surveillance Aérobiologique' (R.N.S.A.)
https://pollens.fr
# Installation (There are two methods, with HACS or manual)
### 1. Easy Mode
### 2. Manual
Install it as you would do with any homeassistant custom component:
1. Download `custom_components` folder.
2. Copy the `pollens` direcotry within the `custom_components` directory of your homeassistant installation. The `custom_components` directory resides within your homeassistant configuration directory.

View File

@@ -0,0 +1,11 @@
{
"domain": "pollens",
"name": "Reseau National de Surveillance Aerobiologique ((R.N.S.A.))",
"documentation": "https://github.com/chris60600/pollens-home-assistant",
"issue_tracker": "https://github.com/chris60600/pollens-home-assistant/issues",
"requirements": [],
"codeowners": ["@chris60600"],
"config_flow": true,
"iot_class": "cloud_polling",
"version": "2023.06.01"
}

View File

@@ -0,0 +1,69 @@
"""asyncio-friendly python API for RNSA (https://pollens.fr)."""
import asyncio
import aiohttp
from aiohttp.client import ClientError, ClientTimeout
import json
import async_timeout
DEFAULT_TIMEOUT = 240
CLIENT_TIMEOUT = ClientTimeout(total=DEFAULT_TIMEOUT)
BASE_URL = "https://pollens.fr/risks/thea/counties/{}"
class PollensClient:
"""Pollens client implementation."""
def __init__(self, session: aiohttp.ClientSession = None, timeout=CLIENT_TIMEOUT):
"""Constructor.
session: aiohttp.ClientSession or None to create a new session.
"""
self._county_name = None
self._params = {}
self._risk_level = None
self._risks = {}
self._timeout = timeout
if session is not None:
self._session = session
else:
self._session = aiohttp.ClientSession()
async def Get(self, number):
"""Get data by station number."""
try:
request = await self._session.get(
BASE_URL.format(number), timeout=CLIENT_TIMEOUT
)
if "application/json" in request.headers["content-type"]:
request_json = await request.json()
else:
request_json = await request.text()
request_json = json.loads(request_json)
self._county_name = request_json["countyName"]
for risk in request_json["risks"]:
self._risks[risk["pollenName"]] = risk["level"]
self._risk_level = request_json["riskLevel"]
return request_json
except (ClientError, asyncio.TimeoutError, ConnectionRefusedError) as err:
return None
@property
def county_name(self):
return self._county_name
@property
def risks(self):
return self._risks
@property
def risk_level(self):
return self._risk_level
# async def _get(self, path, **kwargs):
# with async_timeout.timeout(self._timeout):
# resp = await self._session.get(path, params=dict(self._params, **kwargs))
# print(type(resp.headers["Content-Type"]))
# return await resp.text(encoding="utf-8")

View File

@@ -0,0 +1 @@
async

View File

@@ -0,0 +1,167 @@
"""Support for the RNSA pollens service."""
import logging
from warnings import catch_warnings
from yaml import KeyToken
from homeassistant.config_entries import ConfigEntry
from homeassistant.components.sensor import SensorEntity, SensorDeviceClass, SensorStateClass
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import (
DOMAIN,
LIST_RISK,
ATTR_URL,
ATTR_COUNTY_NAME,
ATTR_POLLEN_NAME,
ATTR_LITERAL_STATE,
KEY_TO_ATTR,
COORDINATOR,
CONF_POLLENSLIST,
CONF_LITERAL,
)
from . import PollensEntity, PollensUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
ICONS = {
0: "mdi:decagram-outline",
1: "mdi:decagram-check",
2: "mdi:alert-decagram-outline",
3: "mdi:alert-decagram",
}
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Setup Sensor Plateform"""
coordinator = hass.data[DOMAIN][entry.entry_id][COORDINATOR]
sensors = []
try:
enabled_pollens = entry.data[CONF_POLLENSLIST]
except KeyError:
enabled_pollens = [pollen for pollen in KEY_TO_ATTR]
for risk in coordinator.api.risks:
name = risk
icon = KEY_TO_ATTR[risk.lower()][1]
sensors.append(PollenSensor(coordinator, name=name, icon=icon, entry=entry, enabled=name.lower() in enabled_pollens))
name = f"pollens_{coordinator.county}"
icon = ICONS[0]
sensors.append(
RiskSensor(coordinator=coordinator, name=name, icon=icon, entry=entry, numeric=False)
)
sensors.append(
RiskSensor(coordinator=coordinator, name=name + "_risklevel", icon=icon, entry=entry, numeric=True)
)
async_add_entities(sensors, True)
class PollenSensor(PollensEntity, SensorEntity):
"""Implementation of a Pollens sensor."""
def __init__(
self,
coordinator: PollensUpdateCoordinator,
name: str,
icon: str,
entry: ConfigEntry,
enabled: bool
) -> None:
super().__init__(coordinator, name, icon, entry)
self._name = f"pollens_{coordinator.county}_{KEY_TO_ATTR[name.lower()][0]}"
self._state = coordinator.api.risks[name]
self._unique_id = f"{entry.entry_id}_{self._name}"
self._attr_name = name
self._attr_unique_id = self._unique_id
self._attr_entity_registry_enabled_default = enabled
try:
self._literal_state = entry.data[CONF_LITERAL]
# Setup DeviceClass in AQI and stateClass in numeric (Issue #15)
if not self._literal_state:
self._attr_device_class = SensorDeviceClass.AQI
self._attr_state_class = SensorStateClass.MEASUREMENT
except KeyError:
self._literal_state = True
self._attr_icon = icon
self._friendly_name = f"{name}"
@property
def name(self):
return self._name
@property
def native_value(self):
value = self.coordinator.api.risks[self._attr_name]
if self._literal_state:
return LIST_RISK[value]
else:
return value
@property
def unique_id(self):
"""Return the unique id."""
return self._unique_id
@property
def extra_state_attributes(self):
"""Return the state attributes of the last update."""
attrs = {}
attrs[ATTR_POLLEN_NAME] = self._friendly_name
if self.coordinator.api.risks is not None:
if not self._literal_state:
value = self.coordinator.api.risks[self._attr_name]
attrs[ATTR_LITERAL_STATE] = LIST_RISK[value]
return attrs
class RiskSensor(PollensEntity, SensorEntity):
"""Implementation of Risk Sensor"""
def __init__(
self,
coordinator: PollensUpdateCoordinator,
name: str,
icon: str,
entry: ConfigEntry,
numeric: bool
) -> None:
super().__init__(coordinator, name, icon, entry)
self._risk_level = coordinator.api.risk_level
self._attr_unique_id = f"{entry.entry_id}_{coordinator.county}"
self._attr_icon = icon
self._name = name
self._numeric = numeric
if numeric:
self._attr_unique_id += "_risklevel"
self._attr_device_class = SensorDeviceClass.AQI
self._attr_state_class = SensorStateClass.MEASUREMENT
@property
def native_value(self):
value = self.coordinator.api.risk_level
if self._numeric:
return value
else:
return LIST_RISK[value]
@property
def icon(self):
return ICONS[self._risk_level]
@property
def name(self):
return self._name
@property
def extra_state_attributes(self):
attrs = {}
attrs[ATTR_URL] = "https://pollens.fr"
attrs[ATTR_COUNTY_NAME] = self.coordinator.api.county_name
return attrs

View File

@@ -0,0 +1,45 @@
{
"title": "Pollens",
"config": {
"step": {
"user": {
"title":"Pollens - Step #1",
"description":"If you need information about pollens on site R.N.S.A. website, have a look here: https://www.pollens.fr",
"data": {
"county": "[%key:common::config_flow::data::county%]",
"scan_interval": "[%key:common::config_flow::data::scan_interval%]",
"literal_states":"[%key:common::config_flow::data::literal_states%]"
}
},
"select_pollens": {
"title":"Pollens - Step #2",
"description":"Select pollens \r\nhttps://www.pollens.fr",
"data": {
"pollens_list": "Select pollens from list"
}
}
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"unknown": "[%key:common::config_flow::error::unknown%]",
"invalid_county": "[%key:common::config_flow::error::invalid_county%]"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
}
},
"options":{
"step":{
"init":{
"data":{
"scan_interval": "Scan interval"
}
}
},
"error": {
"invalid_scan_interval":"[%key:common::config_flow::error::invalid_scan_interval%]"
}
}
}

View File

@@ -0,0 +1,46 @@
{
"title": "Pollens (from R.N.S.A. website)\r\nPlease visit www.pollen.fr for more information",
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"single_instance_allowed": "Only one configuration of Pollens allowed"
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_county": "Only one county is actualy allowed",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"step": {
"user": {
"title":"Pollens - Step #1",
"description":"Select county \r\nhttps://www.pollens.fr",
"data": {
"county": "County",
"scan_interval": "Scan interval (hours)",
"literal_states":"States in literal (in numeric if not selected)"
}
},
"select_pollens": {
"title":"Pollens - Step #2",
"description":"Select pollens \r\nhttps://www.pollens.fr",
"data": {
"pollens_list": "Select pollens from list"
}
}
}
},
"options":{
"step":{
"init":{
"data":{
"scan_interval": "Scan interval (hours)"
}
}
},
"error": {
"invalid_scan_interval": "Invalid scan interval. Must be higher than 1 hour"
}
}
}

View File

@@ -0,0 +1,45 @@
{
"title": "Pollens",
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"single_instance_allowed": "Une seule configuration de Pollens est autoris\u00e9e"
},
"error": {
"cannot_connect": "Impossible de se connecter au serveur",
"invalid_county": "Un seul d\u00e9partment est actuellemnt g\u00e9r\u00e9",
"unknown": "Erreur inconnue"
},
"step": {
"user": {
"data": {
"county": "D\u00e9partement",
"scan_interval": "P\u00e9riode d\u0027int\u00e9rogation (heures)",
"literal_states": "Etats en texte (non selection\u00e9 = num\u00e9rique)"
},
"title":"Pollens - Etape #1",
"description":"Si vous avez besoin d\u0027information sur les pollens rendez vous sur le site du R.N.S.A, https://www.pollens.fr"
},
"select_pollens": {
"title":"Pollens - Etape #2",
"description":"Selection des pollens \r\nhttps://www.pollens.fr",
"data": {
"pollens_list": "Selectionnez un/des pollens dans la liste"
}
}
}
},
"options":{
"step":{
"init":{
"data":{
"scan_interval": "P\u00e9riode d\u0027int\u00e9rogation (heures)"
}
}
},
"error": {
"invalid_scan_interval": "La periode d interrogation doit etre superieure a 1 heure"
}
}
}