diff --git a/custom_components/bluetti_bt/__init__.py b/custom_components/bluetti_bt/__init__.py index 5e73625..107be83 100644 --- a/custom_components/bluetti_bt/__init__.py +++ b/custom_components/bluetti_bt/__init__.py @@ -12,7 +12,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import DeviceInfo from homeassistant.exceptions import ConfigEntryNotReady -from .const import CONF_PERSISTANT_CONN, CONF_POLLING_INTERVAL, DATA_COORDINATOR, DOMAIN, MANUFACTURER, CONF_USE_CONTROLS, DATA_POLLING_RUNNING +from .const import CONF_PERSISTENT_CONN, CONF_POLLING_INTERVAL, DATA_COORDINATOR, DOMAIN, MANUFACTURER, CONF_USE_CONTROLS, DATA_POLLING_RUNNING from .coordinator import PollingCoordinator PLATFORMS: [Platform] = [Platform.BINARY_SENSOR, Platform.SENSOR] @@ -26,12 +26,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: device_name = entry.data.get(CONF_NAME) use_controls = entry.data.get(CONF_USE_CONTROLS) polling_interval = entry.data.get(CONF_POLLING_INTERVAL, 20) - persistant_conn = entry.data.get(CONF_PERSISTANT_CONN, False) + persistent_conn = entry.data.get(CONF_PERSISTENT_CONN, False) # Prevent persistant connection and controls at same time - if use_controls is True and persistant_conn is True: + if use_controls is True and persistent_conn is True: _LOGGER.warning("Enabling controls and persistant connection at the same time is currently not supported. Disabled persistant connection") - persistant_conn = False + persistent_conn = False if address is None: return False @@ -45,7 +45,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data[DOMAIN][entry.entry_id].setdefault(DATA_POLLING_RUNNING, False) # Create coordinator for polling - coordinator = PollingCoordinator(hass, address, device_name, polling_interval, persistant_conn) + coordinator = PollingCoordinator(hass, address, device_name, polling_interval, persistent_conn) await coordinator.async_config_entry_first_refresh() hass.data[DOMAIN][entry.entry_id].setdefault(DATA_COORDINATOR, coordinator) diff --git a/custom_components/bluetti_bt/binary_sensor.py b/custom_components/bluetti_bt/binary_sensor.py index ecf00d9..b177e1c 100644 --- a/custom_components/bluetti_bt/binary_sensor.py +++ b/custom_components/bluetti_bt/binary_sensor.py @@ -28,6 +28,7 @@ from bluetti_mqtt.mqtt_client import ( from . import device_info as dev_info, get_unique_id from .const import DATA_COORDINATOR, DOMAIN, ADDITIONAL_DEVICE_FIELDS from .coordinator import PollingCoordinator, DummyDevice +from .utils import unique_id_loggable _LOGGER = logging.getLogger(__name__) @@ -103,10 +104,10 @@ class BluettiBinarySensor(CoordinatorEntity, BinarySensorEntity): @callback def _handle_coordinator_update(self) -> None: """Handle updated data from the coordinator.""" - _LOGGER.debug("Updating state of %s", self._attr_unique_id) + _LOGGER.debug("Updating state of %s", unique_id_loggable(self._attr_unique_id)) if not isinstance(self.coordinator.data, dict): _LOGGER.debug( - "Invalid data from coordinator (binary_sensor.%s)", self._attr_unique_id + "Invalid data from coordinator (binary_sensor.%s)", unique_id_loggable(self._attr_unique_id) ) self._attr_available = False return @@ -119,7 +120,7 @@ class BluettiBinarySensor(CoordinatorEntity, BinarySensorEntity): if not isinstance(response_data, bool): _LOGGER.warning( "Invalid response data type from coordinator (binary_sensor.%s): %s", - self._attr_unique_id, + unique_id_loggable(self._attr_unique_id), response_data, ) self._attr_available = False diff --git a/custom_components/bluetti_bt/config_flow.py b/custom_components/bluetti_bt/config_flow.py index 025f2ef..d487271 100644 --- a/custom_components/bluetti_bt/config_flow.py +++ b/custom_components/bluetti_bt/config_flow.py @@ -17,7 +17,7 @@ from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import selector from . import get_type_by_bt_name -from .const import CONF_PERSISTANT_CONN, CONF_POLLING_INTERVAL, DATA_COORDINATOR, DOMAIN, CONF_USE_CONTROLS +from .const import CONF_PERSISTENT_CONN, CONF_POLLING_INTERVAL, DATA_COORDINATOR, DOMAIN, CONF_USE_CONTROLS _LOGGER = logging.getLogger(__name__) @@ -119,7 +119,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): **self.config_entry.data, **{ CONF_USE_CONTROLS: user_input[CONF_USE_CONTROLS], - CONF_PERSISTANT_CONN: user_input[CONF_PERSISTANT_CONN], + CONF_PERSISTENT_CONN: user_input[CONF_PERSISTENT_CONN], CONF_POLLING_INTERVAL: user_input[CONF_POLLING_INTERVAL], }, }, @@ -133,7 +133,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): title="", data={ CONF_USE_CONTROLS: user_input[CONF_USE_CONTROLS], - CONF_PERSISTANT_CONN: user_input[CONF_PERSISTANT_CONN], + CONF_PERSISTENT_CONN: user_input[CONF_PERSISTENT_CONN], CONF_POLLING_INTERVAL: user_input[CONF_POLLING_INTERVAL], }, ) @@ -147,8 +147,8 @@ class OptionsFlowHandler(config_entries.OptionsFlow): default=self.config_entry.data.get(CONF_USE_CONTROLS, False), ): selector.BooleanSelector(), vol.Required( - CONF_PERSISTANT_CONN, - default=self.config_entry.data.get(CONF_PERSISTANT_CONN, False), + CONF_PERSISTENT_CONN, + default=self.config_entry.data.get(CONF_PERSISTENT_CONN, False), ): selector.BooleanSelector(), vol.Required( CONF_POLLING_INTERVAL, diff --git a/custom_components/bluetti_bt/const.py b/custom_components/bluetti_bt/const.py index dd7baae..acbf2ab 100644 --- a/custom_components/bluetti_bt/const.py +++ b/custom_components/bluetti_bt/const.py @@ -7,7 +7,7 @@ MANUFACTURER = "Bluetti" CONF_OPTIONS = "options" CONF_USE_CONTROLS = "use_controls" -CONF_PERSISTANT_CONN = "persistant_conn" +CONF_PERSISTENT_CONN = "persistent_conn" CONF_POLLING_INTERVAL = "polling_interval" DATA_COORDINATOR = "coordinator" diff --git a/custom_components/bluetti_bt/coordinator.py b/custom_components/bluetti_bt/coordinator.py index a167253..f8ded43 100644 --- a/custom_components/bluetti_bt/coordinator.py +++ b/custom_components/bluetti_bt/coordinator.py @@ -27,6 +27,7 @@ from bluetti_mqtt.core.devices.bluetti_device import BluettiDevice from bluetti_mqtt.core.commands import ReadHoldingRegisters from .const import DATA_POLLING_RUNNING, DOMAIN +from .utils import mac_loggable _LOGGER = logging.getLogger(__name__) @@ -135,7 +136,7 @@ class PollingCoordinator(DataUpdateCoordinator): address: str, device_name: str, polling_interval: int, - persistant_conn: bool, + persistent_conn: bool, ): """Initialize coordinator.""" super().__init__( @@ -145,17 +146,23 @@ class PollingCoordinator(DataUpdateCoordinator): update_interval=timedelta(seconds=polling_interval), ) self._address = address - self.client = None self.has_notifier = False self.notify_future = None self.current_command = None self.notify_response = bytearray() bluetti_device = build_device(address, device_name) - self.persistant_conn = persistant_conn + self.persistent_conn = persistent_conn # Add or modify device fields self.bluetti_device = DummyDevice(bluetti_device) + # Create client + device = bluetooth.async_ble_device_from_address(hass, address) + if device is None: + self.logger.error("Device %s not available", mac_loggable(address)) + return None + self.client = BleakClient(device) + async def _async_update_data(self): """Fetch data from API endpoint. @@ -166,17 +173,10 @@ class PollingCoordinator(DataUpdateCoordinator): self.logger.debug("Polling data") - device = bluetooth.async_ble_device_from_address(self.hass, self._address) - if device is None: - self.logger.error("Device %s not available", self._address) - return None - if self.bluetti_device is None: - self.logger.error("Device type for %s not found", self._address) + self.logger.error("Device type for %s not found", mac_loggable(self._address)) return None - # Create client - self.client = BleakClient(device) parsed_data: dict = {} # Connect to device @@ -189,10 +189,10 @@ class PollingCoordinator(DataUpdateCoordinator): ) self.has_notifier = True except TimeoutError: - self.logger.warning("Connection timed out for device %s", self._address) + self.logger.debug("Connection timed out for device %s", mac_loggable(self._address)) return None except BleakError as err: - self.logger.warning("Bleak error: %s", err) + self.logger.debug("Bleak error: %s", err) return None try: @@ -240,7 +240,7 @@ class PollingCoordinator(DataUpdateCoordinator): except TimeoutError: self.logger.debug( - "Polling timed out (address: %s)", self._address + "Polling timed out (address: %s)", mac_loggable(self._address) ) except ParseError: self.logger.warning("Got a parse exception...") @@ -255,14 +255,14 @@ class PollingCoordinator(DataUpdateCoordinator): "Needed to disconnect due to error: %s (This can also be the case if you used device controls)", err ) except TimeoutError: - self.logger.debug("Polling timed out for device %s", self._address) + self.logger.debug("Polling timed out for device %s", mac_loggable(self._address)) return None except BleakError as err: self.logger.warning("Bleak error: %s", err) return None finally: # Disconnect if connection not persistant - if not self.persistant_conn: + if not self.persistent_conn: await self.client.disconnect() self.hass.data[DOMAIN][self.config_entry.entry_id][DATA_POLLING_RUNNING] = False diff --git a/custom_components/bluetti_bt/sensor.py b/custom_components/bluetti_bt/sensor.py index 1347396..9721f70 100644 --- a/custom_components/bluetti_bt/sensor.py +++ b/custom_components/bluetti_bt/sensor.py @@ -32,6 +32,7 @@ from bluetti_mqtt.mqtt_client import ( from . import device_info as dev_info, get_unique_id from .const import DATA_COORDINATOR, DOMAIN, CONF_OPTIONS, DIAGNOSTIC_FIELDS, ADDITIONAL_DEVICE_FIELDS from .coordinator import PollingCoordinator, DummyDevice +from .utils import unique_id_loggable _LOGGER = logging.getLogger(__name__) @@ -129,10 +130,10 @@ class BluettiSensor(CoordinatorEntity, SensorEntity): @callback def _handle_coordinator_update(self) -> None: """Handle updated data from the coordinator.""" - _LOGGER.debug("Updating state of %s", self._attr_unique_id) + _LOGGER.debug("Updating state of %s", unique_id_loggable(self._attr_unique_id)) if not isinstance(self.coordinator.data, dict): _LOGGER.debug( - "Invalid data from coordinator (sensor.%s)", self._attr_unique_id + "Invalid data from coordinator (sensor.%s)", unique_id_loggable(self._attr_unique_id) ) self._attr_available = False return @@ -151,7 +152,7 @@ class BluettiSensor(CoordinatorEntity, SensorEntity): ): _LOGGER.warning( "Invalid response data type from coordinator (sensor.%s): %s has type %s", - self._attr_unique_id, + unique_id_loggable(self._attr_unique_id), response_data, type(response_data), ) diff --git a/custom_components/bluetti_bt/switch.py b/custom_components/bluetti_bt/switch.py index 2945226..6e4d5f0 100644 --- a/custom_components/bluetti_bt/switch.py +++ b/custom_components/bluetti_bt/switch.py @@ -35,6 +35,7 @@ from bluetti_mqtt.mqtt_client import ( from . import device_info as dev_info, get_unique_id from .const import CONTROL_FIELDS, DATA_COORDINATOR, DATA_POLLING_RUNNING, DOMAIN, ADDITIONAL_DEVICE_FIELDS from .coordinator import PollingCoordinator, DummyDevice +from .utils import mac_loggable, unique_id_loggable _LOGGER = logging.getLogger(__name__) @@ -113,10 +114,10 @@ class BluettiSwitch(CoordinatorEntity, SwitchEntity): @callback def _handle_coordinator_update(self) -> None: """Handle updated data from the coordinator.""" - _LOGGER.debug("Updating state of %s", self._attr_unique_id) + _LOGGER.debug("Updating state of %s", unique_id_loggable(self._attr_unique_id)) if not isinstance(self.coordinator.data, dict): _LOGGER.error( - "Invalid data from coordinator (switch.%s)", self._attr_unique_id + "Invalid data from coordinator (switch.%s)", unique_id_loggable(self._attr_unique_id) ) self._attr_available = False return @@ -129,7 +130,7 @@ class BluettiSwitch(CoordinatorEntity, SwitchEntity): if not isinstance(response_data, bool): _LOGGER.warning( "Invalid response data type from coordinator (switch.%s): %s", - self._attr_unique_id, + unique_id_loggable(self._attr_unique_id), response_data, ) self._attr_available = False @@ -141,19 +142,19 @@ class BluettiSwitch(CoordinatorEntity, SwitchEntity): async def async_turn_on(self, **kwargs): """Turn the entity on.""" - _LOGGER.debug("Turn on %s on %s", self._response_key, self._address) + _LOGGER.debug("Turn on %s on %s", self._response_key, mac_loggable(self._address)) await self.write_to_device(True) async def async_turn_off(self, **kwargs): """Turn the entity off.""" - _LOGGER.debug("Turn off %s on %s", self._response_key, self._address) + _LOGGER.debug("Turn off %s on %s", self._response_key, mac_loggable(self._address)) await self.write_to_device(False) async def write_to_device(self, state: bool): """Write to device.""" device = bluetooth.async_ble_device_from_address(self.hass, self._address) if device is None: - _LOGGER.error("Device %s not available", self._address) + _LOGGER.error("Device %s not available", mac_loggable(self._address)) return command = self._bluetti_device.build_setter_command(self._response_key, state) @@ -177,7 +178,7 @@ class BluettiSwitch(CoordinatorEntity, SwitchEntity): await asyncio.sleep(5) except TimeoutError: - _LOGGER.error("Timed out for device %s", self._address) + _LOGGER.error("Timed out for device %s", mac_loggable(self._address)) return None except BleakError as err: _LOGGER.error("Bleak error: %s", err) diff --git a/custom_components/bluetti_bt/translations/de.json b/custom_components/bluetti_bt/translations/de.json index b30d9f9..4e3ea74 100644 --- a/custom_components/bluetti_bt/translations/de.json +++ b/custom_components/bluetti_bt/translations/de.json @@ -4,7 +4,7 @@ "init": { "data": { "use_controls": "Steuerung aktivieren (auf eigenes Risiko)", - "persistant_conn": "Dauerhafte Verbindung (Neustart erforderlich)", + "persistent_conn": "Dauerhafte Verbindung (Neustart erforderlich)", "polling_interval": "Datenabruf-Intervall in Sekunden (Neustart erforderlich)" } } diff --git a/custom_components/bluetti_bt/translations/en.json b/custom_components/bluetti_bt/translations/en.json index 3d0e7e6..055b9a9 100644 --- a/custom_components/bluetti_bt/translations/en.json +++ b/custom_components/bluetti_bt/translations/en.json @@ -4,7 +4,7 @@ "init": { "data": { "use_controls": "Use controls (use at own risk)", - "persistant_conn": "Persistant connection (restart required)", + "persistent_conn": "Persistent connection (restart required)", "polling_interval": "Polling interval in seconds (restart required)" } } diff --git a/custom_components/bluetti_bt/utils.py b/custom_components/bluetti_bt/utils.py new file mode 100644 index 0000000..fce973c --- /dev/null +++ b/custom_components/bluetti_bt/utils.py @@ -0,0 +1,12 @@ +"""Tools.""" + +def mac_loggable(mac: str) -> str: + """Remove parts of the mac address for logging.""" + splitted = mac.split(":") + return "XX:XX:XX:XX:XX:" + splitted[-1] + +def unique_id_loggable(unique_id: str) -> str: + """Remove parts of the unique id for logging.""" + splitted = unique_id.split("_", maxsplit=1) + serial = splitted[0][:6] + return serial + "XXXXXXXXXX" + splitted[1]