Pilight, KaKu switch, switch state?

Hi jayjay,

-S/-P are the ip and port of the device pilight is running on ie. the server
-p is the device to control
-t = turn on -f = turn off -d is the dim value
-i is the device id to control
-u in the unit to control

All explained in https://wiki.pilight.org/doku.php/psend

Nevertheless thanx for explaining!

Google seems most overlooked in here :stuck_out_tongue: and the search button at the top :stuck_out_tongue:

Still glad to be of service :smiley:

Hi Guys,

I’m new to Home Assistant… I do like the platform so far, but a dealbreaker for me is that the current pilight compoment doesn’t support dimmers…

I have no experience with python at all, but I do with some other languages. I’ve put this together this afternoon and tested it, works fine for me… I know this can be done better, but it works. I’ve reused much code from the switch compoment.

To test you can place this file als pilight.py in the /custom_components/light (folders should be created).

Can anyone test if it works fine for them too? If so I’ll do a git pull request to add it

"""
Support for lights via Pilight.

For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.pilight/
"""
import logging

import voluptuous as vol

import homeassistant.helpers.config_validation as cv
import homeassistant.components.pilight as pilight
from homeassistant.components.light import (ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light, PLATFORM_SCHEMA)
from homeassistant.const import (CONF_NAME, CONF_ID, CONF_DEVICES, CONF_STATE,
                                 CONF_PROTOCOL)

_LOGGER = logging.getLogger(__name__)

CONF_OFF_CODE = 'off_code'
CONF_OFF_CODE_RECIEVE = 'off_code_receive'
CONF_ON_CODE = 'on_code'
CONF_ON_CODE_RECIEVE = 'on_code_receive'
CONF_SYSTEMCODE = 'systemcode'
CONF_UNIT = 'unit'
CONF_UNITCODE = 'unitcode'
CONF_ECHO = 'echo'
CONF_DIMMABLE = 'dimmable'

DEPENDENCIES = ['pilight']

COMMAND_SCHEMA = vol.Schema({
    vol.Optional(CONF_PROTOCOL): cv.string,
    vol.Optional('on'): cv.positive_int,
    vol.Optional('off'): cv.positive_int,
    vol.Optional(CONF_UNIT): cv.positive_int,
    vol.Optional(CONF_UNITCODE): cv.positive_int,
    vol.Optional(CONF_ID): vol.Any(cv.positive_int, cv.string),
    vol.Optional(CONF_STATE): cv.string,
    vol.Optional(CONF_SYSTEMCODE): cv.positive_int,
}, extra=vol.ALLOW_EXTRA)

RECEIVE_SCHEMA = COMMAND_SCHEMA.extend({
    vol.Optional(CONF_ECHO): cv.boolean
})

SWITCHES_SCHEMA = vol.Schema({
    vol.Required(CONF_ON_CODE): COMMAND_SCHEMA,
    vol.Required(CONF_OFF_CODE): COMMAND_SCHEMA,
    vol.Optional(CONF_NAME): cv.string,
    vol.Optional(CONF_OFF_CODE_RECIEVE, default=[]): vol.All(cv.ensure_list,
                                                             [COMMAND_SCHEMA]),
    vol.Optional(CONF_ON_CODE_RECIEVE, default=[]): vol.All(cv.ensure_list,
                                                            [COMMAND_SCHEMA]),
    vol.Optional(CONF_DIMMABLE, default=False): cv.boolean
})

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    vol.Required(CONF_DEVICES):
        vol.Schema({cv.string: SWITCHES_SCHEMA}),
})


def setup_platform(hass, config, add_devices, discovery_info=None):
    """Set up the Pilight platform."""
    devices = []

    for dev_name, properties in config.get(CONF_DEVICES).items():
        devices.append(
            PilightDimmableLight(
                hass,
                properties.get(CONF_NAME, dev_name),
                properties.get(CONF_ON_CODE),
                properties.get(CONF_OFF_CODE),
                properties.get(CONF_ON_CODE_RECIEVE),
                properties.get(CONF_OFF_CODE_RECIEVE),
                properties.get(CONF_DIMMABLE)
            )
        )

    add_devices(devices)


