Skip to content

Commit 73442e8

Browse files
authored
Add SensorPush Cloud integration (#134223)
1 parent 0d8c449 commit 73442e8

File tree

22 files changed

+1955
-3
lines changed

22 files changed

+1955
-3
lines changed

.strict-typing

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,7 @@ homeassistant.components.select.*
438438
homeassistant.components.sensibo.*
439439
homeassistant.components.sensirion_ble.*
440440
homeassistant.components.sensor.*
441+
homeassistant.components.sensorpush_cloud.*
441442
homeassistant.components.sensoterra.*
442443
homeassistant.components.senz.*
443444
homeassistant.components.sfr_box.*

CODEOWNERS

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"domain": "sensorpush",
3+
"name": "SensorPush",
4+
"integrations": ["sensorpush", "sensorpush_cloud"]
5+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"""The SensorPush Cloud integration."""
2+
3+
from __future__ import annotations
4+
5+
from homeassistant.const import Platform
6+
from homeassistant.core import HomeAssistant
7+
8+
from .coordinator import SensorPushCloudConfigEntry, SensorPushCloudCoordinator
9+
10+
PLATFORMS: list[Platform] = [Platform.SENSOR]
11+
12+
13+
async def async_setup_entry(
14+
hass: HomeAssistant, entry: SensorPushCloudConfigEntry
15+
) -> bool:
16+
"""Set up SensorPush Cloud from a config entry."""
17+
coordinator = SensorPushCloudCoordinator(hass, entry)
18+
entry.runtime_data = coordinator
19+
await coordinator.async_config_entry_first_refresh()
20+
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
21+
return True
22+
23+
24+
async def async_unload_entry(
25+
hass: HomeAssistant, entry: SensorPushCloudConfigEntry
26+
) -> bool:
27+
"""Unload a config entry."""
28+
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"""Config flow for the SensorPush Cloud integration."""
2+
3+
from __future__ import annotations
4+
5+
from typing import Any
6+
7+
from sensorpush_ha import SensorPushCloudApi, SensorPushCloudAuthError
8+
import voluptuous as vol
9+
10+
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
11+
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
12+
from homeassistant.helpers.aiohttp_client import async_get_clientsession
13+
from homeassistant.helpers.selector import (
14+
TextSelector,
15+
TextSelectorConfig,
16+
TextSelectorType,
17+
)
18+
19+
from .const import DOMAIN, LOGGER
20+
21+
22+
class SensorPushCloudConfigFlow(ConfigFlow, domain=DOMAIN):
23+
"""Handle a config flow for SensorPush Cloud."""
24+
25+
async def async_step_user(
26+
self, user_input: dict[str, Any] | None = None
27+
) -> ConfigFlowResult:
28+
"""Handle the initial step."""
29+
errors: dict[str, str] = {}
30+
if user_input is not None:
31+
email, password = user_input[CONF_EMAIL], user_input[CONF_PASSWORD]
32+
await self.async_set_unique_id(email)
33+
self._abort_if_unique_id_configured()
34+
clientsession = async_get_clientsession(self.hass)
35+
api = SensorPushCloudApi(email, password, clientsession)
36+
try:
37+
await api.async_authorize()
38+
except SensorPushCloudAuthError:
39+
errors["base"] = "invalid_auth"
40+
except Exception: # noqa: BLE001
41+
LOGGER.exception("Unexpected error")
42+
errors["base"] = "unknown"
43+
else:
44+
return self.async_create_entry(title=email, data=user_input)
45+
46+
return self.async_show_form(
47+
step_id="user",
48+
data_schema=vol.Schema(
49+
{
50+
vol.Required(CONF_EMAIL): TextSelector(
51+
TextSelectorConfig(
52+
type=TextSelectorType.EMAIL, autocomplete="username"
53+
)
54+
),
55+
vol.Required(CONF_PASSWORD): TextSelector(
56+
TextSelectorConfig(
57+
type=TextSelectorType.PASSWORD,
58+
autocomplete="current-password",
59+
)
60+
),
61+
}
62+
),
63+
errors=errors,
64+
)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
"""Constants for the SensorPush Cloud integration."""
2+
3+
from datetime import timedelta
4+
import logging
5+
from typing import Final
6+
7+
LOGGER = logging.getLogger(__package__)
8+
9+
DOMAIN: Final = "sensorpush_cloud"
10+
11+
UPDATE_INTERVAL: Final = timedelta(seconds=60)
12+
MAX_TIME_BETWEEN_UPDATES: Final = UPDATE_INTERVAL * 60
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
"""Coordinator for the SensorPush Cloud integration."""
2+
3+
from __future__ import annotations
4+
5+
from sensorpush_ha import (
6+
SensorPushCloudApi,
7+
SensorPushCloudData,
8+
SensorPushCloudError,
9+
SensorPushCloudHelper,
10+
)
11+
12+
from homeassistant.config_entries import ConfigEntry
13+
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
14+
from homeassistant.core import HomeAssistant
15+
from homeassistant.helpers.aiohttp_client import async_get_clientsession
16+
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
17+
18+
from .const import LOGGER, UPDATE_INTERVAL
19+
20+
type SensorPushCloudConfigEntry = ConfigEntry[SensorPushCloudCoordinator]
21+
22+
23+
class SensorPushCloudCoordinator(DataUpdateCoordinator[dict[str, SensorPushCloudData]]):
24+
"""SensorPush Cloud coordinator."""
25+
26+
def __init__(self, hass: HomeAssistant, entry: SensorPushCloudConfigEntry) -> None:
27+
"""Initialize the coordinator."""
28+
super().__init__(
29+
hass,
30+
LOGGER,
31+
name=entry.title,
32+
update_interval=UPDATE_INTERVAL,
33+
config_entry=entry,
34+
)
35+
email, password = entry.data[CONF_EMAIL], entry.data[CONF_PASSWORD]
36+
clientsession = async_get_clientsession(hass)
37+
api = SensorPushCloudApi(email, password, clientsession)
38+
self.helper = SensorPushCloudHelper(api)
39+
40+
async def _async_update_data(self) -> dict[str, SensorPushCloudData]:
41+
"""Fetch data from API endpoints."""
42+
try:
43+
return await self.helper.async_get_data()
44+
except SensorPushCloudError as e:
45+
raise UpdateFailed(e) from e
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"domain": "sensorpush_cloud",
3+
"name": "SensorPush Cloud",
4+
"codeowners": ["@sstallion"],
5+
"config_flow": true,
6+
"documentation": "https://www.home-assistant.io/integrations/sensorpush_cloud",
7+
"iot_class": "cloud_polling",
8+
"loggers": ["sensorpush_api", "sensorpush_ha"],
9+
"quality_scale": "bronze",
10+
"requirements": ["sensorpush-api==2.1.1", "sensorpush-ha==1.3.2"]
11+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
rules:
2+
# Bronze
3+
action-setup:
4+
status: exempt
5+
comment: Integration does not register custom actions.
6+
appropriate-polling: done
7+
brands: done
8+
common-modules: done
9+
config-flow-test-coverage: done
10+
config-flow: done
11+
dependency-transparency: done
12+
docs-actions:
13+
status: exempt
14+
comment: Integration does not register custom actions.
15+
docs-high-level-description: done
16+
docs-installation-instructions: done
17+
docs-removal-instructions: done
18+
entity-event-setup: done
19+
entity-unique-id: done
20+
has-entity-name: done
21+
runtime-data: done
22+
test-before-configure: done
23+
test-before-setup: done
24+
unique-config-entry: done
25+
26+
# Silver
27+
action-exceptions:
28+
status: exempt
29+
comment: Integration does not register custom actions.
30+
config-entry-unloading: done
31+
docs-configuration-parameters:
32+
status: exempt
33+
comment: Integration does not support options flow.
34+
docs-installation-parameters: todo
35+
entity-unavailable: done
36+
integration-owner: done
37+
log-when-unavailable: done
38+
parallel-updates: done
39+
reauthentication-flow: todo
40+
test-coverage: todo
41+
42+
# Gold
43+
devices: done
44+
diagnostics: todo
45+
discovery-update-info: todo
46+
discovery: todo
47+
docs-data-update: todo
48+
docs-examples: todo
49+
docs-known-limitations: done
50+
docs-supported-devices: done
51+
docs-supported-functions: done
52+
docs-troubleshooting: todo
53+
docs-use-cases: todo
54+
dynamic-devices: todo
55+
entity-category: todo
56+
entity-device-class: done
57+
entity-disabled-by-default: done
58+
entity-translations: todo
59+
exception-translations: todo
60+
icon-translations: todo
61+
reconfiguration-flow: todo
62+
repair-issues: todo
63+
stale-devices: todo
64+
65+
# Platinum
66+
async-dependency: done
67+
inject-websession: done
68+
strict-typing: done

0 commit comments

Comments
 (0)