First ZigBee tests with python (bellows) forming a network controller

#1

Someone asked in an issue: https://github.com/cmetz/python-matrixio-hal/issues/2

So, the last days I played around a little with ZigBee and read myself into various topics (EM385 chip, bellows, ezsp protocol). The goal was to get bellows controlling my Osram Zigbee lamp via the Creator. I stumbled across many pitfalls. After long nights and much stracing of the ZigBeeGatway (blob) I finally found a runnable configuration. As far as I know bellows is also used in the HASS.IO ZigBee plugin, so not entirely unexciting.

Here briefly written together what my current installation status is. I’m afraid I only have a single lamp here at the start.

Caution: Connecting a device to a ZigBee network, binds the device to the controller and needs a factory reset to get it connecting to an other device. so remove it properly from the network if you just want to play around, or do know how you can factory reset it.

# Install possible missing packages
sudo apt update
sudo apt install git python3 python3-dev virtualenvwrapper build-essential

# reboot, re login or run
source /usr/share/virtualenvwrapper/virtualenvwrapper.sh

# Stop and (optional) disable malos serivce if installed
sudo systemctl stop matrixio-malos.service
sudo systemctl disable matrixio-malos.service (optional, stop it everytime on a reboot manually)

# comment out em358-program.bash to prevent a device factory reset on reboot or power cycle
# (possible an unwanted behavior in matrix init)
sudo vi /usr/share/matrixlabs/matrixio-devices/matrix-init.bash
# change
./em358-program.bash
to
# ./em358-program.bash

# create virtualenv
mkvirtualenv -p/usr/bin/python3.5 bellows

# Install bellows
pip install bellows

# "create" device database
mkdir -p ~/.config/bellows
touch ~/.config/bellows/app.db

# Export UART DEVICE
export EZSP_DEVICE=/dev/ttyS0

# run once: forming a zigbee network controller on the creator
bellows form

# permit joining of network device
bellows permit
# turn on lamp (device)
# wait for device detection

# list known devices
bellows devices

# list cluster commands on endpoint e.g.
# Endpoint = 3
# Cluster = 6 (On/Off)

# toggle light on/off
bellows zcl 7c:b0:3e:aa:00:a8:68:4e 3 6 commands

# use the toggle commands
bellows zcl 7c:b0:3e:aa:00:a8:68:4e 3 6 command toggle

# change light level
# bellows zcl {IEEE} 3 8 command move_to_level {level: 0-255} {fading-time}
bellows zcl 7c:b0:3e:aa:00:a8:68:4e 3 8 command move_to_level 100 0
bellows zcl 7c:b0:3e:aa:00:a8:68:4e 3 8 command move_to_level 255 30

# change color temperature to 4000K (osram value calculation, could also be also the direct kevlin value)
bellows zcl 7c:b0:3e:aa:00:a8:68:4e 3 768 command move_to_color_temp $(expr 1000000 / 4000) 10

# remove the device from the network
bellows zdo 7c:b0:3e:aa:00:a8:68:4e leave
# the device can now be connected to an other network

# the single bellows zcl commands are quite slow, because bellows does an init on every startup,
# if you write you on service on bellows or use HASS.IO it should be much faster.

some screenshots from bellows running in a docker container (this is why it is root):

device join:

device list:

zcl command description (on/off):

2 Likes
Zigbee bellows not working after reinstall
Zigbee and z-wave at the same time
Zwave Initial Setup errors
Zigbee bellows not working after reinstall
#2

Really nice integration @loom. Can’t wait to test this myself.

-Yoel

#3

ohhh will this work with openhab maybe?

#4

openhab is written in java, so i think they are using their own driver
i would use the Ember EZSP NCP addon from:

i did not try it, but it is discussed here:

another way could be implementing your home automation with home assistant (python) the plugin there uses bellows. but it was not working with my light and the creator, because it uses the latest released version of zigpy. Maybe i’ll play around with home assistant this weekend.

1 Like
#5

Are using you HA directly on the pi? My try vision was to have HA or openhab on a VM and having the creator mounte on the wall upstairs as a controller

#6

i think there are many different solutions, e.g.:

Single Coordinator: (easy)
run openhab / home assistant directly on the creator
(creator home assistant / zigbee coordinator) <-- ZigBee --> (devices)

multiple home assistant installations (advanced)
run two home assistant installations and connect them by an event bus: https://www.home-assistant.io/components/mqtt_eventstream/
it seems that home assistant can only work with one zigbee coordinator connected over the event bus
(main home assistant instance) <-- MQTT --> (creator home assistant/coordinator) —> (devices)

