Skip to content

Commit

Permalink
Add SensorPush Cloud integration
Browse files Browse the repository at this point in the history
  • Loading branch information
sstallion committed Jan 9, 2025
1 parent c5f80dd commit 4b0c94f
Show file tree
Hide file tree
Showing 22 changed files with 1,957 additions and 3 deletions.
1 change: 1 addition & 0 deletions .strict-typing
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,7 @@ homeassistant.components.select.*
homeassistant.components.sensibo.*
homeassistant.components.sensirion_ble.*
homeassistant.components.sensor.*
homeassistant.components.sensorpush_cloud.*
homeassistant.components.sensoterra.*
homeassistant.components.senz.*
homeassistant.components.sfr_box.*
Expand Down
2 changes: 2 additions & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -1332,6 +1332,8 @@ build.json @home-assistant/supervisor
/tests/components/sensorpro/ @bdraco
/homeassistant/components/sensorpush/ @bdraco
/tests/components/sensorpush/ @bdraco
/homeassistant/components/sensorpush_cloud/ @sstallion
/tests/components/sensorpush_cloud/ @sstallion
/homeassistant/components/sensoterra/ @markruys
/tests/components/sensoterra/ @markruys
/homeassistant/components/sentry/ @dcramer @frenck
Expand Down
5 changes: 5 additions & 0 deletions homeassistant/brands/sensorpush.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"domain": "sensorpush",
"name": "SensorPush",
"integrations": ["sensorpush", "sensorpush_cloud"]
}
28 changes: 28 additions & 0 deletions homeassistant/components/sensorpush_cloud/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""The SensorPush Cloud integration."""

from __future__ import annotations

from homeassistant.const import Platform
from homeassistant.core import HomeAssistant

from .coordinator import SensorPushCloudConfigEntry, SensorPushCloudCoordinator

PLATFORMS: list[Platform] = [Platform.SENSOR]


async def async_setup_entry(
hass: HomeAssistant, entry: SensorPushCloudConfigEntry
) -> bool:
"""Set up SensorPush Cloud from a config entry."""
coordinator = SensorPushCloudCoordinator(hass, entry)
entry.runtime_data = coordinator
await coordinator.async_config_entry_first_refresh()
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True


async def async_unload_entry(
hass: HomeAssistant, entry: SensorPushCloudConfigEntry
) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
65 changes: 65 additions & 0 deletions homeassistant/components/sensorpush_cloud/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""Config flow for the SensorPush Cloud integration."""

from __future__ import annotations

from typing import Any

from sensorpush_ha import SensorPushCloudApi, SensorPushCloudError
import voluptuous as vol

from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.selector import (
TextSelector,
TextSelectorConfig,
TextSelectorType,
)

from .const import DOMAIN, LOGGER


class SensorPushCloudConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for SensorPush Cloud."""

async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle the initial step."""
errors: dict[str, str] = {}
if user_input is not None:
email, password = user_input[CONF_EMAIL], user_input[CONF_PASSWORD]
await self.async_set_unique_id(email, raise_on_progress=False)
self._abort_if_unique_id_configured()
clientsession = async_get_clientsession(self.hass)
api = SensorPushCloudApi(email, password, clientsession)
try:
await api.async_authorize()
except SensorPushCloudError:
LOGGER.exception("Invalid authentication")
errors["base"] = "invalid_auth"
except Exception: # noqa: BLE001
LOGGER.exception("Unexpected error")
errors["base"] = "unknown"
else:
return self.async_create_entry(title=email, data=user_input)

