Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add proof of concept support for HSL lighting #1

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,14 @@ mesh:
<hass_device_id>:
uuid: <bluetooth_mesh_device_uuid>
name: <hass_device_name>
type: light # thats it for now
[brightness_min: 0] # might be always 0.
[brightness_max: 100] # max BLE brightness value
[mireds_min: 0] # min BLE mireds value.
[mireds_max: 100] # max BLE mireds value
[ack: <true|false>] # use ack or unack mode
[relay: <true|false>] # whether this node should act as relay
type: light # thats it for now
[brightness_min: 0] # might be always 0.
[brightness_max: 100] # max BLE brightness value
[mireds_min: 0] # min BLE mireds value.
[mireds_max: 100] # max BLE mireds value
[hsl_ligthness_max: 65535] # the maximum brightness allowed for HSL (RGB) mode. Lower this if colors become washed out at high brightness.
[ack: <true|false>] # use ack or unack mode
[relay: <true|false>] # whether this node should act as relay
...
```

Expand Down
4 changes: 2 additions & 2 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ RUN sh ./install-bluez.sh

# install bridge
WORKDIR /opt/hass-ble-mesh
RUN git clone https://github.com/minims/homeassistant-bluetooth-mesh.git .
RUN git checkout master
RUN git clone https://github.com/louisjennings/homeassistant-bluetooth-mesh.git .
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
RUN git clone https://github.com/louisjennings/homeassistant-bluetooth-mesh.git .
RUN git clone https://github.com/minims/homeassistant-bluetooth-mesh.git .

After merge it will use minims/master

RUN git checkout light-hsl
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
RUN git checkout light-hsl
RUN git checkout master

RUN pip3 install -r requirements.txt

# mount config
Expand Down
30 changes: 16 additions & 14 deletions docker/config/config.yaml.sample
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,22 @@ mesh:
<hass_device_id>:
uuid: <bluetooth_mesh_device_uuid>
name: <hass_device_name>
type: light # Only type supported for now.
brightness_min: 0 # Might be always 0.
brightness_max: 65535 # Max BLE brightness value
mireds_min: 5 # Min BLE mireds value.
mireds_max: 1250 # Max BLE mireds value
ack: false # use ack or unack mode
relay: false # Whether this node should act as a Bluetooth Relay
type: light # Only type supported for now.
brightness_min: 0 # Might be always 0.
brightness_max: 65535 # Max BLE brightness value
mireds_min: 5 # Min BLE mireds value.
mireds_max: 1250 # Max BLE mireds value
hsl_ligthness_max: 65535 # the maximum brightness allowed for HSL (RGB) mode. Lower this if colors become washed out at high brightness.
ack: false # use ack or unack mode
relay: false # Whether this node should act as a Bluetooth Relay
<hass_device_id_2>:
uuid: <bluetooth_mesh_device_uuid>
name: <hass_device_name>
type: light # Only type supported for now.
brightness_min: 0 # Might be always 0.
brightness_max: 65535 # Max BLE brightness value
mireds_min: 5 # Min BLE mireds value.
mireds_max: 1250 # Max BLE mireds value
ack: true # use ack or unack mode
relay: false # Whether this node should act as a Bluetooth Relay
type: light # Only type supported for now.
brightness_min: 0 # Might be always 0.
brightness_max: 65535 # Max BLE brightness value
mireds_min: 5 # Min BLE mireds value.
mireds_max: 1250 # Max BLE mireds value
hsl_ligthness_max: 65535 # the maximum brightness allowed for HSL (RGB) mode. Lower this if colors become washed out at high brightness.
ack: false # use ack or unack mode
relay: false # Whether this node should act as a Bluetooth Relay
3 changes: 3 additions & 0 deletions gateway/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class MainElement(Element):
models.GenericOnOffClient,
models.LightLightnessClient,
models.LightCTLClient,
models.LightHSLClient,
]


Expand Down Expand Up @@ -160,6 +161,8 @@ async def _import_keys(self):
await client.bind(self.app_keys[0][0])
client = self.elements[0][models.LightCTLClient]
await client.bind(self.app_keys[0][0])
client = self.elements[0][models.LightHSLClient]
await client.bind(self.app_keys[0][0])

async def _try_bind_node(self, node):
try:
Expand Down
38 changes: 38 additions & 0 deletions gateway/mesh/nodes/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
BLE_MESH_MAX_TEMPERATURE = 20000 # Kelvin
BLE_MESH_MIN_MIRED = 50
BLE_MESH_MAX_MIRED = 1250
BLE_MESH_MAX_HSL_LIGHTNESS = 65535


class Light(Generic):
Expand All @@ -32,6 +33,9 @@ class Light(Generic):
OnOffProperty = "onoff"
BrightnessProperty = "brightness"
TemperatureProperty = "temperature"
HueProperty = "hue"
SaturationProperty = "saturation"
ModeProperty = "mode"

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
Expand Down Expand Up @@ -78,6 +82,14 @@ async def mireds_to_kelvin(self, temperature, ack=False):
else:
await self.set_ctl(temperature=kelvin)

async def hsl(self, h, s, l, ack=False):
if self._is_model_bound(models.LightHSLServer):
if not ack:
await self.set_hsl_unack(h=h,s=s,l=l)
else:
await self.set_hsl(h=h,s=s,l=l)


async def bind(self, app):
await super().bind(app)

Expand All @@ -97,6 +109,10 @@ async def bind(self, app):
await self.get_ctl()
await self.get_light_temperature_range()

if await self.bind_model(models.LightHSLServer):
self._features.add(Light.HueProperty)
self._features.add(Light.SaturationProperty)

async def set_onoff_unack(self, onoff, **kwargs):
self.notify(Light.OnOffProperty, onoff)
client = self._app.elements[0][models.GenericOnOffClient]
Expand Down Expand Up @@ -136,6 +152,24 @@ async def set_lightness(self, lightness, **kwargs):
client = self._app.elements[0][models.LightLightnessClient]
await client.set_lightness([self.unicast], app_index=self._app.app_keys[0][0], lightness=lightness, **kwargs)

async def set_hsl(self, h, s, l, **kwargs):
self.notify(Light.ModeProperty, 'hsl')
self.notify(Light.HueProperty, h)
self.notify(Light.SaturationProperty, s)
self.notify(Light.BrightnessProperty, l)

client = self._app.elements[0][models.LightHSLClient]
await client.set_hsl(self.unicast, app_index=self._app.app_keys[0][0], lightness=l, hue=h, saturation=s, transition_time=0, **kwargs)

async def set_hsl_unack(self, h, s, l, **kwargs):
self.notify(Light.ModeProperty, 'hsl')
self.notify(Light.HueProperty, h)
self.notify(Light.SaturationProperty, s)
self.notify(Light.BrightnessProperty, l)

client = self._app.elements[0][models.LightHSLClient]
await client.set_hsl_unack(self.unicast, app_index=self._app.app_keys[0][0], lightness=l, hue=h, saturation=s, transition_time=0, **kwargs)

async def get_lightness(self):
client = self._app.elements[0][models.LightLightnessClient]
state = await client.get_lightness([self.unicast], self._app.app_keys[0][0])
Expand Down Expand Up @@ -165,6 +199,8 @@ async def set_ctl_unack(self, temperature=None, brightness=None, **kwargs):
if brightness and brightness > BLE_MESH_MAX_LIGHTNESS:
brightness = BLE_MESH_MAX_LIGHTNESS

self.notify(Light.ModeProperty, 'ctl')

if temperature:
self.notify(Light.TemperatureProperty, temperature)
else:
Expand All @@ -190,6 +226,8 @@ async def set_ctl(self, temperature=None, **kwargs):
elif temperature and temperature > BLE_MESH_MAX_TEMPERATURE:
temperature = BLE_MESH_MAX_TEMPERATURE

self.notify(Light.ModeProperty, 'ctl')

if temperature:
self.notify(Light.TemperatureProperty, temperature)
else:
Expand Down
40 changes: 35 additions & 5 deletions gateway/mqtt/bridges/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
BLE_MESH_MAX_TEMPERATURE,
BLE_MESH_MAX_MIRED,
BLE_MESH_MIN_MIRED,
BLE_MESH_MAX_HSL_LIGHTNESS,
Light,
)
from mqtt.bridge import HassMqttBridge
Expand Down Expand Up @@ -62,6 +63,9 @@ async def config(self, node):
message["min_mireds"] = node.config.optional("min_mireds", BLE_MESH_MIN_MIRED)
message["max_mireds"] = node.config.optional("max_mireds", BLE_MESH_MAX_MIRED)

if node.supports(Light.HueProperty) and node.supports(Light.SaturationProperty):
color_modes.add("hs")

if color_modes:
message["color_mode"] = True
message["supported_color_modes"] = list(color_modes)
Expand All @@ -82,9 +86,12 @@ async def _state(self, node, onoff):
int(node.retained(Light.BrightnessProperty, BLE_MESH_MAX_LIGHTNESS)) / self.brightness_max * 100
)

if onoff and node.supports(Light.TemperatureProperty):
if onoff and node.supports(Light.TemperatureProperty) and (node.retained(Light.ModeProperty, None) == 'ctl'):
message["color_temp"] = node.retained(Light.TemperatureProperty, BLE_MESH_MAX_TEMPERATURE)

if onoff and node.supports(Light.HueProperty) and node.supports(Light.SaturationProperty) and (node.retained(Light.ModeProperty, None) == 'hsl'):
message["color"] = {'h': node.retained(Light.HueProperty, None)/0xFFFF * 360, 's': node.retained(Light.SaturationProperty, None) / 0xFFFF * 100}

await self._messenger.publish(self.component, node, "state", message, retain=True)

async def _mqtt_set(self, node, payload):
Expand All @@ -93,10 +100,33 @@ async def _mqtt_set(self, node, payload):

if "brightness" in payload:
brightness = int(payload["brightness"])
desired_brightness = int(brightness * self.brightness_max / 100)
if desired_brightness > BLE_MESH_MAX_LIGHTNESS:
desired_brightness = BLE_MESH_MAX_LIGHTNESS
await node.set_brightness(brightness=desired_brightness, ack=node.config.optional("ack"))

if node.retained(Light.ModeProperty, 'ctl') == 'ctl':
desired_brightness = int(brightness * self.brightness_max / 100)
if desired_brightness > BLE_MESH_MAX_LIGHTNESS:
desired_brightness = BLE_MESH_MAX_LIGHTNESS
await node.set_brightness(brightness=desired_brightness, ack=node.config.optional("ack"))
elif node.retained(Light.ModeProperty, None) == 'hsl':

max_hsl_lightness = node.config.optional("hsl_ligthness_max", BLE_MESH_MAX_HSL_LIGHTNESS)

h = node.retained(Light.HueProperty, 0)
s = node.retained(Light.SaturationProperty, 0xFFFF)
l = brightness * max_hsl_lightness // 100
await node.hsl(h=h,s=s,l=l)
else:
raise NotImplemented(f"Cannot set brightness for mode: {node.retained(Light.ModeProperty, None)}")


if "color" in payload:
h = int(payload["color"]["h"]*0xffff/360)
s = int(payload["color"]["s"]*0xffff/100)
l = node.retained(Light.BrightnessProperty, 0xFFFF)

max_hsl_lightness = node.config.optional("hsl_ligthness_max", BLE_MESH_MAX_HSL_LIGHTNESS)
l_scaled = l * max_hsl_lightness // 0xFFFF

await node.hsl(h=h,s=s,l=l_scaled)

if payload.get("state") == "ON":
await node.turn_on(ack=node.config.optional("ack"))
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
asyncio-mqtt==0.12.1
bitstring==3.1.9
black==22.10.0
git+https://github.com/minims/python-bluetooth-mesh@43f3b368add0e0a40782478369a30454b95a9034
git+https://github.com/louisjennings/python-bluetooth-mesh@dc4132274b0883c9296eab9dd0998a345280e051
Copy link
Owner Author

@Minims Minims Aug 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
git+https://github.com/louisjennings/python-bluetooth-mesh@dc4132274b0883c9296eab9dd0998a345280e051
git+https://github.com/minims/python-bluetooth-mesh@d813d4860025bf28a7338681229e728feeeed851

I have rebase my fork to latest version of the official repo

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The HSL functionality depends upon changes I made in the required commit. I'll eventually try getting the HSL models included in the official repo. Until then, I can create a pull request from the feature branch to your repo.

cffi==1.15.1
construct==2.9.45
crc==0.3.0
Expand Down