extra coordinator and (multiple) router (extra costs)
get another zigbee stick for your main host and configure it as coordinator, let the creator act as router (https://www.silabs.com/community/wireless/zigbee-and-thread/knowledge-base.entry.html/2012/07/02/what_is_the_differen-IYze)
this would allow multiple router controlled by one coordinator
(main host coordinator) <-- ZigBee --> (creator as ZigBee router) <-- ZigBee --> (devices)

own addon (eventually complex / time intense)
write your own code. eventually use the matrix ZigBeeGatway (blob) as it provides communication over network, or even better use the ZMQ / Protobuf zigbee service (Malos)
(main host) <–own addon–> (creator as coordinator)

as this is not a specific creator issue i think there are many other discussing solutions for this and you will find better suggestions in the specific communities :slight_smile: : https://community.home-assistant.io/t/remote-dongles/30353

and as always it mainly depends on your plans. if you only want to control a small area i would go for solution one. if you want to connect a larger area then i would prefer a zigbee mesh with one coordinator (eventually the creator) and multiple zigbee router.

#7

I’m currently using the last solution @loom mentioned:
Home Assistant with custom component (host) <–> creator with Malos as coordinator <–> two Ikea Tradfri bulbs

I wrote a custom home assistant component which listens for and sends Malos Zigbee messages via ZMQ/Protobuf and acts as a standard light platform.
Currently it’s rather hardcoded to match my setup, so only color temperature, brightness and on/off are supported. The python code is based on the zigbee js example: https://github.com/matrix-io/matrix-malos-zigbee/blob/master/src/js_test/test_zigbee.js.
I don’t really have experience with python so the code can be better for sure.

I hope to find the time to improve/generalize the component and add it to Home Assistant, but for those interested in the current version:

  • Your Creator should be running with the latest Malos release without the Kernel Modules

  • Drop the code below into a file in your home assistant config folder:

    custom_components/light/matrix_zigbee.py
    
  • Add a config entry to your home assistant config:

      light:
        - platform: matrix_zigbee
          host: "192.168.178.48" <- matrix creator ip
    
  • Restart Home Assistant

  • Devices already bound to the creator zigbee coordinator should be added as lights, alternatively switch them on now to join the network.


custom_components/light/matrix_zigbee.py:

import logging
import asyncio

import voluptuous as vol

# Import the device class from the component that you want to support from
from enum import Enum
import homeassistant.util.color as color_util
from homeassistant.components.light import (
    ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_FLASH, ATTR_RGB_COLOR,
    ATTR_TRANSITION, ATTR_XY_COLOR, ATTR_WHITE_VALUE, EFFECT_COLORLOOP, EFFECT_RANDOM,
    FLASH_LONG, FLASH_SHORT, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP,
    SUPPORT_EFFECT, SUPPORT_FLASH, SUPPORT_RGB_COLOR, SUPPORT_TRANSITION, SUPPORT_WHITE_VALUE,
    SUPPORT_XY_COLOR, Light, PLATFORM_SCHEMA, DOMAIN)
from homeassistant.const import CONF_HOST
import homeassistant.helpers.config_validation as cv
from homeassistant.util.async import run_callback_threadsafe

REQUIREMENTS = ['pyzmq==17.0.0b3', 'protobuf==3.3.0', 'matrix_io-proto==0.0.17']

_LOGGER = logging.getLogger(__name__)
_LOGGER.setLevel(logging.INFO)

# Validation of the user's configuration
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    vol.Required(CONF_HOST): cv.string
})