class _ReceiveHandle(object):
    def __init__(self, config, echo):
        """Initialize the handle."""
        self.config_items = config.items()
        self.echo = echo

    def match(self, code):
        """Test if the received code matches the configured values.

        The received values have to be a subset of the configured options.
        """
        return self.config_items <= code.items()

    def run(self, light, turn_on):
        """Change the state of the switch."""
        switch.set_state(turn_on=turn_on, send_code=self.echo)


class PilightDimmableLight(Light):
    """Representation of a Pilight light."""

    def __init__(self, hass, name, code_on, code_off, code_on_receive,
                 code_off_receive, dimmable):
        """Initialize the light."""
        self._hass = hass
        self._name = name
        self._state = False

        self._code_on = code_on
        self._code_off = code_off

        self._code_on_receive = []
        self._code_off_receive = []

        self._brightness = 255
        self._dimmable = dimmable

        for code_list, conf in ((self._code_on_receive, code_on_receive),
                                (self._code_off_receive, code_off_receive)):
            for code in conf:
                echo = code.pop(CONF_ECHO, True)
                code_list.append(_ReceiveHandle(code, echo))

        if any(self._code_on_receive) or any(self._code_off_receive):
            hass.bus.listen(pilight.EVENT, self._handle_code)

    @property
    def name(self):
        """Get the name of the light."""
        return self._name

    @property
    def brightness(self):
        """Return the brightness"""
        return self._brightness

    @property
    def is_on(self):
        """Return true if switch is on."""
        return self._state

    @property
    def supported_features(self):
        """Flag supported features."""

        if self._dimmable:
            return SUPPORT_BRIGHTNESS
        else:
            return None

    def _handle_code(self, call):
        """Check if received code by the pilight-daemon.

        If the code matches the receive on/off codes of this switch the switch
        state is changed accordingly.
        """
        # - True if off_code/on_code is contained in received code dict, not
        #   all items have to match.
        # - Call turn on/off only once, even if more than one code is received
        if any(self._code_on_receive):
            for on_code in self._code_on_receive:
                if on_code.match(call.data):
                    on_code.run(light=self, turn_on=True)
                    break

        if any(self._code_off_receive):
            for off_code in self._code_off_receive:
                if off_code.match(call.data):
                    off_code.run(light=self, turn_on=False)
                    break

    def set_state(self, turn_on, dimlevel=15, send_code=True):
        """Set the state of the switch.

        This sets the state of the switch. If send_code is set to True, then
        it will call the pilight.send service to actually send the codes
        to the pilight daemon.
        """
        if send_code:
            if turn_on:
                code = self._code_on

                if self._dimmable:
                    code.update({'dimlevel':dimlevel})

                self._hass.services.call(pilight.DOMAIN, pilight.SERVICE_NAME,
                                         code, blocking=True)
            else:
                self._hass.services.call(pilight.DOMAIN, pilight.SERVICE_NAME,
                                         self._code_off, blocking=True)

        self._state = turn_on
        self.schedule_update_ha_state()

    def turn_on(self, **kwargs):
        """Turn the switch on by calling pilight.send service with on code."""
        self._brightness = kwargs.get(ATTR_BRIGHTNESS, 255)
        dimlevel = int(self._brightness / 17)

        self.set_state(turn_on=True, dimlevel=dimlevel)

    def turn_off(self, **kwargs):
        """Turn the switch on by calling pilight.send service with off code."""
        self.set_state(turn_on=False)

Configuration example:

light:
  - platform: pilight
    devices:
      test2:
        dimmable: true
        on_code:
          protocol: kaku_dimmer
          id: 23298822
          unit: 10
          'on': 1
        off_code:
          protocol: kaku_dimmer
          id: 23298822
          unit: 10
          'off': 1
        on_code_receive:
          protocol: kaku_dimmer
          id: 23298822
          unit: 10
          state: 'on'
        off_code_receive:
          protocol: kaku_dimmer
          id: 23298822
          unit: 10
          state: 'off'

Hi KennieNL,

I just came across your post while searching for a way to be able to dim using pilight.
The code you posted works great for me, dimming works good.
The only thing I did not get working up until now is the on_code_receive/off_code_receive.

