Skip to content

Commit

Permalink
Add Stookwijzer (#84435)
Browse files Browse the repository at this point in the history
Co-authored-by: Franck Nijhof <frenck@frenck.nl>
Co-authored-by: Franck Nijhof <git@frenck.dev>
  • Loading branch information
3 people authored Jan 20, 2023
1 parent 658db7f commit 29b2b67
Show file tree
Hide file tree
Showing 16 changed files with 303 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -1249,6 +1249,9 @@ omit =
homeassistant/components/stookalert/__init__.py
homeassistant/components/stookalert/binary_sensor.py
homeassistant/components/stookalert/diagnostics.py
homeassistant/components/stookwijzer/__init__.py
homeassistant/components/stookwijzer/diagnostics.py
homeassistant/components/stookwijzer/sensor.py
homeassistant/components/stream/*
homeassistant/components/streamlabswater/*
homeassistant/components/suez_water/*
Expand Down
2 changes: 2 additions & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -1139,6 +1139,8 @@ build.json @home-assistant/supervisor
/homeassistant/components/stiebel_eltron/ @fucm
/homeassistant/components/stookalert/ @fwestenberg @frenck
/tests/components/stookalert/ @fwestenberg @frenck
/homeassistant/components/stookwijzer/ @fwestenberg
/tests/components/stookwijzer/ @fwestenberg
/homeassistant/components/stream/ @hunterjm @uvjustin @allenporter
/tests/components/stream/ @hunterjm @uvjustin @allenporter
/homeassistant/components/stt/ @pvizeli
Expand Down
29 changes: 29 additions & 0 deletions homeassistant/components/stookwijzer/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""The Stookwijzer integration."""
from __future__ import annotations

from stookwijzer import Stookwijzer

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_LATITUDE, CONF_LOCATION, CONF_LONGITUDE, Platform
from homeassistant.core import HomeAssistant

from .const import DOMAIN

PLATFORMS = [Platform.SENSOR]


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Stookwijzer from a config entry."""
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = Stookwijzer(
entry.data[CONF_LOCATION][CONF_LATITUDE],
entry.data[CONF_LOCATION][CONF_LONGITUDE],
)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload Stookwijzer config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
del hass.data[DOMAIN][entry.entry_id]
return unload_ok
45 changes: 45 additions & 0 deletions homeassistant/components/stookwijzer/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""Config flow to configure the Stookwijzer integration."""
from __future__ import annotations

from typing import Any

import voluptuous as vol

from homeassistant.config_entries import ConfigFlow
from homeassistant.const import CONF_LATITUDE, CONF_LOCATION, CONF_LONGITUDE
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers.selector import LocationSelector

from .const import DOMAIN


class StookwijzerFlowHandler(ConfigFlow, domain=DOMAIN):
"""Config flow for Stookwijzer."""

VERSION = 1

async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle a flow initialized by the user."""

if user_input is not None:
return self.async_create_entry(
title="Stookwijzer",
data=user_input,
)

return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{
vol.Required(
CONF_LOCATION,
default={
CONF_LATITUDE: self.hass.config.latitude,
CONF_LONGITUDE: self.hass.config.longitude,
},
): LocationSelector()
}
),
)
16 changes: 16 additions & 0 deletions homeassistant/components/stookwijzer/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""Constants for the Stookwijzer integration."""
import logging
from typing import Final

from homeassistant.backports.enum import StrEnum

DOMAIN: Final = "stookwijzer"
LOGGER = logging.getLogger(__package__)


class StookwijzerState(StrEnum):
"""Stookwijzer states for sensor entity."""

BLUE = "blauw"
ORANGE = "oranje"
RED = "rood"
31 changes: 31 additions & 0 deletions homeassistant/components/stookwijzer/diagnostics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""Diagnostics support for Stookwijzer."""
from __future__ import annotations

from typing import Any

from stookwijzer import Stookwijzer

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant

from .const import DOMAIN


async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: ConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
client: Stookwijzer = hass.data[DOMAIN][entry.entry_id]

last_updated = None
if client.last_updated:
last_updated = client.last_updated.isoformat()

return {
"state": client.state,
"last_updated": last_updated,
"lqi": client.lqi,
"windspeed": client.windspeed,
"weather": client.weather,
"concentrations": client.concentrations,
}
10 changes: 10 additions & 0 deletions homeassistant/components/stookwijzer/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"domain": "stookwijzer",
"name": "Stookwijzer",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/stookwijzer",
"codeowners": ["@fwestenberg"],
"requirements": ["stookwijzer==1.3.0"],
"integration_type": "service",
"iot_class": "cloud_polling"
}
65 changes: 65 additions & 0 deletions homeassistant/components/stookwijzer/sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""This integration provides support for Stookwijzer Sensor."""
from __future__ import annotations

from datetime import timedelta

from stookwijzer import Stookwijzer

from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntryType
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from .const import DOMAIN, StookwijzerState

SCAN_INTERVAL = timedelta(minutes=60)


async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Stookwijzer sensor from a config entry."""
client = hass.data[DOMAIN][entry.entry_id]
async_add_entities([StookwijzerSensor(client, entry)], update_before_add=True)


class StookwijzerSensor(SensorEntity):
"""Defines a Stookwijzer binary sensor."""

_attr_attribution = "Data provided by stookwijzer.nu"
_attr_device_class = SensorDeviceClass.ENUM
_attr_has_entity_name = True
_attr_translation_key = "stookwijzer"

def __init__(self, client: Stookwijzer, entry: ConfigEntry) -> None:
"""Initialize a Stookwijzer device."""
self._client = client
self._attr_options = [cls.value for cls in StookwijzerState]
self._attr_unique_id = entry.entry_id
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, f"{entry.entry_id}")},
name="Stookwijzer",
manufacturer="stookwijzer.nu",
entry_type=DeviceEntryType.SERVICE,
configuration_url="https://www.stookwijzer.nu",
)

def update(self) -> None:
"""Update the data from the Stookwijzer handler."""
self._client.update()

@property
def available(self) -> bool:
"""Return if entity is available."""
return self._client.state is not None

@property
def native_value(self) -> str | None:
"""Return the state of the device."""
if self._client.state is None:
return None
return StookwijzerState(self._client.state).value
23 changes: 23 additions & 0 deletions homeassistant/components/stookwijzer/strings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"config": {
"step": {
"user": {
"description": "Select the location you want to recieve the Stookwijzer information for.",
"data": {
"location": "[%key:common::config_flow::data::location%]"
}
}
}
},
"entity": {
"sensor": {
"stookwijzer": {
"state": {
"blauw": "Blue",
"oranje": "Orange",
"rood": "Red"
}
}
}
}
}
23 changes: 23 additions & 0 deletions homeassistant/components/stookwijzer/translations/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"config": {
"step": {
"user": {
"data": {
"location": "Location"
},
"description": "Select the location you want to recieve the Stookwijzer information for."
}
}
},
"entity": {
"sensor": {
"stookwijzer": {
"state": {
"blauw": "Blue",
"oranje": "Orange",
"rood": "Red"
}
}
}
}
}
1 change: 1 addition & 0 deletions homeassistant/generated/config_flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@
"steam_online",
"steamist",
"stookalert",
"stookwijzer",
"subaru",
"sun",
"surepetcare",
Expand Down
6 changes: 6 additions & 0 deletions homeassistant/generated/integrations.json
Original file line number Diff line number Diff line change
Expand Up @@ -5243,6 +5243,12 @@
"config_flow": true,
"iot_class": "cloud_polling"
},
"stookwijzer": {
"name": "Stookwijzer",
"integration_type": "service",
"config_flow": true,
"iot_class": "cloud_polling"
},
"streamlabswater": {
"name": "StreamLabs",
"integration_type": "hub",
Expand Down
3 changes: 3 additions & 0 deletions requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2405,6 +2405,9 @@ steamodd==4.21
# homeassistant.components.stookalert
stookalert==0.1.4

# homeassistant.components.stookwijzer
stookwijzer==1.3.0

# homeassistant.components.streamlabswater
streamlabswater==1.0.1

Expand Down
3 changes: 3 additions & 0 deletions requirements_test_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1699,6 +1699,9 @@ steamodd==4.21
# homeassistant.components.stookalert
stookalert==0.1.4

# homeassistant.components.stookwijzer
stookwijzer==1.3.0

# homeassistant.components.huawei_lte
# homeassistant.components.solaredge
# homeassistant.components.thermoworks_smoke
Expand Down
1 change: 1 addition & 0 deletions tests/components/stookwijzer/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tests for the Stookwijzer integration."""
42 changes: 42 additions & 0 deletions tests/components/stookwijzer/test_config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""Tests for the Stookwijzer config flow."""
from unittest.mock import patch

from homeassistant.components.stookwijzer.const import DOMAIN
from homeassistant.config_entries import SOURCE_USER
from homeassistant.const import CONF_LATITUDE, CONF_LOCATION, CONF_LONGITUDE
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType


async def test_full_user_flow(hass: HomeAssistant) -> None:
"""Test the full user configuration flow."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)

assert result.get("type") == FlowResultType.FORM
assert result.get("step_id") == SOURCE_USER
assert "flow_id" in result

with patch(
"homeassistant.components.stookwijzer.async_setup_entry", return_value=True
) as mock_setup_entry:
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_LOCATION: {
CONF_LATITUDE: 1.0,
CONF_LONGITUDE: 1.1,
}
},
)

assert result2.get("type") == FlowResultType.CREATE_ENTRY
assert result2.get("data") == {
"location": {
"latitude": 1.0,
"longitude": 1.1,
},
}

assert len(mock_setup_entry.mock_calls) == 1

0 comments on commit 29b2b67

Please sign in to comment.