Need help "customizing" the modem_callerid component

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.

Larry

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.

Try this…

https://developers.home-assistant.io/docs/en/creating_component_loading.html

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!

Thanks! I’ll try that. I didn’t think about replicating the folder structure within the custom component directory.

Thanks! That actually worked.

This is exactly what I am after, have you released your custom component code?

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)