When I turn a lamp on/off using a hardware remote, I expect to see a switch change in HASS but this does not happen.
Not sure if this is due to the code you wrote or something on my side.

Cheers

Hi,

I just figured out why receiving codes wasn’t working with KennieNL’s code.
There is one typo/misconfig in the pilight.py file posted above, a switch.set_state should be light.set_state.

For completeness below the working file:

Support for lights via Pilight.

"""
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.pilight/
"""
import logging

import voluptuous as vol

import homeassistant.helpers.config_validation as cv
import homeassistant.components.pilight as pilight
from homeassistant.components.light import (ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light, PLATFORM_SCHEMA)
from homeassistant.const import (CONF_NAME, CONF_ID, CONF_DEVICES, CONF_STATE,
                                 CONF_PROTOCOL)

_LOGGER = logging.getLogger(__name__)

CONF_OFF_CODE = 'off_code'
CONF_OFF_CODE_RECIEVE = 'off_code_receive'
CONF_ON_CODE = 'on_code'
CONF_ON_CODE_RECIEVE = 'on_code_receive'
CONF_SYSTEMCODE = 'systemcode'
CONF_UNIT = 'unit'
CONF_UNITCODE = 'unitcode'
CONF_ECHO = 'echo'
CONF_DIMMABLE = 'dimmable'

DEPENDENCIES = ['pilight']

COMMAND_SCHEMA = vol.Schema({
    vol.Optional(CONF_PROTOCOL): cv.string,
    vol.Optional('on'): cv.positive_int,
    vol.Optional('off'): cv.positive_int,
    vol.Optional(CONF_UNIT): cv.positive_int,
    vol.Optional(CONF_UNITCODE): cv.positive_int,
    vol.Optional(CONF_ID): vol.Any(cv.positive_int, cv.string),
    vol.Optional(CONF_STATE): cv.string,
    vol.Optional(CONF_SYSTEMCODE): cv.positive_int,
}, extra=vol.ALLOW_EXTRA)

RECEIVE_SCHEMA = COMMAND_SCHEMA.extend({
    vol.Optional(CONF_ECHO): cv.boolean
})

SWITCHES_SCHEMA = vol.Schema({
    vol.Required(CONF_ON_CODE): COMMAND_SCHEMA,
    vol.Required(CONF_OFF_CODE): COMMAND_SCHEMA,
    vol.Optional(CONF_NAME): cv.string,
    vol.Optional(CONF_OFF_CODE_RECIEVE, default=[]): vol.All(cv.ensure_list,
                                                             [COMMAND_SCHEMA]),
    vol.Optional(CONF_ON_CODE_RECIEVE, default=[]): vol.All(cv.ensure_list,
                                                            [COMMAND_SCHEMA]),
    vol.Optional(CONF_DIMMABLE, default=False): cv.boolean
})

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    vol.Required(CONF_DEVICES):
        vol.Schema({cv.string: SWITCHES_SCHEMA}),
})


def setup_platform(hass, config, add_devices, discovery_info=None):
    """Set up the Pilight platform."""
    devices = []

    for dev_name, properties in config.get(CONF_DEVICES).items():
        devices.append(
            PilightDimmableLight(
                hass,
                properties.get(CONF_NAME, dev_name),
                properties.get(CONF_ON_CODE),
                properties.get(CONF_OFF_CODE),
                properties.get(CONF_ON_CODE_RECIEVE),
                properties.get(CONF_OFF_CODE_RECIEVE),
                properties.get(CONF_DIMMABLE)
            )
        )

    add_devices(devices)


class _ReceiveHandle(object):
    def __init__(self, config, echo):
        """Initialize the handle."""
        self.config_items = config.items()
        self.echo = echo

    def match(self, code):
        """Test if the received code matches the configured values.

        The received values have to be a subset of the configured options.
        """
        return self.config_items <= code.items()

    def run(self, light, turn_on):
        """Change the state of the switch."""
        light.set_state(turn_on=turn_on, send_code=self.echo)