class Zigbee(object):
    def __init__(self, hass, zmq_socket, driver_pb2, comm_pb2):
        """Initialize an Zigbee proxy."""
        self._hass = hass
        self._socket = zmq_socket
        self._driver_pb2 = driver_pb2
        self._comm_pb2 = comm_pb2
    
    @asyncio.coroutine
    def ResetGateway(self):
        _LOGGER.info('Reseting the Gateway App')
        _LOGGER.info(self._comm_pb2.ZigBeeMsg.ZigBeeCmdType.keys())
        driver_config_proto = self._driver_pb2.DriverConfig()
        driver_config_proto.zigbee_message.type = self._comm_pb2.ZigBeeMsg.ZigBeeCmdType.Value("NETWORK_MGMT")
        driver_config_proto.zigbee_message.network_mgmt_cmd.type = self._comm_pb2.ZigBeeMsg.NetworkMgmtCmd.NetworkMgmtCmdTypes.Value("RESET_PROXY")
        self._socket.send(driver_config_proto.SerializeToString())
        
    @asyncio.coroutine
    def IsGatewayActive(self):
        _LOGGER.info('Checking connection with the Gateway')
        driver_config_proto = self._driver_pb2.DriverConfig()
        driver_config_proto.zigbee_message.type = self._comm_pb2.ZigBeeMsg.ZigBeeCmdType.Value("NETWORK_MGMT")
        driver_config_proto.zigbee_message.network_mgmt_cmd.type = self._comm_pb2.ZigBeeMsg.NetworkMgmtCmd.NetworkMgmtCmdTypes.Value("IS_PROXY_ACTIVE")
        self._socket.send(driver_config_proto.SerializeToString())

    @asyncio.coroutine
    def RequestNetworkStatus(self):
        _LOGGER.info('Requesting network status')
        driver_config_proto = self._driver_pb2.DriverConfig()
        driver_config_proto.zigbee_message.type = self._comm_pb2.ZigBeeMsg.ZigBeeCmdType.Value("NETWORK_MGMT")
        driver_config_proto.zigbee_message.network_mgmt_cmd.type = self._comm_pb2.ZigBeeMsg.NetworkMgmtCmd.NetworkMgmtCmdTypes.Value("NETWORK_STATUS")
        driver_config_proto.zigbee_message.network_mgmt_cmd.permit_join_params.time = 60
        self._socket.send(driver_config_proto.SerializeToString())
        return Status.WAITING_FOR_NETWORK_STATUS

    @asyncio.coroutine
    def CreateNetwork(self):
        _LOGGER.info('NO NETWORK')
        _LOGGER.info('CREATING A ZigBee Network')

        driver_config_proto = self._driver_pb2.DriverConfig()
        driver_config_proto.zigbee_message.type = self._comm_pb2.ZigBeeMsg.ZigBeeCmdType.Value("NETWORK_MGMT")
        driver_config_proto.zigbee_message.network_mgmt_cmd.type = self._comm_pb2.ZigBeeMsg.NetworkMgmtCmd.NetworkMgmtCmdTypes.Value("CREATE_NWK")
        driver_config_proto.zigbee_message.network_mgmt_cmd.permit_join_params.time = 60
        self._socket.send(driver_config_proto.SerializeToString())
        return Status.WAITING_FOR_NETWORK_STATUS

    @asyncio.coroutine
    def PermitJoin(self):
        _LOGGER.info('Permitting join')
        driver_config_proto = self._driver_pb2.DriverConfig()
        driver_config_proto.zigbee_message.type = self._comm_pb2.ZigBeeMsg.ZigBeeCmdType.Value("NETWORK_MGMT")
        driver_config_proto.zigbee_message.network_mgmt_cmd.type = self._comm_pb2.ZigBeeMsg.NetworkMgmtCmd.NetworkMgmtCmdTypes.Value("PERMIT_JOIN")
        driver_config_proto.zigbee_message.network_mgmt_cmd.permit_join_params.time = 60
        self._socket.send(driver_config_proto.SerializeToString())
        
        _LOGGER.info('Please reset your zigbee devices')
        _LOGGER.info('.. Waiting 60s for new devices')            
        return Status.WAITING_FOR_DEVICES

    @asyncio.coroutine
    def checkGatewayActive(self):
        yield from asyncio.sleep(3)
        yield from self.IsGatewayActive()

class Status(Enum):
    NONE = 1
    WAITING_FOR_DEVICES = 2
    WAITING_FOR_NETWORK_STATUS = 3
    NODES_DISCOVERED = 4

status = Status.NONE

