Taking a crack at UPB platform development

@bbrendon: It’s my understanding (from https://home-assistant.io/developers/add_new_platform) that what’s needed is a “platform” script for UPB. If I only wanted to control UPB devices, I think I could use just about any of these as a starting point: https://github.com/home-assistant/home-assistant/tree/master/homeassistant/components/switch. That actually looks pretty straightforward.

But I not only want to control UPB devices, I also want those devices to provide events to HA when they are physically changed (primarily when someone presses a button on a UPB device). I don’t want to poll for the status (and in fact, UPB doesn’t really allow for that anyway). Z-Wave (which is the other switch type I have on my HA system) does this (I can control the state of a Z-Wave switch from HA, and if the Z-Wave switch is manually changed, HA is immediately updated).

This is what I’m not clear on: It does not appear to me that the “platform” scripts handle this “event” feedback. So does that mean I also need to write a platform script here (https://github.com/home-assistant/home-assistant/tree/master/homeassistant/components/sensor)?

What I don’t want to do is develop a switch platform script for UPB, and then find out that I should have done something completely differently in order to also support the “event” feedback.

Also, X10 may be a good starting point (although only if it provides the “event” feedback to HA). But even so, I can’t find a switch platform script for X10 (it’s not on github that I can see).

@eric24 what did the guys in the developer chat room say?

Sorry, but I’m not familiar with that chat room. I’ll take a look if you can point me in the right direction.

Try here:

#dev room.

@eric24 Any progress?

I wrote a platform that works. It’s a bit hackey because it uses a node application to send and interpret UPB messages. The main reason I did this exploration into the bowels of HASS is because I wanted my UPB lights to appear in HASS as lights which with my other method, they did not. It’s an improvement over what I had working before. Hopefully someone can help improve on this.

Thanks, I’ll give this a try. But I’m running Hassio, so I won’t be able to install the upb-cli. I guess I’ll switch to Hassbian, then I’ll be able to install the upb-cli ?

Ahh yes. That’s a good point. I’m not sure how that’ll work. I just run on straight linux.

I’m guessing you can modify the docker container somehow but I’m not sure.

Also, here is how to make a hassio addon but it doesn’t look easy at first glance.

Are you running in a Python Virtual Environment?

Yes

Thanks! I’m VERY new to all of this but it looks like Hassbian might work then…

I’ll work on it this weekend!

@eric24 @fabaff …and anyone else?
Maybe one of you can help. There is what I have so far: https://pastebin.com/czWwKbhZ

I’m trying to add to my platform where it listens on the serial port for updates and then applies them to the entity’s state. I’m stuck at line 180. I get the data from the serial port, decode it into a light/brightness level. But I can’t figure out how to tell HASS about the new values. If I do something like hass.states.async_set('light.upb_office_lamp', 'on'), then it gets over-written by (I think) the state machine.

Thanks.

Progress so far. I spun up a Hassbian install on a Pi 3b and migrated all my Hassio configurations over. I installed Nodejs and upb-cli and that seem to have gone okay. I setup my UPB PIM with a USB to Serial adapter and configured everything to ttyUSB0. I installed your upb.py lights in my custom_components folder and added my devices to configuration.yaml. Everything looks good, but nothing is working. I’m testing the upb-cli with command line commands and even thought it’s not throwing any errors, I still have not be able to get it to active or deactivate any of my upb devices. It’s a start, and I know I’m getting close!!!

I’m having to regroup. My installation of Node is having issues with finding the bindings for the serial ports. This is why I can’t get upb-cli to send commands. It can formulate the commands just fine.

I’m not savvy enough with Linux to figure out what’s going on. So I was going to take another route and slightly change the upb.py to get the command and use python-serial to send them. BUT, now I’m running into a problem with importing serial into upb.py

Going to have to dig more… :frowning:

Here is my modified version of your upb.py since I could not get the upb-cli serial to work. Still no additional progress, but I got it working with your one-way hack:

"""
Support for UPB lights.

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

Improvements:
- ditch upb-cli and do everything natively
- listen for status changes in real time on the network
- optionally poll all the devices in the network every x minutes for their
  status
- instead of recreating the light config in configuration.yaml, read the config
  from the upstart export file

Changes:
- only use upb-cli to generate command codes
- send commands to serial port with send_serial
- hard code path to upb-cli with PATH var

"""
import serial
import logging
import time
from subprocess import check_output, CalledProcessError, STDOUT

import voluptuous as vol

from homeassistant.components.light import (
    ATTR_BRIGHTNESS, ATTR_BRIGHTNESS_PCT, SUPPORT_BRIGHTNESS, Light,
    PLATFORM_SCHEMA)

from homeassistant.const import (CONF_NAME, CONF_ID, CONF_DEVICES, CONF_BRIGHTNESS)

import homeassistant.helpers.config_validation as cv

# Home Assistant depends on 3rd party packages for API specific code.
REQUIREMENTS = ['pyserial==3.4']

serial_port = ''
upb_net = ''

_LOGGER = logging.getLogger(__name__)

#DOMAIN = 'UPB'

CONF_SERIAL_PORT = 'serial_port'
CONF_UPB_NET = 'upb_net'
PATH = '/opt/nodejs/bin/'


PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    vol.Required(CONF_DEVICES): vol.All(cv.ensure_list, [
        {
            vol.Required(CONF_ID): cv.string,
            vol.Required(CONF_NAME): cv.string,
            vol.Optional(CONF_BRIGHTNESS): cv.string,
        }
    ]),
    vol.Required(CONF_SERIAL_PORT): cv.string,
    vol.Required(CONF_UPB_NET): cv.string,

})


