Skip to content

Commit

Permalink
0x62 & 0x54 are the same protocol, so drop 0x62 and move it to 0x54. …
Browse files Browse the repository at this point in the history
…Plus other changes
  • Loading branch information
8none1 committed Dec 19, 2024
1 parent 003ad94 commit ffc9bf7
Show file tree
Hide file tree
Showing 9 changed files with 40 additions and 337 deletions.
20 changes: 16 additions & 4 deletions custom_components/lednetwf_ble/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import homeassistant.helpers.config_validation as cv
from bluetooth_sensor_state_data import BluetoothData
from home_assistant_bluetooth import BluetoothServiceInfo
import importlib
import pkgutil

from .const import (
DOMAIN,
Expand All @@ -27,11 +29,20 @@
CONF_MODEL,
LedTypes_StripLight,
LedTypes_RingLight,
ColorOrdering,
SUPPORTED_MODELS
ColorOrdering
)

LOGGER = logging.getLogger(__name__)
SUPPORTED_MODELS = []

package = __package__
for _, module_name, _ in pkgutil.iter_modules([f"{package.replace('.', '/')}/models"]):
if module_name.startswith('model_0x'):
module = importlib.import_module(f'.models.{module_name}', package)
if hasattr(module, "SUPPORTED_MODELS"):
LOGGER.debug(f"Supported models: {getattr(module, 'SUPPORTED_MODELS')}")
SUPPORTED_MODELS.extend(getattr(module, "SUPPORTED_MODELS"))
LOGGER.debug(f"All supported modules: {SUPPORTED_MODELS}")

class DeviceData(BluetoothData):
def __init__(self, discovery_info) -> None:
Expand Down Expand Up @@ -196,7 +207,7 @@ async def toggle_light(self):
# # TODO: Why not just this though by default and not use self.mac etc?
# data = {CONF_MAC: self.device_data.address(), CONF_NAME: self.device_data.human_readable_name(), CONF_DELAY: 120, CONF_MODEL: self.device_data.model()}
# LOGGER.debug(f"Device data exists: {data}")
data = {CONF_MAC: self.mac, CONF_NAME: self.name, CONF_DELAY: 120, CONF_MODEL: self.device_data.get_model()}
# data = {CONF_MAC: self.mac, CONF_NAME: self.name, CONF_DELAY: 120, CONF_MODEL: self.device_data.get_model()}
data = {CONF_MAC: self.device_data.address(), CONF_NAME: self.device_data.human_readable_name(), CONF_DELAY: 120, CONF_MODEL: self.device_data.get_model()}
LOGGER.debug(f"Device data is None, creating new data to pass up: {data}")
self._instance = LEDNETWFInstance(self.mac, self.hass, data)
Expand All @@ -205,7 +216,7 @@ async def toggle_light(self):
await self._instance.update()
LOGGER.debug(f"Sending initial packets")
await self._instance.send_initial_packets()
# await self._instance._write_packet(self._instance._model_interface.GET_LED_SETTINGS_PACKET)
await self._instance._write_packet(self._instance._model_interface.GET_LED_SETTINGS_PACKET)
# await asyncio.sleep(1)
for n in range(3):
LOGGER.debug(f"Turning on and off: {n}")
Expand Down Expand Up @@ -250,6 +261,7 @@ async def async_step_user(self, user_input=None):
LOGGER.debug(f"Options flow handler step user. Data: {self._data}, Options: {self._options}, _user_input: {user_input}, model: {model}")

if user_input is not None:
#TODO: check LED types codes for each type. Does this lookup actually work for each type?
new_led_type = user_input.get(CONF_LEDTYPE)
new_led_type = LedTypes_StripLight[new_led_type].value if model == 0x56 else LedTypes_RingLight[new_led_type].value
new_color_order = user_input.get(CONF_COLORORDER)
Expand Down
2 changes: 1 addition & 1 deletion custom_components/lednetwf_ble/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
CONF_LEDTYPE = "ledtype"
CONF_COLORORDER = "colororder"
CONF_MODEL = "model"
SUPPORTED_MODELS = [0x00, 0x53, 0x54, 0x56, 0x62]
# SUPPORTED_MODELS = [0x00, 0x53, 0x54, 0x56, 0x62]


class LedTypes_StripLight(Enum):
Expand Down
35 changes: 2 additions & 33 deletions custom_components/lednetwf_ble/lednetwf.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,25 @@
import pkgutil
from homeassistant.components import bluetooth
from homeassistant.exceptions import ConfigEntryNotReady
# from homeassistant.components.light import (ColorMode)
from homeassistant.components.light import EFFECT_OFF
from bleak.backends.device import BLEDevice
from bleak.backends.service import BleakGATTCharacteristic, BleakGATTServiceCollection
from bleak.exc import BleakDBusError
from bleak_retry_connector import BLEAK_RETRY_EXCEPTIONS as BLEAK_EXCEPTIONS
from bleak_retry_connector import (
BleakClientWithServiceCache,
# BleakError,
BleakNotFoundError,
establish_connection,
retry_bluetooth_connection_error,
)

