diff --git a/README.md b/README.md index 9eda1b596..2ea19f9dd 100644 --- a/README.md +++ b/README.md @@ -92,14 +92,21 @@ Go into Settings -> Integrations -> Battery Notes and click Configure on the dev By default Battery Notes filters the device list to only devices with a battery, if you want to add a battery note to a random device then you can disable this filtering by adding the following configuration to your `configuration.yaml` and restart Home Assistant to see all devices. ``` battery_notes: - show_all_devices: true + show_all_devices: True ``` * I only want to add notes to a few devices, can I disable auto discovery? If you want to disable this functionality you can add the following to your `configuration.yaml`, after a restart of Home Assistant you will not see discovered battery notes. ``` battery_notes: - enable_autodiscovery: false + enable_autodiscovery: False +``` + +* I don't want to track battery replacement, can I disable this? +Yes, you can add the following to your `configuration.yaml`, after a restart of Home Assistant *new* devices added to battery notes will have the battery replaced sensor and button disabled. Any devices you have previously added to Battery Notes you will have to disable/enable these sensors manually, which also means you can just enable specific sensors of important ones you want to track. +``` +battery_notes: + enable_replaced: False ``` * How can I show my support? @@ -124,10 +131,10 @@ Do not enable GitHub Actions (disabled by default) on your fork as this will mes * The make & model names may be different between integrations such as Zigbee2MQTT and ZHA, if you see a similar device please duplicate the entry rather than changing it. * Please keep devices in alphabetical order by manufacturer/model. * The `battery_quantity` data is numeric (no quotes) and optional. If a device only requires a single battery, it should be omitted. -* The `battery_type` data should follow the most common naming for general batteries (ex. AAA, D) and the IEC naming for battery cells according to [Wikipedia](https://en.wikipedia.org/wiki/List_of_battery_sizes) (ex. CR2032, 18650) +* The `battery_type` data should follow the most common naming for general batteries (ex. AAA, D) and the IEC naming for battery cells (ex. CR2032, 18650) according to [Wikipedia](https://en.wikipedia.org/wiki/List_of_battery_sizes) * If a device has a bespoke rechargeable battery you can use `"battery_type": "Rechargeable"` * For devices like smoke alarms where the battery is not replaceable you can use `"battery_type": "Irreplaceable"` -* If a device shouldn't be discovered because there are multiple revisions with the same model number but different battery types or it's optionally mains powered, it can be added to the library with a `"battery_type": "MANUAL"` to note it is a device that shouldn't have a battery definition added to the library to save removal/re-add because people don't realise there are variants. +* If a device shouldn't be discovered because there are multiple revisions with the same model number but different battery types it can be added to the library with a `"battery_type": "MANUAL"` to note it is a device that shouldn't have a battery definition added to the library to save removal/re-add because people don't realise there are variants. For the example image below, your JSON entry will look like this: diff --git a/custom_components/battery_notes/__init__.py b/custom_components/battery_notes/__init__.py index 382f79030..d6f9d93be 100644 --- a/custom_components/battery_notes/__init__.py +++ b/custom_components/battery_notes/__init__.py @@ -34,9 +34,10 @@ DOMAIN_CONFIG, PLATFORMS, CONF_ENABLE_AUTODISCOVERY, - CONF_LIBRARY, + CONF_USER_LIBRARY, DATA_UPDATE_COORDINATOR, CONF_SHOW_ALL_DEVICES, + CONF_ENABLE_REPLACED, SERVICE_BATTERY_REPLACED, SERVICE_BATTERY_REPLACED_SCHEMA, DATA_COORDINATOR, @@ -53,8 +54,9 @@ vol.Schema( { vol.Optional(CONF_ENABLE_AUTODISCOVERY, default=True): cv.boolean, - vol.Optional(CONF_LIBRARY, default="library.json"): cv.string, + vol.Optional(CONF_USER_LIBRARY, default=""): cv.string, vol.Optional(CONF_SHOW_ALL_DEVICES, default=False): cv.boolean, + vol.Optional(CONF_ENABLE_REPLACED, default=True): cv.boolean, }, ), ), @@ -79,6 +81,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: domain_config: ConfigType = config.get(DOMAIN) or { CONF_ENABLE_AUTODISCOVERY: True, CONF_SHOW_ALL_DEVICES: False, + CONF_ENABLE_REPLACED: True, } hass.data[DOMAIN] = { diff --git a/custom_components/battery_notes/button.py b/custom_components/battery_notes/button.py index 01860019e..812655d68 100644 --- a/custom_components/battery_notes/button.py +++ b/custom_components/battery_notes/button.py @@ -26,13 +26,9 @@ ) from homeassistant.helpers.reload import async_setup_reload_service -from homeassistant.helpers.typing import ( - ConfigType, -) from homeassistant.const import ( CONF_NAME, - CONF_UNIQUE_ID, CONF_DEVICE_ID, ) @@ -40,7 +36,9 @@ from .const import ( DOMAIN, + DOMAIN_CONFIG, DATA_COORDINATOR, + CONF_ENABLE_REPLACED, ) from .entity import ( @@ -65,6 +63,7 @@ class BatteryNotesButtonEntityDescription( translation_key="battery_replaced", icon="mdi:battery-sync", entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default = False, ), ) @@ -131,38 +130,38 @@ async def async_registry_updated(event: Event) -> None: device_id = async_add_to_device(hass, config_entry) + enable_replaced = True + if DOMAIN_CONFIG in hass.data[DOMAIN]: + domain_config = hass.data[DOMAIN][DOMAIN_CONFIG] + enable_replaced = domain_config.get(CONF_ENABLE_REPLACED, True) + + description = BatteryNotesButtonEntityDescription( + unique_id_suffix="_battery_replaced_button", + key="battery_replaced", + translation_key="battery_replaced", + icon="mdi:battery-sync", + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default = enable_replaced, + ) + async_add_entities( - BatteryNotesButton( + [ + BatteryNotesButton( hass, description, f"{config_entry.entry_id}{description.unique_id_suffix}", device_id, - ) - for description in ENTITY_DESCRIPTIONS + ) + ] ) - async def async_setup_platform( hass: HomeAssistant, - config: ConfigType, - async_add_entities: AddEntitiesCallback, ) -> None: - """Set up the battery type button.""" - device_id: str = config[CONF_DEVICE_ID] + """Set up the battery note sensor.""" await async_setup_reload_service(hass, DOMAIN, PLATFORMS) - async_add_entities( - BatteryNotesButton( - hass, - description, - f"{config.get(CONF_UNIQUE_ID)}{description.unique_id_suffix}", - device_id, - ) - for description in ENTITY_DESCRIPTIONS - ) - - class BatteryNotesButton(ButtonEntity): """Represents a battery replaced button.""" @@ -185,7 +184,6 @@ def __init__( self._attr_has_entity_name = True self._device_id = device_id - self._device_id = device_id if device_id and (device := device_registry.async_get(device_id)): self._attr_device_info = DeviceInfo( connections=device.connections, @@ -194,29 +192,15 @@ def __init__( async def async_added_to_hass(self) -> None: """Handle added to Hass.""" - # Update entity options registry = er.async_get(self.hass) if registry.async_get(self.entity_id) is not None: + registry.async_update_entity_options( self.entity_id, DOMAIN, {"entity_id": self._attr_unique_id}, ) - async def update_battery_last_replaced(self): - """Handle sensor state changes.""" - - # device_id = self._device_id - - # device_entry = { - # "battery_last_replaced" : datetime.utcnow() - # } - - # coordinator = self.hass.data[DOMAIN][DATA_COORDINATOR] - # coordinator.async_update_device_config(device_id = device_id, data = device_entry) - - self.async_write_ha_state() - async def async_press(self) -> None: """Press the button.""" device_id = self._device_id @@ -227,3 +211,4 @@ async def async_press(self) -> None: coordinator.async_update_device_config(device_id=device_id, data=device_entry) await coordinator._async_update_data() await coordinator.async_request_refresh() + diff --git a/custom_components/battery_notes/config_flow.py b/custom_components/battery_notes/config_flow.py index e5535a936..ea67c3243 100644 --- a/custom_components/battery_notes/config_flow.py +++ b/custom_components/battery_notes/config_flow.py @@ -91,6 +91,10 @@ async def async_step_integration_discovery( """Handle integration discovery.""" _LOGGER.debug("Starting discovery flow: %s", discovery_info) + unique_id = f"bn_{discovery_info[CONF_DEVICE_ID]}" + await self.async_set_unique_id(unique_id) + self._abort_if_unique_id_configured() + self.context["title_placeholders"] = { "name": discovery_info[CONF_DEVICE_NAME], "manufacturer": discovery_info[CONF_MANUFACTURER], diff --git a/custom_components/battery_notes/const.py b/custom_components/battery_notes/const.py index 4facd5ce5..6285b0dd9 100644 --- a/custom_components/battery_notes/const.py +++ b/custom_components/battery_notes/const.py @@ -27,12 +27,13 @@ CONF_BATTERY_TYPE = "battery_type" CONF_SENSORS = "sensors" CONF_ENABLE_AUTODISCOVERY = "enable_autodiscovery" -CONF_LIBRARY = "library" +CONF_USER_LIBRARY = "user_library" CONF_MODEL = "model" CONF_MANUFACTURER = "manufacturer" CONF_DEVICE_NAME = "device_name" CONF_LIBRARY_URL = "https://raw.githubusercontent.com/andrew-codechimp/HA-Battery-Notes/main/custom_components/battery_notes/data/library.json" # pylint: disable=line-too-long CONF_SHOW_ALL_DEVICES = "show_all_devices" +CONF_ENABLE_REPLACED = "enable_replaced" DATA_CONFIGURED_ENTITIES = "configured_entities" DATA_DISCOVERED_ENTITIES = "discovered_entities" diff --git a/custom_components/battery_notes/library.py b/custom_components/battery_notes/library.py index ed0905b0e..8be989692 100644 --- a/custom_components/battery_notes/library.py +++ b/custom_components/battery_notes/library.py @@ -12,7 +12,7 @@ DOMAIN, DATA_LIBRARY, DOMAIN_CONFIG, - CONF_LIBRARY, + CONF_USER_LIBRARY, ) BUILT_IN_DATA_DIRECTORY = os.path.join(os.path.dirname(__file__), "data") @@ -23,29 +23,47 @@ class Library: # pylint: disable=too-few-public-methods """Hold all known battery types.""" - _devices = None + _devices = [] def __init__(self, hass: HomeAssistant) -> None: """Init.""" - if DOMAIN_CONFIG not in hass.data[DOMAIN]: - json_path = os.path.join( - BUILT_IN_DATA_DIRECTORY, - "library.json", - ) - else: - json_path = os.path.join( - BUILT_IN_DATA_DIRECTORY, - hass.data[DOMAIN][DOMAIN_CONFIG].get(CONF_LIBRARY, "library.json"), - ) + # User Library + if DOMAIN_CONFIG in hass.data[DOMAIN]: + if CONF_USER_LIBRARY in hass.data[DOMAIN][DOMAIN_CONFIG]: + user_library_filename = hass.data[DOMAIN][DOMAIN_CONFIG].get(CONF_USER_LIBRARY) + if user_library_filename != "": + json_user_path = os.path.join( + BUILT_IN_DATA_DIRECTORY, + user_library_filename, + ) + _LOGGER.debug("Using user library file at %s", json_user_path) + + try: + with open(json_user_path, encoding="utf-8") as user_file: + user_json_data = json.load(user_file) + self._devices = user_json_data["devices"] + user_file.close() + + except FileNotFoundError: + _LOGGER.error( + "User library file not found at %s", + json_user_path, + ) + + # Default Library + json_default_path = os.path.join( + BUILT_IN_DATA_DIRECTORY, + "library.json",) - _LOGGER.debug("Using library file at %s", json_path) + _LOGGER.debug("Using library file at %s", json_default_path) try: - with open(json_path, encoding="utf-8") as myfile: - json_data = json.load(myfile) - self._devices = json_data["devices"] - myfile.close() + with open(json_default_path, encoding="utf-8") as default_file: + default_json_data = json.load(default_file) + for i in default_json_data["devices"]: + self._devices.append(i) + default_file.close() except FileNotFoundError: _LOGGER.error( diff --git a/custom_components/battery_notes/manifest.json b/custom_components/battery_notes/manifest.json index d3f1e2792..5b764a158 100644 --- a/custom_components/battery_notes/manifest.json +++ b/custom_components/battery_notes/manifest.json @@ -9,5 +9,5 @@ "integration_type": "device", "iot_class": "calculated", "issue_tracker": "https://github.com/andrew-codechimp/ha-battery-notes/issues", - "version": "1.3.3" + "version": "1.3.4" } \ No newline at end of file diff --git a/custom_components/battery_notes/sensor.py b/custom_components/battery_notes/sensor.py index 9ef1602ba..efcd3e3e7 100644 --- a/custom_components/battery_notes/sensor.py +++ b/custom_components/battery_notes/sensor.py @@ -30,7 +30,6 @@ from homeassistant.helpers.update_coordinator import ( CoordinatorEntity, ) - from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.const import ( @@ -45,6 +44,8 @@ DATA_UPDATE_COORDINATOR, DATA_COORDINATOR, LAST_REPLACED, + DOMAIN_CONFIG, + CONF_ENABLE_REPLACED, ) from .library_coordinator import BatteryNotesLibraryUpdateCoordinator @@ -66,24 +67,6 @@ class BatteryNotesSensorEntityDescription( unique_id_suffix: str - -typeSensorEntityDescription = BatteryNotesSensorEntityDescription( - unique_id_suffix="", # battery_type has uniqueId set to entityId in V1, never add a suffix - key="battery_type", - translation_key="battery_type", - icon="mdi:battery-unknown", - entity_category=EntityCategory.DIAGNOSTIC, -) - -lastReplacedSensorEntityDescription = BatteryNotesSensorEntityDescription( - unique_id_suffix="_battery_last_replaced", - key="battery_last_replaced", - translation_key="battery_last_replaced", - icon="mdi:battery-clock", - entity_category=EntityCategory.DIAGNOSTIC, - device_class=SensorDeviceClass.TIMESTAMP, -) - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Optional(CONF_NAME): cv.string, @@ -155,6 +138,29 @@ async def async_registry_updated(event: Event) -> None: library_coordinator = hass.data[DOMAIN][DATA_UPDATE_COORDINATOR] coordinator = hass.data[DOMAIN][DATA_COORDINATOR] + enable_replaced = True + if DOMAIN_CONFIG in hass.data[DOMAIN]: + domain_config = hass.data[DOMAIN][DOMAIN_CONFIG] + enable_replaced = domain_config.get(CONF_ENABLE_REPLACED, True) + + typeSensorEntityDescription = BatteryNotesSensorEntityDescription( + unique_id_suffix="", # battery_type has uniqueId set to entityId in V1, never add a suffix + key="battery_type", + translation_key="battery_type", + icon="mdi:battery-unknown", + entity_category=EntityCategory.DIAGNOSTIC, + ) + + lastReplacedSensorEntityDescription = BatteryNotesSensorEntityDescription( + unique_id_suffix="_battery_last_replaced", + key="battery_last_replaced", + translation_key="battery_last_replaced", + icon="mdi:battery-clock", + entity_category=EntityCategory.DIAGNOSTIC, + device_class=SensorDeviceClass.TIMESTAMP, + entity_registry_enabled_default = enable_replaced, + ) + entities = [ BatteryNotesTypeSensor( hass, @@ -240,7 +246,9 @@ async def async_added_to_hass(self) -> None: registry.async_update_entity_options( self.entity_id, DOMAIN, - {"entity_id": self._attr_unique_id}, + { + "entity_id": self._attr_unique_id, + }, ) @callback @@ -328,27 +336,6 @@ def _set_native_value(self, log_on_error=True): return True return False - # async def async_added_to_hass(self) -> None: - # """Handle added to Hass.""" - # await super().async_added_to_hass() - - # self.async_on_remove( - # async_track_state_change_event( - # self.hass, - # [self._attr_unique_id], - # self._async_battery_note_state_replaced_listener, - # ) - # ) - - # # Update entity options - # registry = er.async_get(self.hass) - # if registry.async_get(self.entity_id) is not None: - # registry.async_update_entity_options( - # self.entity_id, - # DOMAIN, - # {"entity_id": self._attr_unique_id}, - # ) - @callback def _handle_coordinator_update(self) -> None: """Handle updated data from the coordinator."""