@asyncio.coroutine
def async_setup_platform(hass, config, add_devices, discovery_info=None):
    """Setup the Matrix Creator Everloop platform."""

    import zmq
    import zmq.asyncio
    zmq.asyncio.install()
    from matrix_io.proto.malos.v1 import driver_pb2
    from matrix_io.proto.malos.v1 import comm_pb2

    zmq_host = config.get(CONF_HOST)
    zmq_base_port = 40000 + 1

    context = zmq.asyncio.Context()
    zmq_socket = context.socket(zmq.PUSH)

    _LOGGER.info("Host: %s", 'tcp://{0}'.format(zmq_host))
    zmq_socket.connect('tcp://{0}:{1}'.format(zmq_host, zmq_base_port))

    # Print errors 

    error_socket = context.socket(zmq.SUB)
    error_socket.connect('tcp://{0}:{1}'.format(zmq_host, zmq_base_port + 2))
    error_socket.subscribe('')

    @asyncio.coroutine
    def recvError():
        while True:
            message = yield from error_socket.recv()
            _LOGGER.error("Error: %s", message)

    hass.loop.create_task(recvError())

    # Ping driver

    ping_socket = context.socket(zmq.PUSH)
    ping_socket.connect('tcp://{0}:{1}'.format(zmq_host, zmq_base_port + 1))
    ping_socket.send_string('')

    @asyncio.coroutine
    def ping():
        while True:
            yield from asyncio.sleep(1)
            ping_socket.send_string('')

    hass.loop.create_task(ping())

    # Receive data messages

    message_socket = context.socket(zmq.SUB)
    message_socket.connect('tcp://{0}:{1}'.format(zmq_host, zmq_base_port + 3))
    message_socket.subscribe('')

    nodes_id = []
    gateway_up = False
    zigbee_proxy = Zigbee(hass, zmq_socket, driver_pb2, comm_pb2)

    def call_zigbee_service(fn):
        @asyncio.coroutine
        def service_fn(service):
            return fn()
        return service_fn

    def addConnectedZigbeeDevices(zig_msg):
        nodes_id = []
        for node in zig_msg.network_mgmt_cmd.connected_nodes:
            for endpoint in node.endpoints:
                for cluster in endpoint.clusters:
                    if cluster.cluster_id == 6:
                        nodes_id.append((node.node_id, endpoint.endpoint_index))

        _LOGGER.info("add %s devices", len(nodes_id))
        add_devices([ ZigbeeBulb(hass,x,y,zmq_socket,driver_pb2,comm_pb2) for x, y in nodes_id])
    
    @asyncio.coroutine
    def checkNetwork(zig_msg, networkStatusType, networkStates):
        global status

        _LOGGER.info('Network status type: %s', networkStatusType)

        if networkStatusType == networkStates.Value("NO_NETWORK"):
            yield from zigbee_proxy.CreateNetwork()
            status = Status.WAITING_FOR_NETWORK_STATUS

        elif networkStatusType == networkStates.Value("JOINED_NETWORK"):
            addConnectedZigbeeDevices(zig_msg)
            yield from zigbee_proxy.PermitJoin()
            status = Status.WAITING_FOR_DEVICES

        else:
            message = 'JOINING_NETWORK message received' if networkStatusType == networkStates.Value("JOINING_NETWORK") else 'JOINED_NETWORK_NO_PARENT' if networkStatusType == networkStates.Value("JOINED_NETWORK_NO_PARENT") else 'LEAVING_NETWORK message received' if networkStatusType ==  networkStates.Value("LEAVING_NETWORK") else None
            _LOGGER.info(message)
 
    @asyncio.coroutine
    def checkNetworkManagementType(zig_msg, networkMgmtCmdType, allNetworkMgmtCmdTypes):
        global status
        nodes_id = []
        _LOGGER.info('Check network type: %s', networkMgmtCmdType)
        _LOGGER.info('Status: %s', status)

        if networkMgmtCmdType == allNetworkMgmtCmdTypes.Value("DISCOVERY_INFO") and status == Status.WAITING_FOR_DEVICES:
            _LOGGER.info('Device found: %s', zig_msg.network_mgmt_cmd.connected_nodes)
            _LOGGER.info('Looking for nodes that have an on-off cluster.')
           
            for node in zig_msg.network_mgmt_cmd.connected_nodes:
                for endpoint in node.endpoints:
                    for cluster in endpoint.clusters:
                        if cluster.cluster_id == 6:
                            nodes_id.append((node.node_id, endpoint.endpoint_index))

            _LOGGER.info("found devices %s", nodes_id) 

            if len(nodes_id) > 0:
                status = Status.NODES_DISCOVERED
                _LOGGER.info('Nodes discovered')
            else:
                _LOGGER.warning('No devices found!')
                return

            _LOGGER.warn("add devices %s", nodes_id) 
            add_devices([ ZigbeeBulb(hass,x,y,zmq_socket,driver_pb2,comm_pb2) for x, y in nodes_id ])
        elif networkMgmtCmdType == allNetworkMgmtCmdTypes.Value("IS_PROXY_ACTIVE"):
            if zig_msg.network_mgmt_cmd.is_proxy_active:
                _LOGGER.info('Gateway connected')
                gateway_up = True
            elif not status == Status.RESETTING:
                yield from ResetGateway()
                _LOGGER.info('Waiting 3 sec ....')
                hass.loop.create_task(zigbee_proxy.checkGatewayActive())
                status = Status.RESETTING
                return
            else:
                _LOGGER.warning('Gateway reset failed')
                return

            yield from zigbee_proxy.RequestNetworkStatus()
            status = Status.WAITING_FOR_NETWORK_STATUS

        elif networkMgmtCmdType == allNetworkMgmtCmdTypes.Value("NETWORK_STATUS") and status == Status.WAITING_FOR_NETWORK_STATUS:
            _LOGGER.info('NETWORK STATUS: ')
            status = Status.NONE
            yield from checkNetwork(zig_msg, zig_msg.network_mgmt_cmd.network_status.type, comm_pb2.ZigBeeMsg.NetworkMgmtCmd.NetworkStatus.Status)

    @asyncio.coroutine
    def recvMessage():
        while True:
            message = yield from message_socket.recv()
            zig_msg = comm_pb2.ZigBeeMsg.FromString(message)

            _LOGGER.info("Message: %s", zig_msg)
            _LOGGER.info("network mgmt: %s", zig_msg.type == comm_pb2.ZigBeeMsg.ZigBeeCmdType.Value("NETWORK_MGMT"))

            if zig_msg.type == comm_pb2.ZigBeeMsg.ZigBeeCmdType.Value("NETWORK_MGMT"):
                yield from checkNetworkManagementType(zig_msg, zig_msg.network_mgmt_cmd.type, comm_pb2.ZigBeeMsg.NetworkMgmtCmd.NetworkMgmtCmdTypes)

    hass.loop.create_task(recvMessage())
    hass.services.async_register(DOMAIN, "reset_gateway", call_zigbee_service(zigbee_proxy.ResetGateway))
    hass.services.async_register(DOMAIN, 'isGatewayActive', call_zigbee_service(zigbee_proxy.IsGatewayActive))
    hass.services.async_register(DOMAIN, 'requestNetworkStatus', call_zigbee_service(zigbee_proxy.RequestNetworkStatus))
    hass.services.async_register(DOMAIN, 'createNetwork', call_zigbee_service(zigbee_proxy.CreateNetwork))
    hass.services.async_register(DOMAIN, 'permitJoin', call_zigbee_service(zigbee_proxy.PermitJoin))


    # Create a new driver config
    driver_config_proto = driver_pb2.DriverConfig()

    driver_config_proto.delay_between_updates = 1.0
    driver_config_proto.timeout_after_last_ping = 1.0

    zmq_socket.send(driver_config_proto.SerializeToString())

    yield from zigbee_proxy.ResetGateway()

    # Trigger zigbee setup
    hass.loop.create_task(zigbee_proxy.checkGatewayActive())

    return True

