Skip to content

Commit

Permalink
support for dali devices
Browse files Browse the repository at this point in the history
  • Loading branch information
jheling committed Apr 7, 2024
1 parent e382111 commit 9366ab3
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 5 deletions.
26 changes: 24 additions & 2 deletions custom_components/freeathome/fah/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,16 @@
0x0045, # Trigger
]

FUNCTION_IDS_COLOR_ACTUATOR = [
0x002F, # FID_RGB_ACTUATOR
]

FUNCTION_IDS_COLOR_TEMP_ACTUATOR = [
0x0040, # FID_COLORTEMPERATURE_ACTUATOR
]

FUNCTION_IDS_DIMMING_ACTUATOR = [
0x0012, # Dimmer
0x0012, # FID_DIMMING_ACTUATOR
0x1810, # FID_DIMMING_ACTUATOR_TYPE0
0x1819, # FID_DIMMING_ACTUATOR_TYPE9
]
Expand Down Expand Up @@ -127,9 +135,19 @@

# Switch actuator
PID_SWITCH_ON_OFF = 0x0001
PID_ABSOLUTE_SET_VALUE = 0x0011
PID_INFO_ON_OFF = 0x0100

# Light actuator
PID_ABSOLUTE_SET_VALUE = 0x0011
PID_INFO_ACTUAL_DIMMING_VALUE = 0x0110
PID_RGB = 0x0015
PID_COLOR_TEMPERATURE = 0x0016
PID_HSV = 0x0017
PID_COLOR = 0x0018
PID_SATURATION = 0x0019
PID_INFO_RGB = 0x0117
PID_INFO_COLOR_TEMPERATURE = 0x0118
PID_INFO_HSV = 0x011B

# Groups
PID_SYSAP_INFO_ON_OFF = 0x0105
Expand Down Expand Up @@ -197,3 +215,7 @@
# PID_MEASURED_BRIGHTNESS = 0x0403 defined above
PID_WIND_SPEED = 0x0404
PID_RAIN_DETECTION = 0x0405

# Light parameters
PAR_MAXIMUM_COLOR_TEMPERATURE = 0x00F5
PAR_MINIMUM_COLOR_TEMPERATURE = 0x00F6
116 changes: 115 additions & 1 deletion custom_components/freeathome/fah/devices/fah_light.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,22 @@
from ..const import (
FUNCTION_IDS_SWITCHING_ACTUATOR,
FUNCTION_IDS_DIMMING_ACTUATOR,
FUNCTION_IDS_COLOR_ACTUATOR,
FUNCTION_IDS_COLOR_TEMP_ACTUATOR,
PID_SWITCH_ON_OFF,
PID_ABSOLUTE_SET_VALUE,
PID_INFO_ON_OFF,
PID_INFO_ACTUAL_DIMMING_VALUE,
PID_COLOR_TEMPERATURE,
PID_INFO_COLOR_TEMPERATURE,
PID_HSV,
PID_INFO_HSV,
PID_RGB,
PID_INFO_RGB,
PID_COLOR,
PID_SATURATION,
PAR_MAXIMUM_COLOR_TEMPERATURE,
PAR_MINIMUM_COLOR_TEMPERATURE,
)

LOG = logging.getLogger(__name__)
Expand All @@ -18,8 +30,53 @@ class FahLight(FahDevice):
""" Free@Home light object """
state = None
brightness = None
color_temp = None
rgb_color = None
max_color_temp = None
min_color_temp = None


def __init__(self, client, device_info, serialnumber, channel_id, name, datapoints={}, parameters={}, device_updated_cb=None):
# Determine minimum and maximum value for color temperature
if PID_COLOR_TEMPERATURE in datapoints:
if PAR_MAXIMUM_COLOR_TEMPERATURE in parameters:
self.max_color_temp = parameters[PAR_MAXIMUM_COLOR_TEMPERATURE]
if PAR_MINIMUM_COLOR_TEMPERATURE in parameters:
self.min_color_temp = parameters[PAR_MINIMUM_COLOR_TEMPERATURE]

FahDevice.__init__(self, client, device_info, serialnumber, channel_id, name, datapoints=datapoints, parameters=parameters, device_updated_cb=None)


def pairing_ids(function_id=None, switch_as_x=False):

if function_id in FUNCTION_IDS_COLOR_ACTUATOR:
return {
"inputs": [
PID_SWITCH_ON_OFF,
PID_ABSOLUTE_SET_VALUE,
PID_RGB,
],
"outputs": [
PID_INFO_ON_OFF,
PID_INFO_ACTUAL_DIMMING_VALUE,
PID_INFO_RGB,
]
}