from typing import Any, TypeVar, cast, Tuple
from collections.abc import Callable
import traceback
import logging

from .const import (
CONF_DELAY,
CONF_MODEL,
CONF_MODEL
)

LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -134,7 +131,6 @@ def __init__(self, mac, hass, data={}, options={}) -> None:
self._options = options
self._hass = hass
self._mac = mac
# TODO: there are too many methods to do the same thing here, just use this one below?
self._delay = self._options.get(CONF_DELAY, self._data.get(CONF_DELAY, 120)) # Try and read from options first, data second so that if this is changed via config then new values are picked up
self.loop = asyncio.get_running_loop()
self._bluetooth_device: BLEDevice | None = None
Expand All @@ -143,35 +139,9 @@ def __init__(self, mac, hass, data={}, options={}) -> None:
raise ConfigEntryNotReady(
f"You need to add bluetooth integration (https://www.home-assistant.io/integrations/bluetooth) or couldn't find a nearby device with address: {self._mac}"
)

service_info = bluetooth.async_last_service_info(self._hass, self._mac).as_dict()
LOGGER.debug(f"Service info: {service_info}")
LOGGER.debug(f"Service info keys: {service_info.keys()}")
## TODO: Do we really need this manu data check now?
# manu_data = service_info['manufacturer_data'].values()
# try:
# manu_data = next(iter(manu_data))
# LOGGER.debug(f"Formatted manufacturer data: {' '.join([f'0x{byte:02X}' for byte in manu_data])}")
# except StopIteration:
# LOGGER.error("Manufacturer data not found.")
# fw_major = f"0x{manu_data[0]:02X}"
# model_class_name = f"Model{fw_major}"
# model_class_name = f"Model0x{self._model:02X}"
# # This might hack in support for more than one model to a single abstraction....
# # This is to attempt support for this issue: https://github.com/raulgbcr/lednetwf_ble/issues/26
# # and avoid just making another copy of 0x62 and renaming it. This is a temporary fix until I work out a better way to do this.
# # Perhaps maintaining a look up table in const would do?
# # TODO: Add a supported model to each model class and then check if the model is supported in the model class?
# if model_class_name == "Model0x55":
# model_class_name = "Model0x62"
# if model_class_name == "Model0x00":
# model_class_name = "Model0x53"
# LOGGER.debug(f"Model class name: {model_class_name}")
# try:
# model_class = globals()[model_class_name]
# except KeyError:
# LOGGER.error(f"Model class {model_class_name} not found. This model is not supported.")
# raise ConfigEntryNotReady(f"Model class {model_class_name} not found. This model is not supported.")
model_class_name = find_model_for_value(self._model)
model_class = globals()[model_class_name]
LOGGER.debug(f"Model class via lookup: {model_class}")
Expand Down Expand Up @@ -328,7 +298,7 @@ async def update(self):
except Exception as error:
LOGGER.debug(f"Error getting status: {error}")
track = traceback.format_exc()
LOGGER.debug(track)
LOGGER.error(track)