# CONFIG_SCHEMA = vol.Schema({
#     DOMAIN: vol.Schema({
#         vol.Optional(CONF_SERIAL_PORT, default=''): cv.string,
#     }),
# }, extra=vol.ALLOW_EXTRA)


def dump(obj):
    for attr in dir(obj):
        if hasattr( obj, attr ):
            print( "obj.%s = %s" % (attr, getattr(obj, attr)))

# def get_unit_status(code):
#     """Get on/off status for given unit"""
#     output = check_output('heyu onstate ' + code, shell=True)
#     return int(output.decode('utf-8')[0])    

def send_serial(command):
    port = serial.Serial(serial_port, 4800, timeout=1)
    port.write(str.encode(chr(20) + str(command) + chr(13)))
    time.sleep(0.1)
    port.close()
    return "serial command sent"
        
def upb_command(command):
    """Execute UPB command and check output"""
    #print("config: " + config[CONF_SERIAL_PORT])
    # print("serial: "+ serial_port)
    # print("upbnet: "+ upb_net)
    #return check_output(['/opt/nodejs/bin/upb-cli','-p',serial_port,'-n',upb_net] + command.split(' '), stderr=STDOUT)
    command_code = check_output([PATH + 'upb-cli','-n',upb_net] + command.split(' '), stderr=STDOUT)
    output = send_serial(command_code.decode('ascii'))
    #print(output)
    
def upb_version():
    return check_output([PATH + 'upb-cli','-V'], stderr=STDOUT)

def setup_platform(hass, config, add_devices, discovery_info=None):
    """Set up the UPB Light platform"""
    
    global serial_port, upb_net

    serial_port = config.get(CONF_SERIAL_PORT)
    upb_net = config.get(CONF_UPB_NET)
    
    try:
        upb_version()
    except CalledProcessError as err:
        _LOGGER.error(err.output)
        return False

    add_devices(UPBLight(light) for light in config[CONF_DEVICES])

class UPBLight(Light):
    """Representation of an UPB Light"""

    def __init__(self, light):
        """Initialize an UPB Light"""
        self._light = light
        self._name = light['name']
        self._id = light['id']
        self._brightness = None
        self._state = None

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

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

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

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

    def turn_on(self, **kwargs):
        """Instruct the light to turn on"""   
        
        bright_pct = int((kwargs.get(ATTR_BRIGHTNESS,255)/255)*100)

        #print(str(self._id)) ## This is the id value from the configutaion.yaml
        #upb_command('-n 99 -i 83 -t device -c goto -l 100 --send -p /dev/ttyS1' )
        #upb_command(' -i ' + self._id + ' -t device -c goto -l ' + str(bright_pct) + ' --send' )
        upb_command(' -i ' + self._id + ' -t device -c goto -l ' + str(bright_pct))
        
        #print(kwargs)
        self._state = True

    def turn_off(self, **kwargs):
        """Instruct the light to turn off"""
        #upb_command(' -i ' + self._id + ' -t device -c goto -l 0 --send' )
        #time.sleep(1)
        #upb_command(' -i ' + self._id + ' -t device -c goto -l 0 --send' )
        upb_command(' -i ' + self._id + ' -t device -c deactivate 0' )
        self._state = False

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

PS: I also enabled the brightness parameter that was missing.

1 Like

If anyone cares, I got UPB to work bi-directional (for the most part). I couldn’t figure out how to do it properly so I have the upb component reading from temp files that a daemon updates as a separate process. It’s not a simple drop in solution like it should be. It requires some general understanding of linux so if anyone wants it I’ll PM you my nasty code.

I’m interested, however I won’t have much time to play this next week because my car is out of commission and I’m working on getting it fixed. The upper oil pan cracked where the oil pressure sensor goes.

Sent. Happy hacking :slight_smile:

Hi Brendon,

I was exploring the upb-cli app in github when I noticed that you mentioned to the author that you got it working with HA. Great.
I was considering about doing the same, but since I don’t have any idea of how to integrate it with Home Assistant, I was thinking about using MQTT for communicating with it. If you have an MQTT broker you can define MQTT lights and use the python paho library for receiving the commands from HA to upb-cli and returning status from upb-cli to HA. I currently use MQTT with some Sonoff switches and I’m super pleased with the quality of the status reporting (after manual changes at the switch). I’m thinking about scrapping my zigbee lights, becuase they don’t report status after turning them on/off locally.

That’s a great idea but I actually thought I was going to be able to make a native component after I got the hass->UPB working. My hack works well enough and seems to be only limited by features/bugs in upb-cli.

All this component really needs is some guidance from a real developer to get it “unhacked”, but I haven’t been able to find someone.