class ZigbeeBulb(Light):

    def __init__(self, hass, nodeId, endointIndex, zmq_socket, driver_pb2, comm_pb2):
        """Initialize an AwesomeLight."""
        self._hass = hass
        self._nodeId = nodeId
        self._endpointIndex = endointIndex
        self._socket = zmq_socket
        self._driver_pb2 = driver_pb2
        self._comm_pb2 = comm_pb2
        self._name = str(nodeId)
        self._state = None
        self._brightness = 50
        self._colorTemp = 340

    @property
    def name(self):
        """Return the display name of this light."""
        return self._name

    #@property
    #def should_poll(self) -> bool:
    #    """No polling needed for a demo light."""
    #    return False

    @property
    def brightness(self):
        """Return the brightness of the light.
        This method is optional. Removing it indicates to Home Assistant
        that brightness is not supported for this light.
        """
        return self._brightness

    @property
    def white_value(self):
        """Return the white value of this light between 0..255."""
        return self._colorTemp

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

    @property
    def supported_features(self):
        """Flag supported features."""
        # TODO: SUPPORT_FLASH = 8, SUPPORT_TRANSITION = 32
        return (SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP)

    def turn_on(self, **kwargs):
        """Instruct the light to turn on.
        You can skip the brightness part if your light does not support
        brightness control.
        """

        _LOGGER.warning("kwargs: %s", kwargs)
        self._state = True
        if ATTR_BRIGHTNESS in kwargs:
            self._brightness = kwargs[ATTR_BRIGHTNESS]
            
            config = self._driver_pb2.DriverConfig()

            config.zigbee_message.type = self._comm_pb2.ZigBeeMsg.ZigBeeCmdType.Value("ZCL")
            config.zigbee_message.zcl_cmd.type = self._comm_pb2.ZigBeeMsg.ZCLCmd.ZCLCmdType.Value("LEVEL")
            config.zigbee_message.zcl_cmd.level_cmd.type = self._comm_pb2.ZigBeeMsg.ZCLCmd.LevelCmd.ZCLLevelCmdType.Value("MOVE_TO_LEVEL")
            config.zigbee_message.zcl_cmd.level_cmd.move_to_level_params.level = self._brightness
            config.zigbee_message.zcl_cmd.level_cmd.move_to_level_params.transition_time = 10
            config.zigbee_message.zcl_cmd.node_id = self._nodeId
            config.zigbee_message.zcl_cmd.endpoint_index = self._endpointIndex
            
            run_callback_threadsafe(
            self._hass.loop, self._socket.send, config.SerializeToString()).result()

        elif ATTR_COLOR_TEMP in kwargs:
            self._colorTemp = kwargs[ATTR_COLOR_TEMP]
            
            config = self._driver_pb2.DriverConfig()

            config.zigbee_message.type = self._comm_pb2.ZigBeeMsg.ZigBeeCmdType.Value("ZCL")
            config.zigbee_message.zcl_cmd.type = self._comm_pb2.ZigBeeMsg.ZCLCmd.ZCLCmdType.Value("COLOR_CONTROL")
            config.zigbee_message.zcl_cmd.colorcontrol_cmd.type = self._comm_pb2.ZigBeeMsg.ZCLCmd.ColorControlCmd.ZCLColorControlCmdType.Value("MOVETOCOLORTEMP")
            config.zigbee_message.zcl_cmd.colorcontrol_cmd.movetocolortemp_params.color_temperature = self._colorTemp
            config.zigbee_message.zcl_cmd.colorcontrol_cmd.movetocolortemp_params.transition_time = 10
            config.zigbee_message.zcl_cmd.node_id = self._nodeId
            config.zigbee_message.zcl_cmd.endpoint_index = self._endpointIndex
            
            run_callback_threadsafe(
            self._hass.loop, self._socket.send, config.SerializeToString()).result()

        else:
        
            config = self._driver_pb2.DriverConfig()

            config.zigbee_message.type = self._comm_pb2.ZigBeeMsg.ZigBeeCmdType.Value("ZCL")
            config.zigbee_message.zcl_cmd.type = self._comm_pb2.ZigBeeMsg.ZCLCmd.ZCLCmdType.Value("ON_OFF")
            config.zigbee_message.zcl_cmd.onoff_cmd.type = self._comm_pb2.ZigBeeMsg.ZCLCmd.OnOffCmd.ZCLOnOffCmdType.Value("ON")
            config.zigbee_message.zcl_cmd.node_id = self._nodeId
            config.zigbee_message.zcl_cmd.endpoint_index = self._endpointIndex
            
            run_callback_threadsafe(
            self._hass.loop, self._socket.send, config.SerializeToString()).result()


    def turn_off(self, **kwargs):
        """Instruct the light to turn off."""
        self._state = False
        
        config = self._driver_pb2.DriverConfig()

        config.zigbee_message.type = self._comm_pb2.ZigBeeMsg.ZigBeeCmdType.Value("ZCL")
        config.zigbee_message.zcl_cmd.type = self._comm_pb2.ZigBeeMsg.ZCLCmd.ZCLCmdType.Value("ON_OFF")
        config.zigbee_message.zcl_cmd.onoff_cmd.type = self._comm_pb2.ZigBeeMsg.ZCLCmd.OnOffCmd.ZCLOnOffCmdType.Value("OFF")
        config.zigbee_message.zcl_cmd.node_id = self._nodeId
        config.zigbee_message.zcl_cmd.endpoint_index = self._endpointIndex
        
        run_callback_threadsafe(
        self._hass.loop, self._socket.send, config.SerializeToString()).result()


    def update(self):
        """Fetch new state data for this light.
        This is the only method that should fetch new data for Home Assistant.
        """
        # TODO:
        #self._light.update()
        #self._state = self._light.is_on()
        #self._brightness = self._light.brightness

    def set(self, socket, whiteValue, intensity):
        """Sets all of the LEDS to a given rgbw value"""
        config = self._driver_pb2.DriverConfig()

        config.zigbee_message.type = self._comm_pb2.ZigBeeMsg.ZigBeeCmdType.Value("ZCL")
        config.zigbee_message.zcl_cmd.type = self._comm_pb2.ZigBeeMsg.ZCLCmd.ZCLCmdType.Value("ON_OFF")
        config.zigbee_message.zcl_cmd.onoff_cmd.type = self._comm_pb2.ZigBeeMsg.ZCLCmd.OnOffCmd.ZCLOnOffCmdType.Value("TOGGLE")
        config.zigbee_message.zcl_cmd.node_id = self._nodeId
        config.zigbee_message.zcl_cmd.endpoint_index = self._endpointIndex
        
        run_callback_threadsafe(
        self._hass.loop, self._socket.send, config.SerializeToString()).result()