return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{
vol.Required(CONF_EMAIL): TextSelector(
TextSelectorConfig(
type=TextSelectorType.EMAIL, autocomplete="username"
)
),
vol.Required(CONF_PASSWORD): TextSelector(
TextSelectorConfig(
type=TextSelectorType.PASSWORD,
autocomplete="current-password",
)
),
}
),
errors=errors,
)
12 changes: 12 additions & 0 deletions homeassistant/components/sensorpush_cloud/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""Constants for the SensorPush Cloud integration."""

from datetime import timedelta
import logging
from typing import Final

LOGGER = logging.getLogger(__package__)

DOMAIN: Final = "sensorpush_cloud"

UPDATE_INTERVAL: Final = timedelta(seconds=60)
MAX_TIME_BETWEEN_UPDATES: Final = UPDATE_INTERVAL * 60
45 changes: 45 additions & 0 deletions homeassistant/components/sensorpush_cloud/coordinator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""Coordinator for the SensorPush Cloud integration."""

from __future__ import annotations

from sensorpush_ha import (
SensorPushCloudApi,
SensorPushCloudData,
SensorPushCloudError,
SensorPushCloudHelper,
)

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from .const import LOGGER, UPDATE_INTERVAL

type SensorPushCloudConfigEntry = ConfigEntry[SensorPushCloudCoordinator]


class SensorPushCloudCoordinator(DataUpdateCoordinator[dict[str, SensorPushCloudData]]):
"""SensorPush Cloud coordinator."""

def __init__(self, hass: HomeAssistant, entry: SensorPushCloudConfigEntry) -> None:
"""Initialize the coordinator."""
super().__init__(
hass,
LOGGER,
name=entry.title,
update_interval=UPDATE_INTERVAL,
config_entry=entry,
)
email, password = entry.data[CONF_EMAIL], entry.data[CONF_PASSWORD]
clientsession = async_get_clientsession(hass)
api = SensorPushCloudApi(email, password, clientsession)
self.helper = SensorPushCloudHelper(api)

async def _async_update_data(self) -> dict[str, SensorPushCloudData]:
"""Fetch data from API endpoints."""
try:
return await self.helper.async_get_data()
except SensorPushCloudError as e:
raise UpdateFailed(e) from e
11 changes: 11 additions & 0 deletions homeassistant/components/sensorpush_cloud/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"domain": "sensorpush_cloud",
"name": "SensorPush Cloud",
"codeowners": ["@sstallion"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/sensorpush_cloud",
"iot_class": "cloud_polling",
"loggers": ["sensorpush_api", "sensorpush_ha"],
"quality_scale": "bronze",
"requirements": ["sensorpush-api==2.1.1", "sensorpush-ha==1.2.0"]
}
68 changes: 68 additions & 0 deletions homeassistant/components/sensorpush_cloud/quality_scale.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
rules:
# Bronze
action-setup:
status: exempt
comment: Integration does not register custom actions.
appropriate-polling: done
brands: done
common-modules: done
config-flow-test-coverage: done
config-flow: done
dependency-transparency: done
docs-actions:
status: exempt
comment: Integration does not register custom actions.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
entity-event-setup: done
entity-unique-id: done
has-entity-name: done
runtime-data: done
test-before-configure: done
test-before-setup: done
unique-config-entry: done

# Silver
action-exceptions:
status: exempt
comment: Integration does not register custom actions.
config-entry-unloading: done
docs-configuration-parameters:
status: exempt
comment: Integration does not support options flow.
docs-installation-parameters: todo
entity-unavailable: done
integration-owner: done
log-when-unavailable: done
parallel-updates: done
reauthentication-flow: todo
test-coverage: todo

# Gold
devices: todo
diagnostics: todo
discovery-update-info: todo
discovery: todo
docs-data-update: todo
docs-examples: todo
docs-known-limitations: done
docs-supported-devices: done
docs-supported-functions: done
docs-troubleshooting: todo
docs-use-cases: todo
dynamic-devices: todo
entity-category: todo
entity-device-class: done
entity-disabled-by-default: done
entity-translations: todo
exception-translations: todo
icon-translations: todo
reconfiguration-flow: todo
repair-issues: todo
stale-devices: todo

# Platinum
async-dependency: done
inject-websession: done
strict-typing: done
Loading

0 comments on commit 4b0c94f

Please sign in to comment.