class PilightDimmableLight(Light):
    """Representation of a Pilight light."""

    def __init__(self, hass, name, code_on, code_off, code_on_receive,
                 code_off_receive, dimmable):
        """Initialize the light."""
        self._hass = hass
        self._name = name
        self._state = False

        self._code_on = code_on
        self._code_off = code_off

        self._code_on_receive = []
        self._code_off_receive = []

        self._brightness = 255
        self._dimmable = dimmable

        for code_list, conf in ((self._code_on_receive, code_on_receive),
                                (self._code_off_receive, code_off_receive)):
            for code in conf:
                echo = code.pop(CONF_ECHO, True)
                code_list.append(_ReceiveHandle(code, echo))

        if any(self._code_on_receive) or any(self._code_off_receive):
            hass.bus.listen(pilight.EVENT, self._handle_code)

    @property
    def name(self):
        """Get the name of the light."""
        return self._name

    @property
    def brightness(self):
        """Return the brightness"""
        return self._brightness

    @property
    def is_on(self):
        """Return true if switch is on."""
        return self._state

    @property
    def supported_features(self):
        """Flag supported features."""

        if self._dimmable:
            return SUPPORT_BRIGHTNESS
        else:
            return None

    def _handle_code(self, call):
        """Check if received code by the pilight-daemon.

        If the code matches the receive on/off codes of this switch the switch
        state is changed accordingly.
        """
        # - True if off_code/on_code is contained in received code dict, not
        #   all items have to match.
        # - Call turn on/off only once, even if more than one code is received
        if any(self._code_on_receive):
            for on_code in self._code_on_receive:
                if on_code.match(call.data):
                    on_code.run(light=self, turn_on=True)
                    break

        if any(self._code_off_receive):
            for off_code in self._code_off_receive:
                if off_code.match(call.data):
                    off_code.run(light=self, turn_on=False)
                    break

    def set_state(self, turn_on, dimlevel=15, send_code=True):
        """Set the state of the switch.

        This sets the state of the switch. If send_code is set to True, then
        it will call the pilight.send service to actually send the codes
        to the pilight daemon.
        """
        if send_code:
            if turn_on:
                code = self._code_on

                if self._dimmable:
                    code.update({'dimlevel':dimlevel})

                self._hass.services.call(pilight.DOMAIN, pilight.SERVICE_NAME,
                                         code, blocking=True)
            else:
                self._hass.services.call(pilight.DOMAIN, pilight.SERVICE_NAME,
                                         self._code_off, blocking=True)

        self._state = turn_on
        self.schedule_update_ha_state()

    def turn_on(self, **kwargs):
        """Turn the switch on by calling pilight.send service with on code."""
        self._brightness = kwargs.get(ATTR_BRIGHTNESS, 255)
        dimlevel = int(self._brightness / 17)

        self.set_state(turn_on=True, dimlevel=dimlevel)

    def turn_off(self, **kwargs):
        """Turn the switch on by calling pilight.send service with off code."""
        self.set_state(turn_on=False)

Credits go to KennieNL for this custom light implementation! Thanks for that!

That is great! One question remains and I guess it might be helpfull for more people how do we get this py file in HASSIO.
Is it simple a matter of dumping it in the addon folder?

To use this custom pilight configuration, you need to possibly create the folders ./custom_components/light and simply dump the pilight.py file in there

Then you of course need to create the proper configuration as KennieNL showed in his post.

I added some functionality to be able to set a default brightness level in configuration, so that the light doesn’t always dim to full when it’s turned on.

    """
Support for lights via Pilight.

For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.pilight/
"""
import logging

import voluptuous as vol

import homeassistant.helpers.config_validation as cv
import homeassistant.components.pilight as pilight
from homeassistant.components.light import (ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light, PLATFORM_SCHEMA)
from homeassistant.const import (CONF_NAME, CONF_ID, CONF_DEVICES, CONF_STATE,
                                 CONF_PROTOCOL)

_LOGGER = logging.getLogger(__name__)

CONF_OFF_CODE = 'off_code'
CONF_OFF_CODE_RECIEVE = 'off_code_receive'
CONF_ON_CODE = 'on_code'
CONF_ON_CODE_RECIEVE = 'on_code_receive'
CONF_SYSTEMCODE = 'systemcode'
CONF_UNIT = 'unit'
CONF_UNITCODE = 'unitcode'
CONF_ECHO = 'echo'
CONF_DIMMABLE = 'dimmable'
CONF_BRIGHTNESS = 'brightness'

