mirror of
https://github.com/home-assistant/core.git
synced 2026-02-15 07:36:16 +00:00
Remove sms integration (#155460)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
6
.github/workflows/ci.yaml
vendored
6
.github/workflows/ci.yaml
vendored
@@ -502,7 +502,6 @@ jobs:
|
||||
libavfilter-dev \
|
||||
libavformat-dev \
|
||||
libavutil-dev \
|
||||
libgammu-dev \
|
||||
libswresample-dev \
|
||||
libswscale-dev \
|
||||
libudev-dev
|
||||
@@ -801,8 +800,7 @@ jobs:
|
||||
-o Dir::State::Lists=${{ env.APT_LIST_CACHE_DIR }} \
|
||||
bluez \
|
||||
ffmpeg \
|
||||
libturbojpeg \
|
||||
libgammu-dev
|
||||
libturbojpeg
|
||||
- *checkout
|
||||
- *setup-python-default
|
||||
- *cache-restore-python-default
|
||||
@@ -853,7 +851,6 @@ jobs:
|
||||
bluez \
|
||||
ffmpeg \
|
||||
libturbojpeg \
|
||||
libgammu-dev \
|
||||
libxml2-utils
|
||||
- *checkout
|
||||
- *setup-python-matrix
|
||||
@@ -1233,7 +1230,6 @@ jobs:
|
||||
bluez \
|
||||
ffmpeg \
|
||||
libturbojpeg \
|
||||
libgammu-dev \
|
||||
libxml2-utils
|
||||
- *checkout
|
||||
- *setup-python-matrix
|
||||
|
||||
2
.github/workflows/wheels.yml
vendored
2
.github/workflows/wheels.yml
vendored
@@ -228,7 +228,7 @@ jobs:
|
||||
arch: ${{ matrix.arch }}
|
||||
wheels-key: ${{ secrets.WHEELS_KEY }}
|
||||
env-file: true
|
||||
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev;nasm;zlib-ng-dev"
|
||||
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev;nasm;zlib-ng-dev"
|
||||
skip-binary: aiohttp;charset-normalizer;grpcio;multidict;SQLAlchemy;propcache;protobuf;pymicro-vad;yarl
|
||||
constraints: "homeassistant/package_constraints.txt"
|
||||
requirements-diff: "requirements_diff.txt"
|
||||
|
||||
2
CODEOWNERS
generated
2
CODEOWNERS
generated
@@ -1479,8 +1479,6 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/smhi/ @gjohansson-ST
|
||||
/homeassistant/components/smlight/ @tl-sl
|
||||
/tests/components/smlight/ @tl-sl
|
||||
/homeassistant/components/sms/ @ocalvo
|
||||
/tests/components/sms/ @ocalvo
|
||||
/homeassistant/components/snapcast/ @luar123
|
||||
/tests/components/snapcast/ @luar123
|
||||
/homeassistant/components/snmp/ @nmaggioni
|
||||
|
||||
@@ -13,7 +13,6 @@ RUN \
|
||||
libavcodec-dev \
|
||||
libavdevice-dev \
|
||||
libavutil-dev \
|
||||
libgammu-dev \
|
||||
libswscale-dev \
|
||||
libswresample-dev \
|
||||
libavfilter-dev \
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
"""The sms component."""
|
||||
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_DEVICE, CONF_NAME, Platform
|
||||
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import config_validation as cv, discovery
|
||||
from homeassistant.helpers.issue_registry import (
|
||||
IssueSeverity,
|
||||
async_create_issue,
|
||||
async_delete_issue,
|
||||
)
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import (
|
||||
CONF_BAUD_SPEED,
|
||||
DEFAULT_BAUD_SPEED,
|
||||
DOMAIN,
|
||||
GATEWAY,
|
||||
HASS_CONFIG,
|
||||
NETWORK_COORDINATOR,
|
||||
SIGNAL_COORDINATOR,
|
||||
SMS_GATEWAY,
|
||||
)
|
||||
from .coordinator import NetworkCoordinator, SignalCoordinator
|
||||
from .gateway import create_sms_gateway
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORMS = [Platform.SENSOR]
|
||||
|
||||
SMS_CONFIG_SCHEMA = {vol.Required(CONF_DEVICE): cv.isdevice}
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
DOMAIN: vol.Schema(
|
||||
vol.All(
|
||||
cv.deprecated(CONF_DEVICE),
|
||||
SMS_CONFIG_SCHEMA,
|
||||
),
|
||||
)
|
||||
},
|
||||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
DEPRECATED_ISSUE_ID = f"deprecated_system_packages_config_flow_integration_{DOMAIN}"
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Configure Gammu state machine."""
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
hass.data[HASS_CONFIG] = config
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Configure Gammu state machine."""
|
||||
async_create_issue(
|
||||
hass,
|
||||
HOMEASSISTANT_DOMAIN,
|
||||
DEPRECATED_ISSUE_ID,
|
||||
breaks_in_ha_version="2025.12.0",
|
||||
is_fixable=False,
|
||||
issue_domain=DOMAIN,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_system_packages_config_flow_integration",
|
||||
translation_placeholders={
|
||||
"integration_title": "SMS notifications via GSM-modem",
|
||||
},
|
||||
)
|
||||
|
||||
device = entry.data[CONF_DEVICE]
|
||||
connection_mode = "at"
|
||||
baud_speed = entry.data.get(CONF_BAUD_SPEED, DEFAULT_BAUD_SPEED)
|
||||
if baud_speed != DEFAULT_BAUD_SPEED:
|
||||
connection_mode += baud_speed
|
||||
config = {"Device": device, "Connection": connection_mode}
|
||||
_LOGGER.debug("Connecting mode:%s", connection_mode)
|
||||
gateway = await create_sms_gateway(config, hass)
|
||||
if not gateway:
|
||||
raise ConfigEntryNotReady(f"Cannot find device {device}")
|
||||
|
||||
signal_coordinator = SignalCoordinator(hass, entry, gateway)
|
||||
network_coordinator = NetworkCoordinator(hass, entry, 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,
|
||||
}
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
# set up notify platform, no entry support for notify component yet,
|
||||
# have to use discovery to load platform.
|
||||
hass.async_create_task(
|
||||
discovery.async_load_platform(
|
||||
hass,
|
||||
Platform.NOTIFY,
|
||||
DOMAIN,
|
||||
{CONF_NAME: DOMAIN},
|
||||
hass.data[HASS_CONFIG],
|
||||
)
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
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]
|
||||
await gateway.terminate_async()
|
||||
|
||||
if not hass.config_entries.async_loaded_entries(DOMAIN):
|
||||
async_delete_issue(hass, HOMEASSISTANT_DOMAIN, DEPRECATED_ISSUE_ID)
|
||||
|
||||
return unload_ok
|
||||
@@ -1,87 +0,0 @@
|
||||
"""Config flow for SMS integration."""
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import gammu
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||
from homeassistant.const import CONF_DEVICE
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import selector
|
||||
|
||||
from .const import CONF_BAUD_SPEED, DEFAULT_BAUD_SPEED, DEFAULT_BAUD_SPEEDS, DOMAIN
|
||||
from .gateway import create_sms_gateway
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DATA_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_DEVICE): str,
|
||||
vol.Optional(CONF_BAUD_SPEED, default=DEFAULT_BAUD_SPEED): selector.selector(
|
||||
{"select": {"options": DEFAULT_BAUD_SPEEDS}}
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def get_imei_from_config(hass: HomeAssistant, data: dict[str, Any]) -> str:
|
||||
"""Validate the user input allows us to connect.
|
||||
|
||||
Data has the keys from DATA_SCHEMA with values provided by the user.
|
||||
"""
|
||||
device = data[CONF_DEVICE]
|
||||
connection_mode = "at"
|
||||
baud_speed = data.get(CONF_BAUD_SPEED, DEFAULT_BAUD_SPEED)
|
||||
if baud_speed != DEFAULT_BAUD_SPEED:
|
||||
connection_mode += baud_speed
|
||||
config = {"Device": device, "Connection": connection_mode}
|
||||
gateway = await create_sms_gateway(config, hass)
|
||||
if not gateway:
|
||||
raise CannotConnect
|
||||
try:
|
||||
imei = await gateway.get_imei_async()
|
||||
except gammu.GSMError as err:
|
||||
raise CannotConnect from err
|
||||
finally:
|
||||
await gateway.terminate_async()
|
||||
|
||||
# Return info that you want to store in the config entry.
|
||||
return imei
|
||||
|
||||
|
||||
class SMSFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for SMS integration."""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle the initial step."""
|
||||
if self._async_current_entries():
|
||||
return self.async_abort(reason="single_instance_allowed")
|
||||
errors = {}
|
||||
if user_input is not None:
|
||||
try:
|
||||
imei = await get_imei_from_config(self.hass, user_input)
|
||||
except CannotConnect:
|
||||
errors["base"] = "cannot_connect"
|
||||
except Exception:
|
||||
_LOGGER.exception("Unexpected exception")
|
||||
errors["base"] = "unknown"
|
||||
|
||||
if not errors:
|
||||
await self.async_set_unique_id(imei)
|
||||
self._abort_if_unique_id_configured()
|
||||
return self.async_create_entry(title=imei, data=user_input)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user", data_schema=DATA_SCHEMA, errors=errors
|
||||
)
|
||||
|
||||
|
||||
class CannotConnect(HomeAssistantError):
|
||||
"""Error to indicate we cannot connect."""
|
||||
@@ -1,35 +0,0 @@
|
||||
"""Constants for sms Component."""
|
||||
|
||||
DOMAIN = "sms"
|
||||
SMS_GATEWAY = "SMS_GATEWAY"
|
||||
HASS_CONFIG = "sms_hass_config"
|
||||
SMS_STATE_UNREAD = "UnRead"
|
||||
SIGNAL_COORDINATOR = "signal_coordinator"
|
||||
NETWORK_COORDINATOR = "network_coordinator"
|
||||
GATEWAY = "gateway"
|
||||
DEFAULT_SCAN_INTERVAL = 30
|
||||
CONF_BAUD_SPEED = "baud_speed"
|
||||
CONF_UNICODE = "unicode"
|
||||
DEFAULT_BAUD_SPEED = "0"
|
||||
DEFAULT_BAUD_SPEEDS = [
|
||||
{"value": DEFAULT_BAUD_SPEED, "label": "Auto"},
|
||||
{"value": "50", "label": "50"},
|
||||
{"value": "75", "label": "75"},
|
||||
{"value": "110", "label": "110"},
|
||||
{"value": "134", "label": "134"},
|
||||
{"value": "150", "label": "150"},
|
||||
{"value": "200", "label": "200"},
|
||||
{"value": "300", "label": "300"},
|
||||
{"value": "600", "label": "600"},
|
||||
{"value": "1200", "label": "1200"},
|
||||
{"value": "1800", "label": "1800"},
|
||||
{"value": "2400", "label": "2400"},
|
||||
{"value": "4800", "label": "4800"},
|
||||
{"value": "9600", "label": "9600"},
|
||||
{"value": "19200", "label": "19200"},
|
||||
{"value": "28800", "label": "28800"},
|
||||
{"value": "38400", "label": "38400"},
|
||||
{"value": "57600", "label": "57600"},
|
||||
{"value": "76800", "label": "76800"},
|
||||
{"value": "115200", "label": "115200"},
|
||||
]
|
||||
@@ -1,59 +0,0 @@
|
||||
"""DataUpdateCoordinators for the sms integration."""
|
||||
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
import gammu
|
||||
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import DEFAULT_SCAN_INTERVAL
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SignalCoordinator(DataUpdateCoordinator):
|
||||
"""Signal strength coordinator."""
|
||||
|
||||
def __init__(self, hass, entry, gateway):
|
||||
"""Initialize signal strength coordinator."""
|
||||
super().__init__(
|
||||
hass,
|
||||
_LOGGER,
|
||||
name="Device signal state",
|
||||
update_interval=timedelta(seconds=DEFAULT_SCAN_INTERVAL),
|
||||
config_entry=entry,
|
||||
)
|
||||
self._gateway = gateway
|
||||
|
||||
async def _async_update_data(self):
|
||||
"""Fetch device signal quality."""
|
||||
try:
|
||||
async with asyncio.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, entry, gateway):
|
||||
"""Initialize network info coordinator."""
|
||||
super().__init__(
|
||||
hass,
|
||||
_LOGGER,
|
||||
name="Device network state",
|
||||
update_interval=timedelta(seconds=DEFAULT_SCAN_INTERVAL),
|
||||
config_entry=entry,
|
||||
)
|
||||
self._gateway = gateway
|
||||
|
||||
async def _async_update_data(self):
|
||||
"""Fetch device network info."""
|
||||
try:
|
||||
async with asyncio.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,211 +0,0 @@
|
||||
"""The sms gateway to interact with a GSM modem."""
|
||||
|
||||
import logging
|
||||
|
||||
import gammu
|
||||
from gammu.asyncworker import GammuAsyncWorker
|
||||
|
||||
from homeassistant.core import callback
|
||||
|
||||
from .const import DOMAIN, SMS_STATE_UNREAD
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Gateway:
|
||||
"""SMS gateway to interact with a GSM modem."""
|
||||
|
||||
def __init__(self, config, hass):
|
||||
"""Initialize the sms gateway."""
|
||||
_LOGGER.debug("Init with connection mode:%s", config["Connection"])
|
||||
self._worker = GammuAsyncWorker(self.sms_pull)
|
||||
self._worker.configure(config)
|
||||
self._hass = hass
|
||||
self._first_pull = True
|
||||
self.manufacturer = None
|
||||
self.model = None
|
||||
self.firmware = None
|
||||
|
||||
async def init_async(self):
|
||||
"""Initialize the sms gateway asynchronously. This method is also called in config flow to verify connection."""
|
||||
await self._worker.init_async()
|
||||
self.manufacturer = await self.get_manufacturer_async()
|
||||
self.model = await self.get_model_async()
|
||||
self.firmware = await self.get_firmware_async()
|
||||
|
||||
def sms_pull(self, state_machine):
|
||||
"""Pull device.
|
||||
|
||||
@param state_machine: state machine
|
||||
@type state_machine: gammu.StateMachine
|
||||
"""
|
||||
state_machine.ReadDevice()
|
||||
|
||||
_LOGGER.debug("Pulling modem")
|
||||
self.sms_read_messages(state_machine, self._first_pull)
|
||||
self._first_pull = False
|
||||
|
||||
def sms_read_messages(self, state_machine, force=False):
|
||||
"""Read all received SMS messages.
|
||||
|
||||
@param state_machine: state machine which invoked action
|
||||
@type state_machine: gammu.StateMachine
|
||||
"""
|
||||
entries = self.get_and_delete_all_sms(state_machine, force)
|
||||
_LOGGER.debug("SMS entries:%s", entries)
|
||||
data = []
|
||||
|
||||
for entry in entries:
|
||||
decoded_entry = gammu.DecodeSMS(entry)
|
||||
message = entry[0]
|
||||
_LOGGER.debug("Processing sms:%s,decoded:%s", message, decoded_entry)
|
||||
sms_state = message["State"]
|
||||
_LOGGER.debug("SMS state:%s", sms_state)
|
||||
if sms_state == SMS_STATE_UNREAD:
|
||||
if decoded_entry is None:
|
||||
text = message["Text"]
|
||||
else:
|
||||
text = ""
|
||||
for inner_entry in decoded_entry["Entries"]:
|
||||
if inner_entry["Buffer"] is not None:
|
||||
text += inner_entry["Buffer"]
|
||||
|
||||
event_data = {
|
||||
"phone": message["Number"],
|
||||
"date": str(message["DateTime"]),
|
||||
"message": text,
|
||||
}
|
||||
|
||||
_LOGGER.debug("Append event data:%s", event_data)
|
||||
data.append(event_data)
|
||||
|
||||
self._hass.add_job(self._notify_incoming_sms, data)
|
||||
|
||||
def get_and_delete_all_sms(self, state_machine, force=False):
|
||||
"""Read and delete all SMS in the modem."""
|
||||
# Read SMS memory status ...
|
||||
memory = state_machine.GetSMSStatus()
|
||||
# ... and calculate number of messages
|
||||
remaining = memory["SIMUsed"] + memory["PhoneUsed"]
|
||||
start_remaining = remaining
|
||||
# Get all sms
|
||||
start = True
|
||||
entries = []
|
||||
all_parts = -1
|
||||
_LOGGER.debug("Start remaining:%i", start_remaining)
|
||||
|
||||
try:
|
||||
while remaining > 0:
|
||||
if start:
|
||||
entry = state_machine.GetNextSMS(Folder=0, Start=True)
|
||||
all_parts = entry[0]["UDH"]["AllParts"]
|
||||
part_number = entry[0]["UDH"]["PartNumber"]
|
||||
part_is_missing = all_parts > start_remaining
|
||||
_LOGGER.debug("All parts:%i", all_parts)
|
||||
_LOGGER.debug("Part Number:%i", part_number)
|
||||
_LOGGER.debug("Remaining:%i", remaining)
|
||||
_LOGGER.debug("Start is_part_missing:%s", part_is_missing)
|
||||
start = False
|
||||
else:
|
||||
entry = state_machine.GetNextSMS(
|
||||
Folder=0, Location=entry[0]["Location"]
|
||||
)
|
||||
|
||||
if part_is_missing and not force:
|
||||
_LOGGER.debug("Not all parts have arrived")
|
||||
break
|
||||
|
||||
remaining = remaining - 1
|
||||
entries.append(entry)
|
||||
|
||||
# delete retrieved sms
|
||||
_LOGGER.debug("Deleting message")
|
||||
try:
|
||||
state_machine.DeleteSMS(Folder=0, Location=entry[0]["Location"])
|
||||
except gammu.ERR_MEMORY_NOT_AVAILABLE:
|
||||
_LOGGER.error("Error deleting SMS, memory not available")
|
||||
|
||||
except gammu.ERR_EMPTY:
|
||||
# error is raised if memory is empty (this induces wrong reported
|
||||
# memory status)
|
||||
_LOGGER.warning("Failed to read messages!")
|
||||
|
||||
# Link all SMS when there are concatenated messages
|
||||
return gammu.LinkSMS(entries)
|
||||
|
||||
@callback
|
||||
def _notify_incoming_sms(self, messages):
|
||||
"""Notify hass when an incoming SMS message is received."""
|
||||
for message in messages:
|
||||
event_data = {
|
||||
"phone": message["phone"],
|
||||
"date": message["date"],
|
||||
"text": message["message"],
|
||||
}
|
||||
self._hass.bus.async_fire(f"{DOMAIN}.incoming_sms", event_data)
|
||||
|
||||
async def send_sms_async(self, message):
|
||||
"""Send sms message via the worker."""
|
||||
return await self._worker.send_sms_async(message)
|
||||
|
||||
async def get_imei_async(self):
|
||||
"""Get the IMEI of the device."""
|
||||
return await self._worker.get_imei_async()
|
||||
|
||||
async def get_signal_quality_async(self):
|
||||
"""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."""
|
||||
network_info = await self._worker.get_network_info_async()
|
||||
# Looks like there is a bug and it's empty for any modem https://github.com/gammu/python-gammu/issues/31, so try workaround
|
||||
if not network_info["NetworkName"]:
|
||||
network_info["NetworkName"] = gammu.GSMNetworks.get(
|
||||
network_info["NetworkCode"]
|
||||
)
|
||||
return network_info
|
||||
|
||||
async def get_manufacturer_async(self):
|
||||
"""Get the manufacturer of the modem."""
|
||||
return await self._worker.get_manufacturer_async()
|
||||
|
||||
async def get_model_async(self):
|
||||
"""Get the model of the modem."""
|
||||
model = await self._worker.get_model_async()
|
||||
if not model or not model[0]:
|
||||
return None
|
||||
display = model[0] # Identification model
|
||||
if model[1]: # Real model
|
||||
display = f"{display} ({model[1]})"
|
||||
return display
|
||||
|
||||
async def get_firmware_async(self):
|
||||
"""Get the firmware information of the modem."""
|
||||
firmware = await self._worker.get_firmware_async()
|
||||
if not firmware or not firmware[0]:
|
||||
return None
|
||||
display = firmware[0] # Version
|
||||
if firmware[1]: # Date
|
||||
display = f"{display} ({firmware[1]})"
|
||||
return display
|
||||
|
||||
async def terminate_async(self):
|
||||
"""Terminate modem connection."""
|
||||
return await self._worker.terminate_async()
|
||||
|
||||
|
||||
async def create_sms_gateway(config, hass):
|
||||
"""Create the sms gateway."""
|
||||
try:
|
||||
gateway = Gateway(config, hass)
|
||||
try:
|
||||
await gateway.init_async()
|
||||
except gammu.GSMError as exc:
|
||||
_LOGGER.error("Failed to initialize, error %s", exc)
|
||||
await gateway.terminate_async()
|
||||
return None
|
||||
except gammu.GSMError as exc:
|
||||
_LOGGER.error("Failed to create async worker, error %s", exc)
|
||||
return None
|
||||
return gateway
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"cid": {
|
||||
"default": "mdi:radio-tower"
|
||||
},
|
||||
"signal_percent": {
|
||||
"default": "mdi:signal-cellular-3"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"domain": "sms",
|
||||
"name": "SMS notifications via GSM-modem",
|
||||
"codeowners": ["@ocalvo"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/sms",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["gammu"],
|
||||
"requirements": ["python-gammu==3.2.4"]
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
"""Support for SMS notification services."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
import gammu
|
||||
|
||||
from homeassistant.components.notify import ATTR_DATA, BaseNotificationService
|
||||
from homeassistant.const import CONF_TARGET
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from .const import CONF_UNICODE, DOMAIN, GATEWAY, SMS_GATEWAY
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_get_service(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigType,
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> SMSNotificationService | None:
|
||||
"""Get the SMS notification service."""
|
||||
|
||||
if discovery_info is None:
|
||||
return None
|
||||
|
||||
return SMSNotificationService(hass)
|
||||
|
||||
|
||||
class SMSNotificationService(BaseNotificationService):
|
||||
"""Implement the notification service for SMS."""
|
||||
|
||||
def __init__(self, hass):
|
||||
"""Initialize the service."""
|
||||
|
||||
self.hass = hass
|
||||
|
||||
async def async_send_message(self, message="", **kwargs):
|
||||
"""Send SMS message."""
|
||||
|
||||
if SMS_GATEWAY not in self.hass.data[DOMAIN]:
|
||||
_LOGGER.error("SMS gateway not found, cannot send message")
|
||||
return
|
||||
|
||||
gateway = self.hass.data[DOMAIN][SMS_GATEWAY][GATEWAY]
|
||||
|
||||
targets = kwargs.get(CONF_TARGET)
|
||||
if targets is None:
|
||||
_LOGGER.error("No target number specified, cannot send message")
|
||||
return
|
||||
|
||||
extended_data = kwargs.get(ATTR_DATA)
|
||||
_LOGGER.debug("Extended data:%s", extended_data)
|
||||
|
||||
if extended_data is None:
|
||||
is_unicode = True
|
||||
else:
|
||||
is_unicode = extended_data.get(CONF_UNICODE, True)
|
||||
|
||||
smsinfo = {
|
||||
"Class": -1,
|
||||
"Unicode": is_unicode,
|
||||
"Entries": [{"ID": "ConcatenatedTextLong", "Buffer": message}],
|
||||
}
|
||||
try:
|
||||
# Encode messages
|
||||
encoded = gammu.EncodeSMS(smsinfo)
|
||||
except gammu.GSMError as exc:
|
||||
_LOGGER.error("Encoding message %s failed: %s", message, exc)
|
||||
return
|
||||
|
||||
# Send messages
|
||||
for encoded_message in encoded:
|
||||
# Fill in numbers
|
||||
encoded_message["SMSC"] = {"Location": 1}
|
||||
|
||||
for target in targets:
|
||||
encoded_message["Number"] = target
|
||||
try:
|
||||
# Actually send the message
|
||||
await gateway.send_sms_async(encoded_message)
|
||||
except gammu.GSMError as exc:
|
||||
_LOGGER.error("Sending to %s failed: %s", target, exc)
|
||||
@@ -1,120 +0,0 @@
|
||||
"""Support for SMS dongle sensor."""
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import PERCENTAGE, SIGNAL_STRENGTH_DECIBELS, EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN, GATEWAY, NETWORK_COORDINATOR, SIGNAL_COORDINATOR, SMS_GATEWAY
|
||||
|
||||
SIGNAL_SENSORS = (
|
||||
SensorEntityDescription(
|
||||
key="SignalStrength",
|
||||
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="SignalPercent",
|
||||
translation_key="signal_percent",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
entity_registry_enabled_default=True,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="BitErrorRate",
|
||||
translation_key="bit_error_rate",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
)
|
||||
|
||||
NETWORK_SENSORS = (
|
||||
SensorEntityDescription(
|
||||
key="NetworkName",
|
||||
translation_key="network_name",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="State",
|
||||
translation_key="state",
|
||||
entity_registry_enabled_default=True,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="NetworkCode",
|
||||
translation_key="network_code",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="CID",
|
||||
translation_key="cid",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="LAC",
|
||||
translation_key="lac",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""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 = [
|
||||
DeviceSensor(signal_coordinator, description, unique_id, gateway)
|
||||
for description in SIGNAL_SENSORS
|
||||
]
|
||||
entities.extend(
|
||||
DeviceSensor(network_coordinator, description, unique_id, gateway)
|
||||
for description in NETWORK_SENSORS
|
||||
)
|
||||
async_add_entities(entities, True)
|
||||
|
||||
|
||||
class DeviceSensor(CoordinatorEntity, SensorEntity):
|
||||
"""Implementation of a device sensor."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(self, coordinator, description, unique_id, gateway):
|
||||
"""Initialize the device sensor."""
|
||||
super().__init__(coordinator)
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, unique_id)},
|
||||
name="SMS Gateway",
|
||||
manufacturer=gateway.manufacturer,
|
||||
model=gateway.model,
|
||||
sw_version=gateway.firmware,
|
||||
)
|
||||
self._attr_unique_id = f"{unique_id}_{description.key}"
|
||||
self.entity_description = description
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
"""Return the state of the device."""
|
||||
return self.coordinator.data.get(self.entity_description.key)
|
||||
@@ -1,46 +0,0 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"baud_speed": "Baud Speed",
|
||||
"device": "[%key:common::config_flow::data::device%]"
|
||||
},
|
||||
"title": "Connect to the modem"
|
||||
}
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"bit_error_rate": {
|
||||
"name": "Bit error rate"
|
||||
},
|
||||
"cid": {
|
||||
"name": "Cell ID"
|
||||
},
|
||||
"lac": {
|
||||
"name": "Local area code"
|
||||
},
|
||||
"network_code": {
|
||||
"name": "GSM network code"
|
||||
},
|
||||
"network_name": {
|
||||
"name": "Network name"
|
||||
},
|
||||
"signal_percent": {
|
||||
"name": "Signal percent"
|
||||
},
|
||||
"state": {
|
||||
"name": "Network status"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1
homeassistant/generated/config_flows.py
generated
1
homeassistant/generated/config_flows.py
generated
@@ -612,7 +612,6 @@ FLOWS = {
|
||||
"smarty",
|
||||
"smhi",
|
||||
"smlight",
|
||||
"sms",
|
||||
"snapcast",
|
||||
"snoo",
|
||||
"snooz",
|
||||
|
||||
@@ -6217,12 +6217,6 @@
|
||||
"config_flow": true,
|
||||
"iot_class": "local_push"
|
||||
},
|
||||
"sms": {
|
||||
"name": "SMS notifications via GSM-modem",
|
||||
"integration_type": "hub",
|
||||
"config_flow": true,
|
||||
"iot_class": "local_polling"
|
||||
},
|
||||
"smtp": {
|
||||
"name": "SMTP",
|
||||
"integration_type": "hub",
|
||||
|
||||
3
requirements_all.txt
generated
3
requirements_all.txt
generated
@@ -2477,9 +2477,6 @@ python-family-hub-local==0.0.2
|
||||
# homeassistant.components.fully_kiosk
|
||||
python-fullykiosk==0.0.14
|
||||
|
||||
# homeassistant.components.sms
|
||||
# python-gammu==3.2.4
|
||||
|
||||
# homeassistant.components.gc100
|
||||
python-gc100==1.0.3a0
|
||||
|
||||
|
||||
3
requirements_test_all.txt
generated
3
requirements_test_all.txt
generated
@@ -2062,9 +2062,6 @@ python-ecobee-api==0.2.20
|
||||
# homeassistant.components.fully_kiosk
|
||||
python-fullykiosk==0.0.14
|
||||
|
||||
# homeassistant.components.sms
|
||||
# python-gammu==3.2.4
|
||||
|
||||
# homeassistant.components.google_drive
|
||||
python-google-drive-api==0.1.0
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ EXCLUDED_REQUIREMENTS_ALL = {
|
||||
"face-recognition",
|
||||
"pybluez",
|
||||
"pycups",
|
||||
"python-gammu",
|
||||
"python-lirc",
|
||||
"pyuserinput",
|
||||
}
|
||||
@@ -41,7 +40,6 @@ EXCLUDED_REQUIREMENTS_ALL = {
|
||||
INCLUDED_REQUIREMENTS_WHEELS = {
|
||||
"evdev",
|
||||
"pycups",
|
||||
"python-gammu",
|
||||
"pyuserinput",
|
||||
}
|
||||
|
||||
@@ -55,7 +53,7 @@ INCLUDED_REQUIREMENTS_WHEELS = {
|
||||
OVERRIDDEN_REQUIREMENTS_ACTIONS = {
|
||||
"pytest": {
|
||||
"exclude": set(),
|
||||
"include": {"python-gammu"},
|
||||
"include": set(),
|
||||
"markers": {},
|
||||
},
|
||||
"wheels_aarch64": {
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
"""Tests for SMS integration."""
|
||||
@@ -1,143 +0,0 @@
|
||||
"""Constants for tests of the SMS component."""
|
||||
|
||||
import datetime
|
||||
|
||||
SMS_STATUS_SINGLE = {
|
||||
"SIMUnRead": 0,
|
||||
"SIMUsed": 1,
|
||||
"SIMSize": 30,
|
||||
"PhoneUnRead": 0,
|
||||
"PhoneUsed": 0,
|
||||
"PhoneSize": 50,
|
||||
"TemplatesUsed": 0,
|
||||
}
|
||||
|
||||
NEXT_SMS_SINGLE = [
|
||||
{
|
||||
"SMSC": {
|
||||
"Location": 0,
|
||||
"Name": "",
|
||||
"Format": "Text",
|
||||
"Validity": "NA",
|
||||
"Number": "+358444111111",
|
||||
"DefaultNumber": "",
|
||||
},
|
||||
"UDH": {
|
||||
"Type": "NoUDH",
|
||||
"Text": b"",
|
||||
"ID8bit": 0,
|
||||
"ID16bit": 0,
|
||||
"PartNumber": -1,
|
||||
"AllParts": 0,
|
||||
},
|
||||
"Folder": 1,
|
||||
"InboxFolder": 1,
|
||||
"Memory": "SM",
|
||||
"Location": 1,
|
||||
"Name": "",
|
||||
"Number": "+358444222222",
|
||||
"Text": "Short message",
|
||||
"Type": "Deliver",
|
||||
"Coding": "Default_No_Compression",
|
||||
"DateTime": datetime.datetime(2024, 3, 23, 20, 15, 37),
|
||||
"SMSCDateTime": datetime.datetime(2024, 3, 23, 20, 15, 41),
|
||||
"DeliveryStatus": 0,
|
||||
"ReplyViaSameSMSC": 0,
|
||||
"State": "UnRead",
|
||||
"Class": -1,
|
||||
"MessageReference": 0,
|
||||
"ReplaceMessage": 0,
|
||||
"RejectDuplicates": 0,
|
||||
"Length": 7,
|
||||
}
|
||||
]
|
||||
|
||||
SMS_STATUS_MULTIPLE = {
|
||||
"SIMUnRead": 0,
|
||||
"SIMUsed": 2,
|
||||
"SIMSize": 30,
|
||||
"PhoneUnRead": 0,
|
||||
"PhoneUsed": 0,
|
||||
"PhoneSize": 50,
|
||||
"TemplatesUsed": 0,
|
||||
}
|
||||
|
||||
NEXT_SMS_MULTIPLE_1 = [
|
||||
{
|
||||
"SMSC": {
|
||||
"Location": 0,
|
||||
"Name": "",
|
||||
"Format": "Text",
|
||||
"Validity": "NA",
|
||||
"Number": "+358444111111",
|
||||
"DefaultNumber": "",
|
||||
},
|
||||
"UDH": {
|
||||
"Type": "ConcatenatedMessages",
|
||||
"Text": b"\x05\x00\x03\x00\x02\x01",
|
||||
"ID8bit": 0,
|
||||
"ID16bit": -1,
|
||||
"PartNumber": 1,
|
||||
"AllParts": 2,
|
||||
},
|
||||
"Folder": 1,
|
||||
"InboxFolder": 1,
|
||||
"Memory": "SM",
|
||||
"Location": 1,
|
||||
"Name": "",
|
||||
"Number": "+358444222222",
|
||||
"Text": "Longer test again: 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123",
|
||||
"Type": "Deliver",
|
||||
"Coding": "Default_No_Compression",
|
||||
"DateTime": datetime.datetime(2024, 3, 25, 19, 53, 56),
|
||||
"SMSCDateTime": datetime.datetime(2024, 3, 25, 19, 54, 6),
|
||||
"DeliveryStatus": 0,
|
||||
"ReplyViaSameSMSC": 0,
|
||||
"State": "UnRead",
|
||||
"Class": -1,
|
||||
"MessageReference": 0,
|
||||
"ReplaceMessage": 0,
|
||||
"RejectDuplicates": 0,
|
||||
"Length": 153,
|
||||
}
|
||||
]
|
||||
|
||||
NEXT_SMS_MULTIPLE_2 = [
|
||||
{
|
||||
"SMSC": {
|
||||
"Location": 0,
|
||||
"Name": "",
|
||||
"Format": "Text",
|
||||
"Validity": "NA",
|
||||
"Number": "+358444111111",
|
||||
"DefaultNumber": "",
|
||||
},
|
||||
"UDH": {
|
||||
"Type": "ConcatenatedMessages",
|
||||
"Text": b"\x05\x00\x03\x00\x02\x02",
|
||||
"ID8bit": 0,
|
||||
"ID16bit": -1,
|
||||
"PartNumber": 2,
|
||||
"AllParts": 2,
|
||||
},
|
||||
"Folder": 1,
|
||||
"InboxFolder": 1,
|
||||
"Memory": "SM",
|
||||
"Location": 2,
|
||||
"Name": "",
|
||||
"Number": "+358444222222",
|
||||
"Text": "4567890123456789012345678901",
|
||||
"Type": "Deliver",
|
||||
"Coding": "Default_No_Compression",
|
||||
"DateTime": datetime.datetime(2024, 3, 25, 19, 53, 56),
|
||||
"SMSCDateTime": datetime.datetime(2024, 3, 25, 19, 54, 7),
|
||||
"DeliveryStatus": 0,
|
||||
"ReplyViaSameSMSC": 0,
|
||||
"State": "UnRead",
|
||||
"Class": -1,
|
||||
"MessageReference": 0,
|
||||
"ReplaceMessage": 0,
|
||||
"RejectDuplicates": 0,
|
||||
"Length": 28,
|
||||
}
|
||||
]
|
||||
@@ -1,52 +0,0 @@
|
||||
"""Test the SMS Gateway."""
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from homeassistant.components.sms.gateway import Gateway
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import (
|
||||
NEXT_SMS_MULTIPLE_1,
|
||||
NEXT_SMS_MULTIPLE_2,
|
||||
NEXT_SMS_SINGLE,
|
||||
SMS_STATUS_MULTIPLE,
|
||||
SMS_STATUS_SINGLE,
|
||||
)
|
||||
|
||||
|
||||
async def test_get_and_delete_all_sms_single_message(hass: HomeAssistant) -> None:
|
||||
"""Test that a single message produces a list of entries containing the single message."""
|
||||
|
||||
# Mock the Gammu state_machine
|
||||
state_machine = MagicMock()
|
||||
state_machine.GetSMSStatus = MagicMock(return_value=SMS_STATUS_SINGLE)
|
||||
state_machine.GetNextSMS = MagicMock(return_value=NEXT_SMS_SINGLE)
|
||||
state_machine.DeleteSMS = MagicMock()
|
||||
|
||||
response = Gateway({"Connection": None}, hass).get_and_delete_all_sms(state_machine)
|
||||
|
||||
# Assert the length of the list
|
||||
assert len(response) == 1
|
||||
assert len(response[0]) == 1
|
||||
|
||||
# Assert the content of the message
|
||||
assert response[0][0]["Text"] == "Short message"
|
||||
|
||||
|
||||
async def test_get_and_delete_all_sms_two_part_message(hass: HomeAssistant) -> None:
|
||||
"""Test that a two-part message produces a list of entries containing one combined message."""
|
||||
|
||||
state_machine = MagicMock()
|
||||
state_machine.GetSMSStatus = MagicMock(return_value=SMS_STATUS_MULTIPLE)
|
||||
state_machine.GetNextSMS = MagicMock(
|
||||
side_effect=iter([NEXT_SMS_MULTIPLE_1, NEXT_SMS_MULTIPLE_2])
|
||||
)
|
||||
state_machine.DeleteSMS = MagicMock()
|
||||
|
||||
response = Gateway({"Connection": None}, hass).get_and_delete_all_sms(state_machine)
|
||||
|
||||
assert len(response) == 1
|
||||
assert len(response[0]) == 2
|
||||
|
||||
assert response[0][0]["Text"] == NEXT_SMS_MULTIPLE_1[0]["Text"]
|
||||
assert response[0][1]["Text"] == NEXT_SMS_MULTIPLE_2[0]["Text"]
|
||||
@@ -1,59 +0,0 @@
|
||||
"""Test init."""
|
||||
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import CONF_DEVICE
|
||||
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
|
||||
from homeassistant.helpers import issue_registry as ir
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
@patch.dict(
|
||||
"sys.modules",
|
||||
{
|
||||
"gammu": Mock(),
|
||||
"gammu.asyncworker": Mock(),
|
||||
},
|
||||
)
|
||||
async def test_repair_issue_is_created(
|
||||
hass: HomeAssistant,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
) -> None:
|
||||
"""Test repair issue is created."""
|
||||
from homeassistant.components.sms import ( # noqa: PLC0415
|
||||
DEPRECATED_ISSUE_ID,
|
||||
DOMAIN,
|
||||
)
|
||||
|
||||
with (
|
||||
patch("homeassistant.components.sms.create_sms_gateway", autospec=True),
|
||||
patch("homeassistant.components.sms.PLATFORMS", []),
|
||||
):
|
||||
config_entry = MockConfigEntry(
|
||||
title="test",
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
CONF_DEVICE: "/dev/ttyUSB0",
|
||||
},
|
||||
)
|
||||
|
||||
config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert config_entry.state is ConfigEntryState.LOADED
|
||||
assert (
|
||||
HOMEASSISTANT_DOMAIN,
|
||||
DEPRECATED_ISSUE_ID,
|
||||
) in issue_registry.issues
|
||||
|
||||
await hass.config_entries.async_unload(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert config_entry.state is ConfigEntryState.NOT_LOADED
|
||||
assert (
|
||||
HOMEASSISTANT_DOMAIN,
|
||||
DEPRECATED_ISSUE_ID,
|
||||
) not in issue_registry.issues
|
||||
Reference in New Issue
Block a user