mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 12:17:07 +00:00
More sensors for SMS integration (#70486)
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
This commit is contained in:
parent
305dff0dc1
commit
551929a175
@ -1,6 +1,9 @@
|
|||||||
"""The sms component."""
|
"""The sms component."""
|
||||||
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
import async_timeout
|
||||||
|
import gammu # pylint: disable=import-error
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
||||||
@ -8,8 +11,18 @@ from homeassistant.const import CONF_DEVICE, Platform
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
|
||||||
from .const import CONF_BAUD_SPEED, DEFAULT_BAUD_SPEED, DOMAIN, SMS_GATEWAY
|
from .const import (
|
||||||
|
CONF_BAUD_SPEED,
|
||||||
|
DEFAULT_BAUD_SPEED,
|
||||||
|
DEFAULT_SCAN_INTERVAL,
|
||||||
|
DOMAIN,
|
||||||
|
GATEWAY,
|
||||||
|
NETWORK_COORDINATOR,
|
||||||
|
SIGNAL_COORDINATOR,
|
||||||
|
SMS_GATEWAY,
|
||||||
|
)
|
||||||
from .gateway import create_sms_gateway
|
from .gateway import create_sms_gateway
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -30,6 +43,8 @@ CONFIG_SCHEMA = vol.Schema(
|
|||||||
extra=vol.ALLOW_EXTRA,
|
extra=vol.ALLOW_EXTRA,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
"""Configure Gammu state machine."""
|
"""Configure Gammu state machine."""
|
||||||
@ -61,7 +76,21 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
gateway = await create_sms_gateway(config, hass)
|
gateway = await create_sms_gateway(config, hass)
|
||||||
if not gateway:
|
if not gateway:
|
||||||
return False
|
return False
|
||||||
hass.data[DOMAIN][SMS_GATEWAY] = gateway
|
|
||||||
|
signal_coordinator = SignalCoordinator(hass, gateway)
|
||||||
|
network_coordinator = NetworkCoordinator(hass, gateway)
|
||||||
|
|
||||||
|
# Fetch initial data so we have data when entities subscribe
|
||||||
|
await signal_coordinator.async_config_entry_first_refresh()
|
||||||
|
await network_coordinator.async_config_entry_first_refresh()
|
||||||
|
|
||||||
|
hass.data.setdefault(DOMAIN, {})
|
||||||
|
hass.data[DOMAIN][SMS_GATEWAY] = {
|
||||||
|
SIGNAL_COORDINATOR: signal_coordinator,
|
||||||
|
NETWORK_COORDINATOR: network_coordinator,
|
||||||
|
GATEWAY: gateway,
|
||||||
|
}
|
||||||
|
|
||||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@ -71,7 +100,51 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
"""Unload a config entry."""
|
"""Unload a config entry."""
|
||||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||||
if unload_ok:
|
if unload_ok:
|
||||||
gateway = hass.data[DOMAIN].pop(SMS_GATEWAY)
|
gateway = hass.data[DOMAIN].pop(SMS_GATEWAY)[GATEWAY]
|
||||||
await gateway.terminate_async()
|
await gateway.terminate_async()
|
||||||
|
|
||||||
return unload_ok
|
return unload_ok
|
||||||
|
|
||||||
|
|
||||||
|
class SignalCoordinator(DataUpdateCoordinator):
|
||||||
|
"""Signal strength coordinator."""
|
||||||
|
|
||||||
|
def __init__(self, hass, gateway):
|
||||||
|
"""Initialize signal strength coordinator."""
|
||||||
|
super().__init__(
|
||||||
|
hass,
|
||||||
|
_LOGGER,
|
||||||
|
name="Device signal state",
|
||||||
|
update_interval=timedelta(seconds=DEFAULT_SCAN_INTERVAL),
|
||||||
|
)
|
||||||
|
self._gateway = gateway
|
||||||
|
|
||||||
|
async def _async_update_data(self):
|
||||||
|
"""Fetch device signal quality."""
|
||||||
|
try:
|
||||||
|
async with async_timeout.timeout(10):
|
||||||
|
return await self._gateway.get_signal_quality_async()
|
||||||
|
except gammu.GSMError as exc:
|
||||||
|
raise UpdateFailed(f"Error communicating with device: {exc}") from exc
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkCoordinator(DataUpdateCoordinator):
|
||||||
|
"""Network info coordinator."""
|
||||||
|
|
||||||
|
def __init__(self, hass, gateway):
|
||||||
|
"""Initialize network info coordinator."""
|
||||||
|
super().__init__(
|
||||||
|
hass,
|
||||||
|
_LOGGER,
|
||||||
|
name="Device network state",
|
||||||
|
update_interval=timedelta(seconds=DEFAULT_SCAN_INTERVAL),
|
||||||
|
)
|
||||||
|
self._gateway = gateway
|
||||||
|
|
||||||
|
async def _async_update_data(self):
|
||||||
|
"""Fetch device network info."""
|
||||||
|
try:
|
||||||
|
async with async_timeout.timeout(10):
|
||||||
|
return await self._gateway.get_network_info_async()
|
||||||
|
except gammu.GSMError as exc:
|
||||||
|
raise UpdateFailed(f"Error communicating with device: {exc}") from exc
|
||||||
|
@ -1,8 +1,17 @@
|
|||||||
"""Constants for sms Component."""
|
"""Constants for sms Component."""
|
||||||
|
from typing import Final
|
||||||
|
|
||||||
|
from homeassistant.components.sensor import SensorDeviceClass, SensorEntityDescription
|
||||||
|
from homeassistant.const import PERCENTAGE, SIGNAL_STRENGTH_DECIBELS
|
||||||
|
from homeassistant.helpers.entity import EntityCategory
|
||||||
|
|
||||||
DOMAIN = "sms"
|
DOMAIN = "sms"
|
||||||
SMS_GATEWAY = "SMS_GATEWAY"
|
SMS_GATEWAY = "SMS_GATEWAY"
|
||||||
SMS_STATE_UNREAD = "UnRead"
|
SMS_STATE_UNREAD = "UnRead"
|
||||||
|
SIGNAL_COORDINATOR = "signal_coordinator"
|
||||||
|
NETWORK_COORDINATOR = "network_coordinator"
|
||||||
|
GATEWAY = "gateway"
|
||||||
|
DEFAULT_SCAN_INTERVAL = 30
|
||||||
CONF_BAUD_SPEED = "baud_speed"
|
CONF_BAUD_SPEED = "baud_speed"
|
||||||
DEFAULT_BAUD_SPEED = "0"
|
DEFAULT_BAUD_SPEED = "0"
|
||||||
DEFAULT_BAUD_SPEEDS = [
|
DEFAULT_BAUD_SPEEDS = [
|
||||||
@ -27,3 +36,61 @@ DEFAULT_BAUD_SPEEDS = [
|
|||||||
{"value": "76800", "label": "76800"},
|
{"value": "76800", "label": "76800"},
|
||||||
{"value": "115200", "label": "115200"},
|
{"value": "115200", "label": "115200"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
SIGNAL_SENSORS: Final[dict[str, SensorEntityDescription]] = {
|
||||||
|
"SignalStrength": SensorEntityDescription(
|
||||||
|
key="SignalStrength",
|
||||||
|
name="Signal Strength",
|
||||||
|
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
"SignalPercent": SensorEntityDescription(
|
||||||
|
key="SignalPercent",
|
||||||
|
icon="mdi:signal-cellular-3",
|
||||||
|
name="Signal Percent",
|
||||||
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
|
entity_registry_enabled_default=True,
|
||||||
|
),
|
||||||
|
"BitErrorRate": SensorEntityDescription(
|
||||||
|
key="BitErrorRate",
|
||||||
|
name="Bit Error Rate",
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
NETWORK_SENSORS: Final[dict[str, SensorEntityDescription]] = {
|
||||||
|
"NetworkName": SensorEntityDescription(
|
||||||
|
key="NetworkName",
|
||||||
|
name="Network Name",
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
"State": SensorEntityDescription(
|
||||||
|
key="State",
|
||||||
|
name="Network Status",
|
||||||
|
entity_registry_enabled_default=True,
|
||||||
|
),
|
||||||
|
"NetworkCode": SensorEntityDescription(
|
||||||
|
key="NetworkCode",
|
||||||
|
name="GSM network code",
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
"CID": SensorEntityDescription(
|
||||||
|
key="CID",
|
||||||
|
name="Cell ID",
|
||||||
|
icon="mdi:radio-tower",
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
"LAC": SensorEntityDescription(
|
||||||
|
key="LAC",
|
||||||
|
name="Local Area Code",
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
@ -154,6 +154,10 @@ class Gateway:
|
|||||||
"""Get the current signal level of the modem."""
|
"""Get the current signal level of the modem."""
|
||||||
return await self._worker.get_signal_quality_async()
|
return await self._worker.get_signal_quality_async()
|
||||||
|
|
||||||
|
async def get_network_info_async(self):
|
||||||
|
"""Get the current network info of the modem."""
|
||||||
|
return await self._worker.get_network_info_async()
|
||||||
|
|
||||||
async def terminate_async(self):
|
async def terminate_async(self):
|
||||||
"""Terminate modem connection."""
|
"""Terminate modem connection."""
|
||||||
return await self._worker.terminate_async()
|
return await self._worker.terminate_async()
|
||||||
|
@ -8,7 +8,7 @@ from homeassistant.components.notify import PLATFORM_SCHEMA, BaseNotificationSer
|
|||||||
from homeassistant.const import CONF_NAME, CONF_RECIPIENT, CONF_TARGET
|
from homeassistant.const import CONF_NAME, CONF_RECIPIENT, CONF_TARGET
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
from .const import DOMAIN, SMS_GATEWAY
|
from .const import DOMAIN, GATEWAY, SMS_GATEWAY
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ def get_service(hass, config, discovery_info=None):
|
|||||||
_LOGGER.error("SMS gateway not found, cannot initialize service")
|
_LOGGER.error("SMS gateway not found, cannot initialize service")
|
||||||
return
|
return
|
||||||
|
|
||||||
gateway = hass.data[DOMAIN][SMS_GATEWAY]
|
gateway = hass.data[DOMAIN][SMS_GATEWAY][GATEWAY]
|
||||||
|
|
||||||
if discovery_info is None:
|
if discovery_info is None:
|
||||||
number = config[CONF_RECIPIENT]
|
number = config[CONF_RECIPIENT]
|
||||||
|
@ -1,22 +1,20 @@
|
|||||||
"""Support for SMS dongle sensor."""
|
"""Support for SMS dongle sensor."""
|
||||||
import logging
|
from homeassistant.components.sensor import SensorEntity
|
||||||
|
|
||||||
import gammu # pylint: disable=import-error
|
|
||||||
|
|
||||||
from homeassistant.components.sensor import (
|
|
||||||
SensorDeviceClass,
|
|
||||||
SensorEntity,
|
|
||||||
SensorEntityDescription,
|
|
||||||
)
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import SIGNAL_STRENGTH_DECIBELS
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity import DeviceInfo
|
from homeassistant.helpers.entity import DeviceInfo
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
from .const import DOMAIN, SMS_GATEWAY
|
from .const import (
|
||||||
|
DOMAIN,
|
||||||
_LOGGER = logging.getLogger(__name__)
|
GATEWAY,
|
||||||
|
NETWORK_COORDINATOR,
|
||||||
|
NETWORK_SENSORS,
|
||||||
|
SIGNAL_COORDINATOR,
|
||||||
|
SIGNAL_SENSORS,
|
||||||
|
SMS_GATEWAY,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
@ -24,61 +22,46 @@ async def async_setup_entry(
|
|||||||
config_entry: ConfigEntry,
|
config_entry: ConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the GSM Signal Sensor sensor."""
|
"""Set up all device sensors."""
|
||||||
gateway = hass.data[DOMAIN][SMS_GATEWAY]
|
sms_data = hass.data[DOMAIN][SMS_GATEWAY]
|
||||||
imei = await gateway.get_imei_async()
|
signal_coordinator = sms_data[SIGNAL_COORDINATOR]
|
||||||
async_add_entities(
|
network_coordinator = sms_data[NETWORK_COORDINATOR]
|
||||||
[
|
gateway = sms_data[GATEWAY]
|
||||||
GSMSignalSensor(
|
unique_id = str(await gateway.get_imei_async())
|
||||||
hass,
|
entities = []
|
||||||
gateway,
|
for description in SIGNAL_SENSORS.values():
|
||||||
imei,
|
entities.append(
|
||||||
SensorEntityDescription(
|
DeviceSensor(
|
||||||
key="signal",
|
signal_coordinator,
|
||||||
name=f"gsm_signal_imei_{imei}",
|
description,
|
||||||
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
|
unique_id,
|
||||||
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
|
|
||||||
entity_registry_enabled_default=False,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
],
|
|
||||||
True,
|
|
||||||
)
|
)
|
||||||
|
for description in NETWORK_SENSORS.values():
|
||||||
|
entities.append(
|
||||||
|
DeviceSensor(
|
||||||
|
network_coordinator,
|
||||||
|
description,
|
||||||
|
unique_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
async_add_entities(entities, True)
|
||||||
|
|
||||||
|
|
||||||
class GSMSignalSensor(SensorEntity):
|
class DeviceSensor(CoordinatorEntity, SensorEntity):
|
||||||
"""Implementation of a GSM Signal sensor."""
|
"""Implementation of a device sensor."""
|
||||||
|
|
||||||
def __init__(self, hass, gateway, imei, description):
|
def __init__(self, coordinator, description, unique_id):
|
||||||
"""Initialize the GSM Signal sensor."""
|
"""Initialize the device sensor."""
|
||||||
|
super().__init__(coordinator)
|
||||||
self._attr_device_info = DeviceInfo(
|
self._attr_device_info = DeviceInfo(
|
||||||
identifiers={(DOMAIN, str(imei))},
|
identifiers={(DOMAIN, unique_id)},
|
||||||
name="SMS Gateway",
|
name="SMS Gateway",
|
||||||
)
|
)
|
||||||
self._attr_unique_id = str(imei)
|
self._attr_unique_id = f"{unique_id}_{description.key}"
|
||||||
self._hass = hass
|
|
||||||
self._gateway = gateway
|
|
||||||
self._state = None
|
|
||||||
self.entity_description = description
|
self.entity_description = description
|
||||||
|
|
||||||
@property
|
|
||||||
def available(self):
|
|
||||||
"""Return if the sensor data are available."""
|
|
||||||
return self._state is not None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self):
|
def native_value(self):
|
||||||
"""Return the state of the device."""
|
"""Return the state of the device."""
|
||||||
return self._state["SignalStrength"]
|
return self.coordinator.data.get(self.entity_description.key)
|
||||||
|
|
||||||
async def async_update(self):
|
|
||||||
"""Get the latest data from the modem."""
|
|
||||||
try:
|
|
||||||
self._state = await self._gateway.get_signal_quality_async()
|
|
||||||
except gammu.GSMError as exc:
|
|
||||||
_LOGGER.error("Failed to read signal quality: %s", exc)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def extra_state_attributes(self):
|
|
||||||
"""Return the sensor attributes."""
|
|
||||||
return self._state
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user