I’ve made an enhancement to the existing modem_callerid sensor.py script. Since so many calls come in as “Wireless Caller” and other obfuscated names, I created an enhancement that queries a local database where I have positioned known numbers with actual names. Now, when a call comes in for a number I’ve recorded in the database, instead of the custom Alexa component announcing a call from “Wireless Caller”, I get an announcement of who it really is. Sweet.
The problem is, to achieve this customization, all I did was modify the modem_callerid sensor component in situ here: /usr/src/homeassistant/homeassistant/components/modem_callerid/sensor.py
While this works, it’s not ideal since updates to the HA docker container overlay my changes, forcing me to reapply the customization after the update.
Can anyone explain to me how to configure the existing modem_callerid component in the custom component directory so that I can then apply my tweaks in a more durable manner? I took a run at it, but, not being skilled in creating custom components, it didn’t work.
What about submitting a change to the base code? If the sensor.py component were to look for a file in the /config directory and use that if it existed, it should be an easy enhancement.
I doubt a PR for the specific change described by the OP would be accepted. There are already ways to perform table lookups from Home Assistant’s configuration. The methods that currently exist are all sucky in one way or another. In some cases, any change to the table requires restarting HA (not just reload scripts or automations, but a complete restart). In other cases, the amount of glue required is error-prone, or large. Etc… However, if the issue of performing table lookups is going to be improved, we need a general solution, not a solution specific to just one sensor.
My question still holds. What if I want to modify the behavior of an existing module, like the modem_callerid sensor, without waiting for an official enhancement that may never happen?
There must be a way to set up an existing component in the custom component section, but I am not exactly sure what all the requirements are to make it work.
You can override a built-in component by having a component with the same name in your config/custom_components folder. If the built-in component is inside a subfolder, take care to place your customization in a folder with the same name in config/custom_components/folder . Note that overriding built-in components is not recommended and will probably break things!
Sorry. I didn’t notice that you asked. Now, it seems that the sensor.py script for the modem_callerid component has changed significantly, rendering my customization OBE.
For those who may be interested, below is the hack I made of the modem_callerid component. There is a dependency on a mysql “callerid” database that contains a “callers” table with the columns caller_name and caller_num. For any caller names that I want to be announced “correctly”, I populate a row in the table.
"""A sensor for incoming calls using a USB modem that supports caller ID."""
from __future__ import annotations
from phone_modem import PhoneModem
from homeassistant.components.sensor import SensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_DEVICE, EVENT_HOMEASSISTANT_STOP, STATE_IDLE
from homeassistant.core import Event, HomeAssistant, callback
from homeassistant.helpers import entity_platform
from .const import CID, DATA_KEY_API, DOMAIN, ICON, SERVICE_REJECT_CALL
import sqlalchemy
from sqlalchemy.engine import create_engine
import logging
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: entity_platform.AddEntitiesCallback,
) -> None:
"""Set up the Modem Caller ID sensor."""
api = hass.data[DOMAIN][entry.entry_id][DATA_KEY_API]
async_add_entities(
[
ModemCalleridSensor(
api,
entry.title,
entry.data[CONF_DEVICE],
entry.entry_id,
)
]
)
async def _async_on_hass_stop(event: Event) -> None:
"""HA is shutting down, close modem port."""
if hass.data[DOMAIN][entry.entry_id][DATA_KEY_API]:
await hass.data[DOMAIN][entry.entry_id][DATA_KEY_API].close()
entry.async_on_unload(
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_on_hass_stop)
)
platform = entity_platform.async_get_current_platform()
platform.async_register_entity_service(SERVICE_REJECT_CALL, {}, "async_reject_call")
class ModemCalleridSensor(SensorEntity):
"""Implementation of USB modem caller ID sensor."""
_attr_icon = ICON
_attr_should_poll = False
def __init__(
self, api: PhoneModem, name: str, device: str, server_unique_id: str
) -> None:
"""Initialize the sensor."""
self.device = device
self.api = api
self._attr_name = name
self._attr_unique_id = server_unique_id
self._attr_native_value = STATE_IDLE
self._attr_extra_state_attributes = {
CID.CID_TIME: 0,
CID.CID_NUMBER: "",
CID.CID_NAME: "",
}
async def async_added_to_hass(self) -> None:
"""Call when the modem sensor is added to Home Assistant."""
self.api.registercallback(self._async_incoming_call)
await super().async_added_to_hass()
@callback
def _async_incoming_call(self, new_state: str) -> None:
"""Handle new states."""
self._attr_extra_state_attributes = {}
if self.api.cid_name:
self._attr_extra_state_attributes[CID.CID_NAME] = self.api.cid_name
if self.api.cid_number:
self._attr_extra_state_attributes[CID.CID_NUMBER] = self.api.cid_number
if self.api.cid_time:
self._attr_extra_state_attributes[CID.CID_TIME] = self.api.cid_time
self._attr_native_value = self.api.state
#
# check callerid table for numbers that need substitute names
engine = create_engine('mysql://user_name:password@host_address/callerid')
connection = engine.connect()
result = connection.execute("select caller_name from callers where caller_num = '" + self.api.cid_number +"'")
if result.rowcount !=1:
# caller number not in the table; do nothing
_LOGGER.debug("Number not found in callerid database table.")
else:
_LOGGER.debug("Number was found in callerid table.")
for row in result:
self._attr_extra_state_attributes[CID.CID_NAME] = row["caller_name"]
result.close()
connection.close()
#
self.async_write_ha_state()
async def async_reject_call(self) -> None:
"""Reject Incoming Call."""
await self.api.reject_call(self.device)