2 Likes
#8

Home assistant and ZigBee:

zha:
  usb_path: /dev/ttyS0
  database_path: /srv/homeassistant/zigbee.db
  • restarted “hass”
  • permitted joining of devices (DevTools / Services):

  • the result is:

works nice :slight_smile:

1 Like
#9

Trying your sample, I am getting:
(bellows) pi@raspberrypi:~ mkdir -p ~/.config/bellows (bellows) pi@raspberrypi:~ touch ~/.config/bellows/app.db
(bellows) pi@raspberrypi:~ export EZSP_DEVICE=/dev/ttyS0 (bellows) pi@raspberrypi:~ bellows form
sys:1: RuntimeWarning: coroutine ‘form’ was never awaited

Any ideas on how to fix?

#10

probably the last update of bellows broke something. can you try the next step (joining a device)? if this not helps you have two options forming a network manually with the zigbeegateway, or downgrade bellows. let me know if it worked maybe i can fix this issue in the code and create a pull request.

#11

bellows join
PAN not provided, scanning channels 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26…
No joinable networks found
error: Task was destroyed but it is pending!
error: task: <Task pending coro=<Gateway._send_task() running at /home/pi/.virtualenvs/bellows/lib/python3.5/site-packages/bellows/uart.py:159> wait_for=>
Exception ignored in: <coroutine object Gateway._send_task at 0x75d94390>
Traceback (most recent call last):
File “/home/pi/.virtualenvs/bellows/lib/python3.5/site-packages/bellows/uart.py”, line 159, in _send_task
File “/usr/lib/python3.5/asyncio/queues.py”, line 169, in get
File “/usr/lib/python3.5/asyncio/futures.py”, line 246, in cancel
File “/usr/lib/python3.5/asyncio/futures.py”, line 261, in _schedule_callbacks
File “/usr/lib/python3.5/asyncio/base_events.py”, line 572, in call_soon
File “/usr/lib/python3.5/asyncio/base_events.py”, line 357, in _check_closed
RuntimeError: Event loop is closed