if function_id in FUNCTION_IDS_COLOR_TEMP_ACTUATOR:
return {
"inputs": [
PID_SWITCH_ON_OFF,
PID_ABSOLUTE_SET_VALUE,
PID_COLOR_TEMPERATURE,
],
"outputs": [
PID_INFO_ON_OFF,
PID_INFO_ACTUAL_DIMMING_VALUE,
PID_INFO_COLOR_TEMPERATURE,
]
}

if function_id in FUNCTION_IDS_DIMMING_ACTUATOR:
return {
"inputs": [
Expand All @@ -41,6 +98,15 @@ def pairing_ids(function_id=None, switch_as_x=False):
PID_INFO_ON_OFF,
]
}

def parameter_ids(function_id=None):
if function_id in FUNCTION_IDS_COLOR_TEMP_ACTUATOR:
return {
"parameters": [
PAR_MAXIMUM_COLOR_TEMPERATURE,
PAR_MINIMUM_COLOR_TEMPERATURE,
]
}

async def turn_on(self):
""" Turn the light on """
Expand All @@ -52,27 +118,67 @@ async def turn_on(self):
and ((oldstate != self.state and int(self.brightness) > 0) or (oldstate == self.state)):
await self.client.set_datapoint(self.serialnumber, self.channel_id, self._datapoints[PID_ABSOLUTE_SET_VALUE], str(self.brightness))

if self.is_color_temp():
await self.client.set_datapoint(self.serialnumber, self.channel_id, self._datapoints[PID_COLOR_TEMPERATURE], str(self.color_temp))

if self.is_rgb():
await self.client.set_datapoint(self.serialnumber, self.channel_id, self._datapoints[PID_RGB], str(self.rgb_color))

async def turn_off(self):
""" Turn the light off """
await self.client.set_datapoint(self.serialnumber, self.channel_id, self._datapoints[PID_SWITCH_ON_OFF], '0')
self.state = False

def set_brightness(self, brightness):
""" Set the brightness of the light """
""" Set the brightness of the light 0 - 100% """
if self.is_dimmer():
self.brightness = brightness

def get_brightness(self):
""" Return the brightness of the light """
return self.brightness

def set_color_temp(self, color_temp):
"""Set the color temp of the light. Input in Kelvin, internal in 0 - 100 %"""
if self.is_color_temp():
if color_temp == self.min_color_temp:
self.color_temp = 0
elif color_temp == self.max_color_temp:
self.color_temp = 100
else:
self.color_temp = int( (color_temp - self.min_color_temp ) / (self.max_color_temp - self.min_color_temp ))

def get_color_temp(self):
if self.color_temp == 0:
return self.min_color_temp
elif self.color_temp == 100:
return self.max_color_temp
else:
return self.color_temp * (self.max_color_temp - self.min_color_temp) + self.min_color_temp

def set_rgb_color(self, red, green, blue):
if self.is_rgb_color():
self.rgb_color = (red<<16) + (green<<8) + blue

def get_rgb_color(self):
blue = self.rgb_color & 255
green = (self.rgb_color >> 8) & 255
red = (self.rgb_color >> 16) & 255
return red, green, blue

def is_on(self):
""" Return the state of the light """
return self.state

def is_dimmer(self):
"""Return true if device is a dimmer"""
return PID_ABSOLUTE_SET_VALUE in self._datapoints

def is_color_temp(self):
return PID_COLOR_TEMPERATURE in self._datapoints

def is_rgb(self):
return PID_RGB in self._datapoints

def update_datapoint(self, dp, value):
"""Receive updated datapoint."""
Expand All @@ -84,6 +190,14 @@ def update_datapoint(self, dp, value):
self.brightness = value
LOG.info("light device %s (%s) dp %s brightness %s", self.name, self.lookup_key, dp, value)

elif PID_INFO_COLOR_TEMPERATURE in self._datapoints and self._datapoints[PID_INFO_COLOR_TEMPERATURE] == dp:
self.color_temp = value
LOG.info("light device %s (%s) dp %s color temperature %s", self.name, self.lookup_key, dp, value)

elif PID_INFO_RGB in self._datapoints and self._datapoints[PID_INFO_RGB] == dp:
self.rgb_color = value
LOG.info("light device %s (%s) dp %s rgb color %s", self.name, self.lookup_key, dp, value)

else:
LOG.info("light device %s (%s) unknown dp %s value %s", self.name, self.lookup_key, dp, value)

Expand Down
8 changes: 8 additions & 0 deletions custom_components/freeathome/fah/devices/fah_light_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ class FahLightGroup(FahDevice):
""" Free@home light group """
state = None
brightness = None
color_temp = None
rgb_color = None

def pairing_ids(function_id=None):
if function_id in FUNCTION_IDS_LIGHT_GROUP:
Expand Down Expand Up @@ -69,6 +71,12 @@ def is_dimmer(self):
"""Return true if device is a dimmer"""
return PID_ABSOLUTE_SET_VALUE in self._datapoints

