Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v2.0.0 #20

Merged
merged 6 commits into from
Feb 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 19 additions & 51 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,64 +18,38 @@
[downloads_total_shield]: https://img.shields.io/github/downloads/PiotrMachowski/Home-Assistant-custom-components-Antistorm/total


# Antistorm sensor
# Antistorm

This sensor uses official API to get storm warnings from [*Antistorm*](https://antistorm.eu/). For more precise explanation of parameters visit [*Antistorm.eu*](https://antistorm.eu/deweloperzy.php).
This integration returns storm and precipitation warnings from [*Antistorm*](https://antistorm.eu/).

## Configuration options
## Configuration

| Key | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `name` | `string` | `False` | `Antistorm` | Name of sensor |
| `station_id` | `positive int` | `True` | - | ID of monitored station |
| `monitored_conditions` | `list` | `True` | - | List of conditions to monitor |
To configure this integration search for `Antistorm` on *Integrations* page.
Alternatively you can just use the button below:

### Possible monitored conditions
[![Open your Home Assistant instance and start setting up a new integration.](https://my.home-assistant.io/badges/config_flow_start.svg)](https://my.home-assistant.io/redirect/config_flow_start/?domain=antistorm)

#### Binary sensor
| Key | Description |
| --- | --- |
| `storm_alarm` | Status of storm alarm |
| `rain_alarm` | Status of rain alarm |
| `storm_active` | Active storm |
Available binary sensors:
- Storm Alarm
- Precipitation Alarm
- Storm Active

#### Sensor
| Key | Description |
| --- | --- |
| `storm_probability` | Probability of storm |
| `storm_time` | Estimated time until storm |
| `rain_probability` | Probability of rain |
| `rain_time` | Estimated time until rain |
Available sensors:
- Storm Probability
- Time to Storm
- Precipitation Probability
- Time to Precipitation

## Example usage

```
binary_sensor:
- platform: antistorm
station_id: 408
monitored_conditions:
- 'storm_alarm'
- 'rain_alarm'
- 'storm_active'
```

```
sensor:
- platform: antistorm
station_id: 408
monitored_conditions:
- 'storm_probability'
- 'storm_time'
- 'rain_probability'
- 'rain_time'
```
For more precise explanation of parameters visit [*Antistorm.eu*](https://antistorm.eu/deweloperzy.php).

## Installation

### Using [HACS](https://hacs.xyz/) (recommended)

This integration can be installed using HACS.
To do it search for `Antistorm` in *Integrations* section.
To do it search for `Antistorm` in *Integrations* section, or just use the buton below.

[![Open your Home Assistant instance and open a repository inside the Home Assistant Community Store.](https://my.home-assistant.io/badges/hacs_repository.svg)](https://my.home-assistant.io/redirect/hacs_repository/?owner=PiotrMachowski&repository=Home-Assistant-custom-components-Antistorm&category=Integration)

### Manual

Expand All @@ -89,12 +63,6 @@ rm antistorm.zip
```


## FAQ

* **How to get value for `station_id`?**

To find out `station_id` use widget code generator available at page [*Antistorm.eu*](https://antistorm.eu/deweloperzy.php).


<!-- piotrmachowski_support_links_start -->

Expand Down
61 changes: 60 additions & 1 deletion custom_components/antistorm/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,60 @@
"""Antistorm"""
import logging

import homeassistant.helpers.config_validation as cv
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_NAME
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady

from .const import (
DOMAIN, PLATFORMS, CONF_CITY_ID
)
from .update_coordinator import AntistormUpdateCoordinator

_LOGGER = logging.getLogger(__name__)

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_CITY_ID): cv.positive_int,
vol.Required(CONF_NAME): cv.string,
}
)


async def async_setup(_hass, _config):
return True


async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry):
if hass.data.get(DOMAIN) is None:
hass.data.setdefault(DOMAIN, {})

city_id = config_entry.data.get(CONF_CITY_ID)

coordinator = AntistormUpdateCoordinator(hass, city_id)
await coordinator.async_refresh()

if not coordinator.last_update_success:
raise ConfigEntryNotReady

hass.data[DOMAIN][config_entry.entry_id] = coordinator

await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
config_entry.async_on_unload(config_entry.add_update_listener(async_reload_entry))
return True


async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry):
"""Unload a config entry."""
unloaded = await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS)
if unloaded:
hass.data[DOMAIN].pop(config_entry.entry_id)
return unloaded


async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Reload config entry."""
await async_unload_entry(hass, entry)
await async_setup_entry(hass, entry)
147 changes: 69 additions & 78 deletions custom_components/antistorm/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -1,89 +1,80 @@
import logging
import requests
from dataclasses import dataclass
from typing import Callable

import voluptuous as vol
from homeassistant.config_entries import ConfigEntry

from homeassistant.components.binary_sensor import DEVICE_CLASS_SAFETY, PLATFORM_SCHEMA, ENTITY_ID_FORMAT
from homeassistant.const import CONF_MONITORED_CONDITIONS, CONF_NAME, ATTR_ATTRIBUTION
import homeassistant.helpers.config_validation as cv
try:
from homeassistant.components.binary_sensor import BinarySensorEntity
except ImportError:
from homeassistant.components.binary_sensor import BinarySensorDevice as BinarySensorEntity
from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.core import HomeAssistant
from homeassistant.components.binary_sensor import BinarySensorDeviceClass, BinarySensorEntityDescription, DOMAIN as BS_DOMAIN
from homeassistant.components.binary_sensor import BinarySensorEntity
from homeassistant.helpers.entity_platform import AddEntitiesCallback

_LOGGER = logging.getLogger(__name__)

CONF_STATION_ID = 'station_id'

DEFAULT_NAME = 'Antistorm'
ATTRIBUTION = 'Information provided by Antistorm.eu.'

SENSOR_TYPES = {
'storm_alarm': ['a_b', 'Alarm burzowy', 'mdi:weather-lightning'],
'rain_alarm': ['a_o', 'Alarm opadów', 'mdi:weather-pouring'],
'storm_active': ['s', 'Aktywna burza', 'mdi:weather-lightning-rainy'],
}

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Required(CONF_STATION_ID): cv.string,
vol.Required(CONF_MONITORED_CONDITIONS, default=[]):
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)])
})


def setup_platform(hass, config, add_entities, discovery_info=None):
station_id = config.get(CONF_STATION_ID)
name = config.get(CONF_NAME)
address = 'http://antistorm.eu/webservice.php?id=' + str(station_id)
request = requests.get(address)
request.encoding = 'utf-8'
city_name = request.json()['m']
dev = []
for monitored_condition in config[CONF_MONITORED_CONDITIONS]:
uid = '{}_{}_{}'.format(name, station_id, monitored_condition)
entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, uid, hass=hass)
dev.append(AntistormBinarySensor(entity_id, name, city_name, monitored_condition, station_id))
add_entities(dev, True)


class AntistormBinarySensor(BinarySensorEntity):
def __init__(self, entity_id, name, city_name, sensor_type, station_id):
self.entity_id = entity_id
self._name = name
self.city_name = city_name
self.station_id = station_id
self.sensor_type = sensor_type
self.data = None
self._state = None
self._jsonParameter = SENSOR_TYPES[sensor_type][0]
self._name = SENSOR_TYPES[sensor_type][1]
from .const import DOMAIN
from .update_coordinator import AntistormUpdateCoordinator, AntistormData
from .entity import AntistormEntity

@property
def extra_state_attributes(self):
output = dict()
output[ATTR_ATTRIBUTION] = ATTRIBUTION
return output
_LOGGER = logging.getLogger(__name__)

@property
def name(self):
return '{} {} - {}'.format(self._name, self.city_name, SENSOR_TYPES[self.sensor_type][1])

@property
def icon(self):
return SENSOR_TYPES[self.sensor_type][2]
@dataclass(frozen=True)
class AntistormBinarySensorDescriptionMixin:
value_fn: Callable[[AntistormData], bool]


@dataclass(frozen=True)
class AntistormBinarySensorEntityDescription(BinarySensorEntityDescription, AntistormBinarySensorDescriptionMixin):
device_class = BinarySensorDeviceClass.SAFETY
has_entity_name = True


entity_descriptions = [
AntistormBinarySensorEntityDescription(
key='storm_alarm',
translation_key='storm_alarm',
icon="mdi:weather-lightning",
value_fn=lambda data: data.storm_alarm,
),
AntistormBinarySensorEntityDescription(
key='precipitation_alarm',
translation_key='precipitation_alarm',
icon="mdi:weather-pouring",
value_fn=lambda data: data.precipitation_alarm,
),
AntistormBinarySensorEntityDescription(
key='storm_active',
translation_key='storm_active',
icon="mdi:weather-lightning-rainy",
value_fn=lambda data: data.storm_active,
),
]


async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback) -> bool:
coordinator: AntistormUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
entities = []
for entity_description in entity_descriptions:
entities.append(AntistormBinarySensor(coordinator, config_entry, entity_description))
async_add_entities(entities)
return True


class AntistormBinarySensor(AntistormEntity, BinarySensorEntity):
entity_description: AntistormBinarySensorEntityDescription

def __init__(self, coordinator: AntistormUpdateCoordinator, config_entry: ConfigEntry,
description: AntistormBinarySensorEntityDescription) -> None:
super().__init__(coordinator, config_entry)
self.entity_description = description
self._attr_unique_id = f"{DOMAIN}_{description.key}"
self.entity_id = f"{BS_DOMAIN}.{DOMAIN}_{self.city_id}_{description.key}"

@property
def is_on(self):
return self.data is not None and int(self.data[self._jsonParameter]) > 0
def is_on(self) -> bool | None:
if self.get_data() is None:
return None
return self.entity_description.value_fn(self.get_data())

@property
def device_class(self):
return DEVICE_CLASS_SAFETY

def update(self):
address = 'http://antistorm.eu/webservice.php?id=' + str(self.station_id)
request = requests.get(address)
if request.status_code == 200 and request.content.__len__() > 0:
self.data = request.json()
def unique_id(self) -> str:
return f"{super().unique_id}_{self.entity_description.key}"
47 changes: 47 additions & 0 deletions custom_components/antistorm/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""Config flow to configure Antistorm integration."""

import logging

import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_NAME
from homeassistant.data_entry_flow import FlowResult

from .connector import AntistormConnector
from .const import CONF_CITY_ID, CONF_CITY_NAME, DOMAIN

_LOGGER = logging.getLogger(__name__)


class AntistormFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
VERSION = 1

async def get_city_details(self, city_name: str) -> tuple[int, str] | None:
city_id = await self.hass.async_add_executor_job(AntistormConnector.get_city_id, city_name)
if city_id is None:
return None
city_name = (await self.hass.async_add_executor_job(lambda: AntistormConnector(city_id).get_data())).city
return city_id, city_name

async def async_step_user(self, user_input=None) -> FlowResult:
errors = {}
usr_city_name = ""
if user_input is not None:
usr_city_name = user_input[CONF_CITY_NAME].strip()
details = await self.get_city_details(usr_city_name)
if details is not None:
city_id = details[0]
city_name = details[1]
return self.async_create_entry(
title=city_name,
data={
CONF_CITY_ID: city_id,
CONF_NAME: city_name
},
)
else:
errors[CONF_CITY_NAME] = "city_not_found"
schema = vol.Schema({
vol.Required(CONF_CITY_NAME, default=usr_city_name): str,
})
return self.async_show_form(step_id="user", data_schema=schema, errors=errors)
Loading
Loading