async def _ensure_connected(self) -> None:
"""Ensure connection to device is established."""
Expand All @@ -352,7 +322,6 @@ async def _ensure_connected(self) -> None:
self._bluetooth_device,
self.bluetooth_device_name,
self._disconnected,
#cached_services=self._cached_services, # NOTE: removed this and added the next
use_services_cache=True,
ble_device_callback=lambda: self._bluetooth_device,
)
Expand Down
25 changes: 2 additions & 23 deletions custom_components/lednetwf_ble/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,6 @@ def __init__(
) -> None:
self._instance = lednetwfinstance
self._entry_id = entry_id
# 2025.3 ColorMode.BRIGHTNESS should not be specified with other combination of supported color modes, as it will throw an error, but is is supported
# when lights are rendering an effect automatically
# https://developers.home-assistant.io/docs/core/entity/light/#color-modes
# if self._instance._model == RING_LIGHT_MODEL:
# self._attr_supported_color_modes = {ColorMode.COLOR_TEMP, ColorMode.HS}
# self._color_temp_kelvin: self._instance._color_temp_kelvin
# else:
# self._attr_supported_color_modes = {ColorMode.RGB}
self._attr_supported_color_modes = self._instance._model_interface.supported_color_modes
self._attr_supported_features = LightEntityFeature.EFFECT
self._attr_name = name
Expand Down Expand Up @@ -126,7 +118,6 @@ def device_info(self):
"""Return device info."""
return DeviceInfo(
identifiers={
# Serial numbers are unique identifiers within a specific domain
(DOMAIN, self._instance.mac)
},
name=self.name,
Expand Down Expand Up @@ -184,7 +175,6 @@ async def async_turn_on(self, **kwargs: Any) -> None:

if ATTR_COLOR_TEMP_KELVIN in kwargs:
self._instance._color_mode = ColorMode.COLOR_TEMP
# self._instance._effect = EFFECT_OFF
await self._instance.set_color_temp_kelvin(kwargs[ATTR_COLOR_TEMP_KELVIN], on_brightness)
elif ATTR_HS_COLOR in kwargs:
await self._instance.set_hs_color(kwargs[ATTR_HS_COLOR], on_brightness)
Expand All @@ -195,16 +185,6 @@ async def async_turn_on(self, **kwargs: Any) -> None:
self.async_write_ha_state()

async def async_turn_off(self, **kwargs: Any) -> None:
# # Fix for turn of circle effect of HSV MODE(controller skips turn off animation if state is not changed since last turn on)
# Disabling for now, needs to be moved in to the 0x53 code as it is specific to that model
# if self._instance.brightness == 255:
# temp_brightness = 254
# else:
# temp_brightness = self._instance.brightness + 1
# if self._instance.color_mode is ColorMode.HS and ATTR_HS_COLOR not in kwargs:
# await self._instance.set_hs_color(self._instance.hs_color, temp_brightness)

# Actual turn off
await self._instance.turn_off()
self.async_write_ha_state()

Expand All @@ -214,7 +194,6 @@ async def async_update(self) -> None:
self.async_write_ha_state()

def light_local_callback(self):
LOGGER.debug("ZZZ light_local_callback called")
self.async_write_ha_state()

def update_ha_state(self) -> None:
Expand All @@ -230,8 +209,8 @@ def update_ha_state(self) -> None:
#2025.3 When not sure of color mode ColorMode.UNKNOWN avoids throwing errors on unsupported combination of color modes
elif self.hs_color is not None:
self._color_mode = ColorMode.HS
elif self.rgb_color is not None:
self._color_mode = ColorMode.RGB
# elif self.rgb_color is not None:
# self._color_mode = ColorMode.RGB
elif self.color_temp_kelvin is not None:
self._color_mode = ColorMode.COLOR_TEMP
self.available = self._instance.is_on != None
Expand Down
8 changes: 4 additions & 4 deletions custom_components/lednetwf_ble/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@
}
],
"codeowners": [
"@raulgbcr",
"@8none1"
"@8none1",
"@raulgbcr"
],
"config_flow": true,
"dependencies": [
"bluetooth"
],
"documentation": "https://github.com/raulgbcr/lednetwf_ble",
"documentation": "https://github.com/8none1/lednetwf_ble",
"integration_type": "device",
"iot_class": "local_polling",
"issue_tracker": "https://github.com/raulgbcr/lednetwf_ble/issues",
"issue_tracker": "https://github.com/8none1/lednetwf_ble/issues",
"requirements": [
"bleak-retry-connector>=1.17.1",
"bleak>=0.17.0",
Expand Down
15 changes: 12 additions & 3 deletions custom_components/lednetwf_ble/models/model_0x53.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
EFFECT_OFF
)

SUPPORTED_MODELS = [0x00, 0x53]
SUPPORTED_MODELS = [0x00, 0x53, 0x55]

EFFECTS_LIST_0x53 = [
"Gold Ring",
Expand Down Expand Up @@ -167,8 +167,8 @@ def model_specific_manu_data(self, manu_data):
# White mode
self.color_temperature_kelvin = self.min_color_temp + self.manu_data[21] * (self.max_color_temp - self.min_color_temp) / 100
self.brightness = int(self.manu_data[17] * 255 // 100) # This one is in range 0-FF
LOGGER.debug(f"From manu data white brightness: {self.brightness}")
self.color_mode = ColorMode.COLOR_TEMP
LOGGER.debug(f"From manu data white brightness: {self.brightness}")
else:
LOGGER.error(f"Unknown colour mode: {self.manu_data[16]}. Assuming RGB")
raise NotImplementedError("Unknown colour mode")
Expand Down Expand Up @@ -287,7 +287,6 @@ def set_led_settings(self, options: dict):
return led_settings_packet

def notification_handler(self, data):
LOGGER.debug(f"ZZZ Notification handler called in model 0x53")
notification_data = data.decode("utf-8", errors="ignore")
last_quote = notification_data.rfind('"')
if last_quote > 0:
Expand Down Expand Up @@ -342,3 +341,13 @@ def notification_handler(self, data):
self.chip_type = const.LedTypes_RingLight.from_value(payload[3])
self.color_order = const.ColorOrdering.from_value(payload[4])

# TODO:

# # Fix for turn of circle effect of HSV MODE(controller skips turn off animation if state is not changed since last turn on)
# Disabling for now, needs to be moved in to the 0x53 code as it is specific to that model
# if self._instance.brightness == 255:
# temp_brightness = 254
# else:
# temp_brightness = self._instance.brightness + 1
# if self._instance.color_mode is ColorMode.HS and ATTR_HS_COLOR not in kwargs:
# await self._instance.set_hs_color(self._instance.hs_color, temp_brightness)
2 changes: 1 addition & 1 deletion custom_components/lednetwf_ble/models/model_0x54.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
EFFECT_OFF
)

SUPPORTED_MODELS = [0x54]
SUPPORTED_MODELS = [0x54, 0x62]

# This device only supports three colour orders, so override the defaults with our own
# In order to maintain compatibility with the rest of the code, we'll use some of the same values for unsupported colour orders
Expand Down
Loading

0 comments on commit ffc9bf7

Please sign in to comment.