#12

Just a little more info if this helps

(bellows) pi@raspberrypi:~/bellows $ bellows -v debug -d /dev/ttyS0 info
debug: Using selector: EpollSelector
debug: Connected. Resetting.
debug: Sending: b’1ac038bc7e’
debug: RSTACK Version: 2 Reason: RESET_SOFTWARE frame: b’c1020b0a527e’
debug: Send command version
debug: Sending: b’004221a850ed2c7e’
debug: Data frame: b’0142a1a8502835e54a947e’
debug: Sending: b’8160597e’
debug: Application frame 0 (version) received
debug: Configuring…
debug: Send command setConfigurationValue
debug: Sending: b’7d314321fb582815c3277e’
debug: Data frame: b’1243a1fb54fb737e’
debug: Sending: b’82503a7e’
debug: Application frame 83 (setConfigurationValue) received
debug: Send command setConfigurationValue
debug: Sending: b’224021fb592f15226f7e’
debug: Data frame: b’2340a1fb54c6107e’
debug: Sending: b’83401b7e’
debug: Application frame 83 (setConfigurationValue) received
debug: Send command setConfigurationValue
debug: Sending: b’334121fb792b15a2d77e’
debug: Data frame: b’3441a1fb54d32a7e’
debug: Sending: b’8430fc7e’
debug: Application frame 83 (setConfigurationValue) received
debug: Send command setConfigurationValue
debug: Sending: b’444621fb55d51534da7e’
debug: Data frame: b’4546a1fb5435d07e’
debug: Sending: b’8520dd7e’
debug: Application frame 83 (setConfigurationValue) received
debug: Send command networkInit
debug: Sending: b’554721bfb6607e’
debug: Data frame: b’5647a1bfc7db3e7e’
debug: Sending: b’8610be7e’
debug: Application frame 23 (networkInit) received
debug: Send command getEui64
debug: Sending: b’6644218e7d5e777e’
debug: Data frame: b’6744a18e258712bf59fb47257a627e’
debug: Sending: b’87009f7e’
debug: Application frame 38 (getEui64) received
[00:0d:6f:00:0d:07:ad:71]
debug: Send command getNodeId
debug: Sending: b’7745218f34757e’
debug: Data frame: b’7045a18faad59b777e’
debug: Sending: b’8070787e’
debug: Application frame 39 (getNodeId) received
[65534]
debug: Send command networkState
debug: Sending: b’004a21b0cca07e’
debug: Data frame: b’014aa1b05433ba7e’
debug: Sending: b’8160597e’
debug: Application frame 24 (networkState) received
[<EmberNetworkStatus.NO_NETWORK: 0>]
debug: Send command getNetworkParameters
debug: Sending: b’7d314b2180a0d07e’
debug: Data frame: b’124ba180c7e715b259944a25aa556db6634e275412ce678bfdc61dce7e’
debug: Sending: b’82503a7e’
debug: Application frame 40 (getNetworkParameters) received
Exception in callback SerialTransport._read_ready()
handle: <Handle SerialTransport._read_ready()>
Traceback (most recent call last):
File “/usr/lib/python3.5/asyncio/events.py”, line 126, in _run
self._callback(*self._args)
File “/home/pi/.virtualenvs/bellows/lib/python3.5/site-packages/serial_asyncio/init.py”, line 106, in _read_ready
self._protocol.data_received(data)
File “/home/pi/.virtualenvs/bellows/lib/python3.5/site-packages/bellows/uart.py”, line 64, in data_received
self.frame_received(frame)
File “/home/pi/.virtualenvs/bellows/lib/python3.5/site-packages/bellows/uart.py”, line 76, in frame_received
self.data_frame_received(data)
File “/home/pi/.virtualenvs/bellows/lib/python3.5/site-packages/bellows/uart.py”, line 97, in data_frame_received
self._application.frame_received(self.randomize(data[1:-3]))
File “/home/pi/.virtualenvs/bellows/lib/python3.5/site-packages/bellows/ezsp.py”, line 170, in frame_received
result, data = t.deserialize(data, schema)
File “/home/pi/.virtualenvs/bellows/lib/python3.5/site-packages/bellows/types/init.py”, line 9, in deserialize
value, data = type
.deserialize(data)
File “/home/pi/.virtualenvs/bellows/lib/python3.5/site-packages/bellows/types/basic.py”, line 10, in deserialize
r = cls(int.from_bytes(data[:cls._size], ‘little’, signed=cls._signed))
File “/usr/lib/python3.5/enum.py”, line 241, in call
return cls.new(cls, value)
File “/usr/lib/python3.5/enum.py”, line 476, in new
raise ValueError("%r is not a valid %s" % (value, cls.name))
ValueError: 205 is not a valid EmberNodeType

#13

ah sorry i did not mean to join a network, instead allow other devices to join your network on the matrix:

bellows permit
#14

same issue:
sys:1: RuntimeWarning: coroutine ‘permit’ was never awaited
Maybe my version of python?
(bellows) pi@raspberrypi:~ $ python --version
Python 3.5.3

#15

no i think it is a bug in bellows, i’ll do some tests with the latest version to find out what is causing the problems.

#16

is ther a way to downgrade for test?

#17

the version bevore was also bugy in the pypi release. i’m getting the same error, wait one sec. i;m trying to find the problem.

#18

can you modify the folling file:

/home/pi/.virtualenvs/bellows/lib/python3.5/site-packages/bellows/cli/application.py
# change:

async def form(ctx, database, channel, pan_id, extended_pan_id):
# to
def form(ctx, database, channel, pan_id, extended_pan_id):

# and 
async def permit(ctx, database, duration_s):
# to
def permit(ctx, database, duration_s):

and try:

bellows form
# and then
bellows permit
# switch your device to join on
#19

How do you renenter the virtualenv ?
I keep recreating it

#20

to renter the virtual env if you followed the example abolve use

workon bellows
# and this to leave it;
deactivate