-
-
Notifications
You must be signed in to change notification settings - Fork 32k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
22 changed files
with
762 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"domain": "sensorpush", | ||
"name": "SensorPush", | ||
"integrations": ["sensorpush", "sensorpush_cloud"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
"""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 .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() | ||
api = SensorPushCloudApi(self.hass, email, password) | ||
try: | ||
await api.async_authorize() | ||
except SensorPushCloudError as e: | ||
errors["base"] = str(e) | ||
except Exception: # noqa: BLE001 | ||
LOGGER.exception("Unexpected exception") | ||
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): str, | ||
vol.Required(CONF_PASSWORD): str, | ||
} | ||
), | ||
errors=errors, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
"""Coordinator for the SensorPush Cloud integration.""" | ||
|
||
from __future__ import annotations | ||
|
||
from collections.abc import Iterable | ||
|
||
from sensorpush_ha import SensorPushCloudApi, SensorPushCloudData, SensorPushCloudHelper | ||
|
||
from homeassistant.config_entries import ConfigEntry | ||
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD | ||
from homeassistant.core import HomeAssistant | ||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator | ||
|
||
from .const import LOGGER, UPDATE_INTERVAL | ||
|
||
type SensorPushCloudConfigEntry = ConfigEntry[SensorPushCloudCoordinator] | ||
|
||
|
||
class SensorPushCloudCoordinator(DataUpdateCoordinator[dict[str, SensorPushCloudData]]): | ||
"""SensorPush Cloud coordinator.""" | ||
|
||
device_ids: Iterable[str] | ||
|
||
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] | ||
api = SensorPushCloudApi(hass, email, password) | ||
self.helper = SensorPushCloudHelper(api) | ||
|
||
async def _async_update_data(self) -> dict[str, SensorPushCloudData]: | ||
"""Fetch data from API endpoints.""" | ||
data = await self.helper.async_get_data() | ||
self.device_ids = data.keys() | ||
return data |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"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"], | ||
"requirements": ["sensorpush-api==2.1.1", "sensorpush-ha==1.1.1"] | ||
} |
60 changes: 60 additions & 0 deletions
60
homeassistant/components/sensorpush_cloud/quality_scale.yaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
rules: | ||
# Bronze | ||
action-setup: done | ||
appropriate-polling: done | ||
brands: done | ||
common-modules: done | ||
config-flow-test-coverage: done | ||
config-flow: done | ||
dependency-transparency: done | ||
docs-actions: done | ||
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: done | ||
config-entry-unloading: todo | ||
docs-configuration-parameters: done | ||
docs-installation-parameters: done | ||
entity-unavailable: done | ||
integration-owner: done | ||
log-when-unavailable: todo | ||
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: todo | ||
docs-supported-devices: todo | ||
docs-supported-functions: todo | ||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
"""Support for SensorPush Cloud sensors.""" | ||
|
||
from __future__ import annotations | ||
|
||
from typing import Final | ||
|
||
from homeassistant.components.sensor import ( | ||
SensorDeviceClass, | ||
SensorEntity, | ||
SensorEntityDescription, | ||
SensorStateClass, | ||
) | ||
from homeassistant.const import ( | ||
ATTR_TEMPERATURE, | ||
PERCENTAGE, | ||
SIGNAL_STRENGTH_DECIBELS_MILLIWATT, | ||
UnitOfElectricPotential, | ||
UnitOfLength, | ||
UnitOfPressure, | ||
UnitOfTemperature, | ||
) | ||
from homeassistant.core import HomeAssistant | ||
from homeassistant.helpers.entity_platform import AddEntitiesCallback | ||
from homeassistant.helpers.typing import StateType | ||
from homeassistant.helpers.update_coordinator import CoordinatorEntity | ||
from homeassistant.util import dt as dt_util | ||
|
||
from .const import DOMAIN, LOGGER, MAX_TIME_BETWEEN_UPDATES | ||
from .coordinator import SensorPushCloudConfigEntry, SensorPushCloudCoordinator | ||
|
||
# Coordinator is used to centralize the data updates | ||
PARALLEL_UPDATES = 0 | ||
|
||
ATTR_ALTITUDE: Final = "altitude" | ||
ATTR_ATMOSPHERIC_PRESSURE: Final = "atmospheric_pressure" | ||
ATTR_BATTERY_VOLTAGE: Final = "battery_voltage" | ||
ATTR_DEWPOINT: Final = "dewpoint" | ||
ATTR_HUMIDITY: Final = "humidity" | ||
ATTR_LAST_UPDATE: Final = "last_update" | ||
ATTR_SIGNAL_STRENGTH: Final = "signal_strength" | ||
ATTR_VAPOR_PRESSURE: Final = "vapor_pressure" | ||
|
||
SENSORS: tuple[SensorEntityDescription, ...] = ( | ||
SensorEntityDescription( | ||
key=ATTR_ALTITUDE, | ||
device_class=SensorDeviceClass.DISTANCE, | ||
entity_registry_enabled_default=False, | ||
translation_key="altitude", | ||
native_unit_of_measurement=UnitOfLength.FEET, | ||
state_class=SensorStateClass.MEASUREMENT, | ||
), | ||
SensorEntityDescription( | ||
key=ATTR_ATMOSPHERIC_PRESSURE, | ||
device_class=SensorDeviceClass.ATMOSPHERIC_PRESSURE, | ||
entity_registry_enabled_default=False, | ||
native_unit_of_measurement=UnitOfPressure.INHG, | ||
state_class=SensorStateClass.MEASUREMENT, | ||
), | ||
SensorEntityDescription( | ||
key=ATTR_BATTERY_VOLTAGE, | ||
device_class=SensorDeviceClass.VOLTAGE, | ||
entity_registry_enabled_default=False, | ||
translation_key="battery_voltage", | ||
native_unit_of_measurement=UnitOfElectricPotential.VOLT, | ||
state_class=SensorStateClass.MEASUREMENT, | ||
), | ||
SensorEntityDescription( | ||
key=ATTR_DEWPOINT, | ||
device_class=SensorDeviceClass.TEMPERATURE, | ||
entity_registry_enabled_default=False, | ||
translation_key="dewpoint", | ||
native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT, | ||
state_class=SensorStateClass.MEASUREMENT, | ||
), | ||
SensorEntityDescription( | ||
key=ATTR_HUMIDITY, | ||
device_class=SensorDeviceClass.HUMIDITY, | ||
native_unit_of_measurement=PERCENTAGE, | ||
state_class=SensorStateClass.MEASUREMENT, | ||
), | ||
SensorEntityDescription( | ||
key=ATTR_SIGNAL_STRENGTH, | ||
device_class=SensorDeviceClass.SIGNAL_STRENGTH, | ||
entity_registry_enabled_default=False, | ||
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT, | ||
state_class=SensorStateClass.MEASUREMENT, | ||
), | ||
SensorEntityDescription( | ||
key=ATTR_TEMPERATURE, | ||
device_class=SensorDeviceClass.TEMPERATURE, | ||
native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT, | ||
state_class=SensorStateClass.MEASUREMENT, | ||
), | ||
SensorEntityDescription( | ||
key=ATTR_VAPOR_PRESSURE, | ||
device_class=SensorDeviceClass.PRESSURE, | ||
entity_registry_enabled_default=False, | ||
translation_key="vapor_pressure", | ||
native_unit_of_measurement=UnitOfPressure.KPA, | ||
state_class=SensorStateClass.MEASUREMENT, | ||
), | ||
) | ||
|
||
|
||
async def async_setup_entry( | ||
hass: HomeAssistant, | ||
entry: SensorPushCloudConfigEntry, | ||
async_add_entities: AddEntitiesCallback, | ||
) -> None: | ||
"""Set up SensorPush Cloud sensors.""" | ||
coordinator: SensorPushCloudCoordinator = entry.runtime_data | ||
async_add_entities( | ||
SensorPushCloudSensor(coordinator, entity_description, device_id) | ||
for entity_description in SENSORS | ||
for device_id in coordinator.device_ids | ||
) | ||
|
||
|
||
class SensorPushCloudSensor( | ||
CoordinatorEntity[SensorPushCloudCoordinator], SensorEntity | ||
): | ||
"""SensorPush Cloud sensor.""" | ||
|
||
_attr_has_entity_name = True | ||
|
||
def __init__( | ||
self, | ||
coordinator: SensorPushCloudCoordinator, | ||
entity_description: SensorEntityDescription, | ||
device_id: str, | ||
) -> None: | ||
"""Initialize the sensor.""" | ||
super().__init__(coordinator) | ||
self.entity_description = entity_description | ||
self.device_id = device_id | ||
|
||
if device_id not in self.coordinator.data: | ||
LOGGER.warning("Ignoring inactive device: %s", device_id) | ||
self._attr_available = False | ||
return | ||
|
||
device = self.coordinator.data[device_id] | ||
self._attr_unique_id = f"{device.device_id}_{entity_description.key}" | ||
self._attr_device_info = device.device_info(DOMAIN) | ||
|
||
@property | ||
def available(self) -> bool: | ||
"""Return true if entity is available.""" | ||
if self.device_id not in self.coordinator.data: | ||
return False # inactive device | ||
last_update = self.coordinator.data[self.device_id][ATTR_LAST_UPDATE] | ||
return bool(dt_util.utcnow() < (last_update + MAX_TIME_BETWEEN_UPDATES)) | ||
|
||
@property | ||
def native_value(self) -> StateType: | ||
"""Return the value reported by the sensor.""" | ||
value: StateType = self.coordinator.data[self.device_id][ | ||
self.entity_description.key | ||
] | ||
return value |
Oops, something went wrong.