DEPENDENCIES = ['pilight']

COMMAND_SCHEMA = vol.Schema({
    vol.Optional(CONF_PROTOCOL): cv.string,
    vol.Optional('on'): cv.positive_int,
    vol.Optional('off'): cv.positive_int,
    vol.Optional(CONF_UNIT): cv.positive_int,
    vol.Optional(CONF_UNITCODE): cv.positive_int,
    vol.Optional(CONF_ID): vol.Any(cv.positive_int, cv.string),
    vol.Optional(CONF_STATE): cv.string,
    vol.Optional(CONF_SYSTEMCODE): cv.positive_int,
    vol.Optional(CONF_BRIGHTNESS): cv.positive_int,
}, extra=vol.ALLOW_EXTRA)

RECEIVE_SCHEMA = COMMAND_SCHEMA.extend({
    vol.Optional(CONF_ECHO): cv.boolean
})

SWITCHES_SCHEMA = vol.Schema({
    vol.Required(CONF_ON_CODE): COMMAND_SCHEMA,
    vol.Required(CONF_OFF_CODE): COMMAND_SCHEMA,
    vol.Optional(CONF_NAME): cv.string,
    vol.Optional(CONF_OFF_CODE_RECIEVE, default=[]): vol.All(cv.ensure_list,
                                                             [COMMAND_SCHEMA]),
    vol.Optional(CONF_ON_CODE_RECIEVE, default=[]): vol.All(cv.ensure_list,
                                                            [COMMAND_SCHEMA]),
    vol.Optional(CONF_DIMMABLE, default=False): cv.boolean,
    vol.Optional(CONF_BRIGHTNESS): cv.positive_int
})

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    vol.Required(CONF_DEVICES):
        vol.Schema({cv.string: SWITCHES_SCHEMA}),
})


def setup_platform(hass, config, add_devices, discovery_info=None):
    """Set up the Pilight platform."""
    devices = []

    for dev_name, properties in config.get(CONF_DEVICES).items():
        devices.append(
            PilightDimmableLight(
                hass,
                properties.get(CONF_NAME, dev_name),
                properties.get(CONF_ON_CODE),
                properties.get(CONF_OFF_CODE),
                properties.get(CONF_ON_CODE_RECIEVE),
                properties.get(CONF_OFF_CODE_RECIEVE),
                properties.get(CONF_DIMMABLE),
                properties.get(CONF_BRIGHTNESS)
            )
        )

    add_devices(devices)


class _ReceiveHandle(object):
    def __init__(self, config, echo):
        """Initialize the handle."""
        self.config_items = config.items()
        self.echo = echo

    def match(self, code):
        """Test if the received code matches the configured values.

        The received values have to be a subset of the configured options.
        """
        return self.config_items <= code.items()

    def run(self, light, turn_on):
        """Change the state of the switch."""
        light.set_state(turn_on=turn_on, send_code=self.echo)