def is_color_temp(self):
return False

def is_rgb(self):
return False

def update_datapoint(self, dp, value):
"""Receive updated datapoint."""
if PID_SYSAP_INFO_ON_OFF in self._datapoints and self._datapoints[PID_SYSAP_INFO_ON_OFF] == dp:
Expand Down
65 changes: 63 additions & 2 deletions custom_components/freeathome/light.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
""" Support for Free@Home lights dimmers """
import logging
from homeassistant.components.light import (
ATTR_BRIGHTNESS, ColorMode, LightEntity)
ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_RGB_COLOR, ColorMode, LightEntity)

from .const import DOMAIN

Expand Down Expand Up @@ -30,17 +30,40 @@ class FreeAtHomeLight(LightEntity):
_state = None
_brightness = None
_is_dimmer = None
_is_rgb = None
_is_color_temp = None
_color_temp_kelvin = None
_max_color_temp_kelvin = None
_min_color_temp_kelvin = None
_rgb_color = None

def __init__(self, device):
self.light_device = device
self._name = self.light_device.name
self._state = self.light_device.state

self._is_dimmer = self.light_device.is_dimmer()
if self.light_device.brightness is not None:
self._brightness = int(float(self.light_device.brightness) * 2.55)
else:
self._brightness = None

self._is_color_temp = self.light_device.is_color_temp()
if self.light_device.color_temp is not None:
self._color_temp_kelvin = self.light_device.get_color_temp()
self._max_color_temp_kelvin = self.light_device.max_color_temp
self._min_color_temp_kelvin = self.light_device.min_color_temp
else:
self._color_temp_kelvin = None
self._max_color_temp_kelvin = None
self._min_color_temp_kelvin = None

self._is_rgb = self.light_device.is_rgb()
if self.light_device.rgb_color is not None:
self._rgb_color = self.light_device.get_rgb_color()
else:
self._rgb_color = None

@property
def name(self):
"""Return the display name of this light."""
Expand All @@ -63,14 +86,22 @@ def should_poll(self):

@property
def color_mode(self) -> str | None:
"""Return the color mode of the light."""
"""Return the color mode of the light."""
if self._is_rgb:
return ColorMode.RGB
if self._is_color_temp:
return ColorMode.COLOR_TEMP
if self._is_dimmer:
return ColorMode.BRIGHTNESS
return ColorMode.ONOFF

@property
def supported_color_modes(self) -> set[str] | None:
"""Flag supported color modes."""
if self._is_rgb:
return {ColorMode.RGB}
if self._is_color_temp:
return {ColorMode.COLOR_TEMP}
if self._is_dimmer:
return {ColorMode.BRIGHTNESS}
return {ColorMode.ONOFF}
Expand All @@ -84,6 +115,22 @@ def is_on(self):
def brightness(self):
"""Brightness of this light between 0..255."""
return self._brightness

@property
def color_temp_kelvin(self) -> int | None:
return self._color_temp_kelvin

@property
def max_color_temp_kelvin(self) -> int | None:
return self._max_color_temp_kelvin

@property
def min_color_temp_kelvin(self) -> int | None:
return self._min_color_temp_kelvin

@property
def rgb_color(self) -> tuple[int, int, int] | None:
return self._rgb_color

async def async_added_to_hass(self):
"""Register callback to update hass after device was changed."""
Expand All @@ -97,6 +144,14 @@ async def after_update_callback(device):
async def async_turn_on(self, **kwargs):
"""Instruct the light to turn on.
"""
if ATTR_COLOR_TEMP in kwargs:
self._color_temp_kelvin = kwargs[ATTR_COLOR_TEMP]
self.light_device.set_color_temp(self._color_temp_kelvin)

if ATTR_RGB_COLOR in kwargs:
self._rgb_color = kwargs[ATTR_RGB_COLOR]
self.light_device.set_rgb_color(int( self._rgb_color[0]), int( self._rgb_color[1]), int( self._rgb_color[2]))

if ATTR_BRIGHTNESS in kwargs:
self._brightness = kwargs[ATTR_BRIGHTNESS]
self.light_device.set_brightness(int(self._brightness / 2.55))
Expand All @@ -117,3 +172,9 @@ async def async_update(self):
self._state = self.light_device.is_on()
if self.light_device.brightness is not None:
self._brightness = int(float(self.light_device.get_brightness()) * 2.55)

if self.light_device.color_temp is not None:
self._color_temp_kelvin = self.light_device.get_color_temp()

if self.light_device.rgb_color is not None:
self._rgb_color = self.light_device.get_rgb_color()

0 comments on commit 9366ab3

Please sign in to comment.