More sensors for SMS integration (#70486)

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
pull/73804/head
Paul Annekov 2022-06-29 07:37:23 +03:00 committed by GitHub
parent 305dff0dc1
commit 551929a175
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 191 additions and 64 deletions

View File

@ -1,6 +1,9 @@
"""The sms component."""
from datetime import timedelta
import logging
import async_timeout
import gammu # pylint: disable=import-error
import voluptuous as vol
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.helpers import config_validation as cv
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
_LOGGER = logging.getLogger(__name__)
@ -30,6 +43,8 @@ CONFIG_SCHEMA = vol.Schema(
extra=vol.ALLOW_EXTRA,
)
_LOGGER = logging.getLogger(__name__)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""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)
if not gateway:
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)
return True
@ -71,7 +100,51 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
gateway = hass.data[DOMAIN].pop(SMS_GATEWAY)
gateway = hass.data[DOMAIN].pop(SMS_GATEWAY)[GATEWAY]
await gateway.terminate_async()
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

View File

@ -1,8 +1,17 @@
"""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"
SMS_GATEWAY = "SMS_GATEWAY"
SMS_STATE_UNREAD = "UnRead"
SIGNAL_COORDINATOR = "signal_coordinator"
NETWORK_COORDINATOR = "network_coordinator"
GATEWAY = "gateway"
DEFAULT_SCAN_INTERVAL = 30
CONF_BAUD_SPEED = "baud_speed"
DEFAULT_BAUD_SPEED = "0"
DEFAULT_BAUD_SPEEDS = [
@ -27,3 +36,61 @@ DEFAULT_BAUD_SPEEDS = [
{"value": "76800", "label": "76800"},
{"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,
),
}

View File

@ -154,6 +154,10 @@ class Gateway:
"""Get the current signal level of the modem."""
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):
"""Terminate modem connection."""
return await self._worker.terminate_async()

View File

@ -8,7 +8,7 @@ from homeassistant.components.notify import PLATFORM_SCHEMA, BaseNotificationSer
from homeassistant.const import CONF_NAME, CONF_RECIPIENT, CONF_TARGET
import homeassistant.helpers.config_validation as cv
from .const import DOMAIN, SMS_GATEWAY
from .const import DOMAIN, GATEWAY, SMS_GATEWAY
_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")
return
gateway = hass.data[DOMAIN][SMS_GATEWAY]
gateway = hass.data[DOMAIN][SMS_GATEWAY][GATEWAY]
if discovery_info is None:
number = config[CONF_RECIPIENT]

View File

@ -1,22 +1,20 @@
"""Support for SMS dongle sensor."""
import logging
import gammu # pylint: disable=import-error
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
)
from homeassistant.components.sensor import SensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import SIGNAL_STRENGTH_DECIBELS
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN, SMS_GATEWAY
_LOGGER = logging.getLogger(__name__)
from .const import (
DOMAIN,
GATEWAY,
NETWORK_COORDINATOR,
NETWORK_SENSORS,
SIGNAL_COORDINATOR,
SIGNAL_SENSORS,
SMS_GATEWAY,
)
async def async_setup_entry(
@ -24,61 +22,46 @@ async def async_setup_entry(
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the GSM Signal Sensor sensor."""
gateway = hass.data[DOMAIN][SMS_GATEWAY]
imei = await gateway.get_imei_async()
async_add_entities(
[
GSMSignalSensor(
hass,
gateway,
imei,
SensorEntityDescription(
key="signal",
name=f"gsm_signal_imei_{imei}",
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
entity_registry_enabled_default=False,
),
"""Set up all device sensors."""
sms_data = hass.data[DOMAIN][SMS_GATEWAY]
signal_coordinator = sms_data[SIGNAL_COORDINATOR]
network_coordinator = sms_data[NETWORK_COORDINATOR]
gateway = sms_data[GATEWAY]
unique_id = str(await gateway.get_imei_async())
entities = []
for description in SIGNAL_SENSORS.values():
entities.append(
DeviceSensor(
signal_coordinator,
description,
unique_id,
)
],
True,
)
)
for description in NETWORK_SENSORS.values():
entities.append(
DeviceSensor(
network_coordinator,
description,
unique_id,
)
)
async_add_entities(entities, True)
class GSMSignalSensor(SensorEntity):
"""Implementation of a GSM Signal sensor."""
class DeviceSensor(CoordinatorEntity, SensorEntity):
"""Implementation of a device sensor."""
def __init__(self, hass, gateway, imei, description):
"""Initialize the GSM Signal sensor."""
def __init__(self, coordinator, description, unique_id):
"""Initialize the device sensor."""
super().__init__(coordinator)
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, str(imei))},
identifiers={(DOMAIN, unique_id)},
name="SMS Gateway",
)
self._attr_unique_id = str(imei)
self._hass = hass
self._gateway = gateway
self._state = None
self._attr_unique_id = f"{unique_id}_{description.key}"
self.entity_description = description
@property
def available(self):
"""Return if the sensor data are available."""
return self._state is not None
@property
def native_value(self):
"""Return the state of the device."""
return self._state["SignalStrength"]
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
return self.coordinator.data.get(self.entity_description.key)