class PilightDimmableLight(Light):
    """Representation of a Pilight light."""

    def __init__(self, hass, name, code_on, code_off, code_on_receive,
                 code_off_receive, dimmable, brightness):
        """Initialize the light."""
        self._hass = hass
        self._name = name
        self._state = False

        self._code_on = code_on
        self._code_off = code_off

        self._code_on_receive = []
        self._code_off_receive = []

        self._brightness = brightness
        self._dimmable = dimmable

        for code_list, conf in ((self._code_on_receive, code_on_receive),
                                (self._code_off_receive, code_off_receive)):
            for code in conf:
                echo = code.pop(CONF_ECHO, True)
                code_list.append(_ReceiveHandle(code, echo))

        if any(self._code_on_receive) or any(self._code_off_receive):
            hass.bus.listen(pilight.EVENT, self._handle_code)

    @property
    def name(self):
        """Get the name of the light."""
        return self._name

    @property
    def brightness(self):
        """Return the brightness"""
        return self._brightness

    @property
    def is_on(self):
        """Return true if switch is on."""
        return self._state

    @property
    def supported_features(self):
        """Flag supported features."""

        if self._dimmable:
            return SUPPORT_BRIGHTNESS
        else:
            return None

    def _handle_code(self, call):
        """Check if received code by the pilight-daemon.

        If the code matches the receive on/off codes of this switch the switch
        state is changed accordingly.
        """
        # - True if off_code/on_code is contained in received code dict, not
        #   all items have to match.
        # - Call turn on/off only once, even if more than one code is received
        if any(self._code_on_receive):
            for on_code in self._code_on_receive:
                if on_code.match(call.data):
                    on_code.run(light=self, turn_on=True)
                    break

        if any(self._code_off_receive):
            for off_code in self._code_off_receive:
                if off_code.match(call.data):
                    off_code.run(light=self, turn_on=False)
                    break

    def set_state(self, turn_on, dimlevel=15, send_code=True):
        """Set the state of the switch.

        This sets the state of the switch. If send_code is set to True, then
        it will call the pilight.send service to actually send the codes
        to the pilight daemon.
        """
        if send_code:
            if turn_on:
                code = self._code_on

                if self._dimmable:
                    code.update({'dimlevel':dimlevel})

                self._hass.services.call(pilight.DOMAIN, pilight.SERVICE_NAME,
                                         code, blocking=True)
            else:
                self._hass.services.call(pilight.DOMAIN, pilight.SERVICE_NAME,
                                         self._code_off, blocking=True)

        self._state = turn_on
        self.schedule_update_ha_state()

    def turn_on(self, **kwargs):
        """Turn the switch on by calling pilight.send service with on code."""
        
        if kwargs.get(ATTR_BRIGHTNESS):
            self._brightness = kwargs.get(ATTR_BRIGHTNESS)
        if not self._brightness:
            self._brightness = 255
#            self._brightness = kwargs.get(ATTR_BRIGHTNESS, 255)
        dimlevel = int(self._brightness / 17)

        self.set_state(turn_on=True, dimlevel=dimlevel)

    def turn_off(self, **kwargs):
        """Turn the switch on by calling pilight.send service with off code."""
        self.set_state(turn_on=False)

With a configuration example:

    - platform: pilight
  devices:
    light1:
      dimmable: true
      brightness: 200
      on_code:
        protocol: kaku_dimmer
        unit: 4
        id: 11111111
        'on': 1
      on_code_receive:
        - protocol: arctech_switch
          unit: 4
          id: 11111111
          state: 'on'
          echo: false
      off_code:
        protocol: kaku_dimmer
        unit: 4
        id: 11111111
        'off': 1
      off_code_receive:
        - protocol: arctech_switch
          unit: 4
          id: 11111111
          state: 'off'
          echo: false

Hope this works for you as well.

Mine is not working crashing HASSIO!

error:

2017-12-07 01:10:31 ERROR (SyncWorker_0) [homeassistant.util.yaml] mapping values are not allowed here
in “/config/lights.yaml”, line 27, column 17
2017-12-07 01:10:31 ERROR (MainThread) [homeassistant.bootstrap] Error loading /config/configuration.yaml: mapping values are not allowed here
in “/config/lights.yaml”, line 27, column 17

what is going wrong?

Basically we can’t really tell from the posted error, since we have no way of knowing what’s in lights.yaml and if it’s correctly formatted :frowning:

Hi Keith, first I used your pilight.py version with brightness option, that gave me the all the errors plus the error that brightness was an invalid number, plus hassIO was not to be reach in anyway so I had to hard reset my pi.
Then I thought well lets try without brightness option and did go for the initial pilight.py file, which was when I had the above mentioned errors.
Plus My other ligths went out… no MQTT nor MQTT.json lights al out. so I removed the code.
I did make an extra folder iin config and placed the pilight.py file there.
I had still the pilight settings in configurations.yaml but I guess they should remain there?!

Hope you can shine a light on this.

Hi Jay,

Not sure what you are talking about I don’t have any pilight.py files. If you mean what @drimps posted above then he will be able to help you. I’m not sure how hass.io works with external python files or if it works at all with them. And you still haven’t posted what’s in lights.yaml?

Sorry yes, you are right it is drimps file. sorry but that is the one used.

But the error is in lights.yaml? Why haven’t you posted it?