AppDaemon, Harmony, and emulated hue solution

I’ve always been disappointed with the integration with harmony and home assistant. The source of my disappointment has been with the harmony hub itself and how it behaves.

When using the physical harmony remote, activating an activity will turn off other activities. That means whatever control we use in home assistant will need to monitor state changes that come from the physical remote. Also, the only way to properly have this behavior represented in home assistant is with a input_select. Unfortunately, input_selects do not work with emulated hue.

The solution I came up with is to treat a series of toggle buttons as “radio buttons”. If you are unfamiliar with radio buttons, they are buttons where only 1 button can be on in a series of buttons or all buttons can be off.

I came up with a solution that sort of worked using automations. This required me to have 4 automations, and 1 script for each input _boolean. Also, I had to create a sensor to monitor all the input_booleans states. To say it was cumbersome is an understatement. Adding a new activity was painful.

So I came up with this AppDaemon solution. Hopefully this will help someone else.

with your current config, create input_booleans that match up with activities. Skip the PowerOff activity.

Activities in config:

Activities
  30947237 - TV
  24089909 - PS4
  24103421 - Xbox One
  -1 - PowerOff

create the following input booleans:

tv:
  initial: off
ps4:
  initial: off
xbox_one:
  initial: off

Then use the following code for an app:
import appdaemon.appapi as appapi
import os

HARMONY_REMOTE = "remote.harmony_hub"
HARMONY_CONFIG = r"/config/harmony_{0}.conf".format(HARMONY_REMOTE.split(".")[-1])
DEBUG = "DEBUG"

class HarmonyBase(object):
    def __init__(self, line):
        items = [ s.strip() for s in line.split(" - ") ]
        self.id = ""
        self.name = ""
        if len(items) == 2:
            self.id, self.name = items
            
    def _is_valid(self):return True if self.id and self.name else False
    is_valid = property(_is_valid, None)

class HarmonyActivity(HarmonyBase):
    def __init__(self, line):
        HarmonyBase.__init__(self, line)
        self._bool_name = self.name.replace(" ", "_").lower() # replace spaces with underscore and make it lowercase to match home assistant.
        
    def _is_power(self): return self.name == "PowerOff"
    is_power = property(_is_power, None)
    def _get_boolname(self, sep): return "input_boolean{0}{1}".format(sep, self._bool_name) if not self._is_power() else ""
    def _get_entity(self): return self._get_boolname(".")
    entity = property(_get_entity, None)
    def _get_path(self): return self._get_boolname(".")
    path = property(_get_path, None)
        
class HarmonyDevice(HarmonyBase):
    def __init__(self, line, commands):
        HarmonyBase.__init__(self, line)
        self.commands = commands
        
class HarmonyConfig(object):
    def __init__(self, config_file, remote):
        with open(config_file, 'r') as f:
            content = f.readlines()
        
        self.remote = remote
        self.activities = []
        self.power_off = None
        self.devices = []
        self._current_section = ""
        self._current_sub_section = ""
        self._content = {}
        self._parse_content(content)
        
    def _is_valid(self):
        # Verifies all information is good from the parsed file
        valid_activities = all(activity.is_valid for activity in self.activities)
        valid_devices = all(device.is_valid for device in self.devices)
        #return ( valid_activities and valid_devices and self.power_off.is_valid )
        return self.power_off.is_valid
    is_valid = property(_is_valid, None)    
        
    def _parse_content(self, content):
        for line in content:
            meat = line.strip()
            whitespace = len(line)-len(line.lstrip(' '))
            if meat:
                if whitespace == 0:
                    self._add_section(meat) # add make an activities or device container
                elif whitespace == 2:
                    self._add_to_section(meat) # add the activity or device to the correct container.
                elif whitespace == 4:
                    self._add_command(meat) # add the command to the correct activity or device.
                else:
                    # shouldn't get here, ignore.
                    continue 
            else:
                # empty line, ignore.
                continue
                
        for section_name, section in self._content.items():
            if "Activities" in section_name:
                for line, commands in section.items():
                    activity = HarmonyActivity(line)
                    if activity.is_power:
                        self.power_off = activity # we found the power off activity
                    else:
                        self.activities.append(activity) # we found something other than power off.
                
            if "Device" in section_name:
                for line, commands in section.items():
                    self.devices.append(HarmonyDevice(line, commands)) # add the device to the object.
                
    def _has_curent_section(self): return self._current_section and self._current_section in self._content.keys()
    def _has_current_sub_section(self):
        if self._has_curent_section(): # if we have the current section in out content
            return self._current_sub_section and self._current_sub_section in self._content[self._current_section].keys() # if we have the current sub section.
        else:
            return False # we don't have the current section.
           
    def _add_section(self, section):
        self._current_section = section
        self._content[section] = {}
        
    def _add_to_section(self, sub_section):
        if self._has_curent_section(): # if we have the current section in out content
            self._current_sub_section = sub_section # update the current sub section
            self._content[self._current_section][self._current_sub_section] = [] # make a list of commands
    
    def _add_command(self, command):
        if self._has_current_sub_section():
            self._content[self._current_section][self._current_sub_section].append(command) # add the command.

class ManageActivities(appapi.AppDaemon):
    def initialize(self):
        self.harmony = harmony = HarmonyConfig(HARMONY_CONFIG, HARMONY_REMOTE)
        if harmony.is_valid: #make the buttons work if we have a valid config, otherwise don't.        
            self.log("Valid Config: {0}".format(HARMONY_CONFIG), level="INFO")
            #listeners for each activity input_boolean.
            for activity in harmony.activities:
                self.log("listening to: {0}".format(activity.entity), level=DEBUG)
                self.listen_state(self.track_activity_toggle, entity = activity.entity)
                
            #listener for the state of the harmony activity.
            self.listen_state(self.track_harmony_activity, entity = harmony.remote, attribute = "current_activity")
        else:
            self.error("Invalid Config! {0}".format(HARMONY_CONFIG))

    def _get_stated_activities(self, current_activity, state):
        """ get all activities but the current one that match the provided state """
        return [ activity for activity in self.harmony.activities if activity.entity != current_activity and self.get_state(activity.entity) == state ]
        
    def get_active_activities(self, current_activity): 
        """ gets all activities that are on """
        return self._get_stated_activities(current_activity, "on") #get all activities that are on
        
    def get_inactive_activities(self, current_activity):
        """ gets all activities that are off, may not be needed. """
        return self._get_stated_activities(current_activity, "off") #get all activities that are off
        
    def get_activity_from_entity(self, which):
        """ gets the provided activity from the entity id """
        for activity in self.harmony.activities:
            if activity.entity == which:
                return activity
        return None
        
    def get_activity_from_friendly_name(self, name):
        """ gets the provided activity from the friendly name from harmony """
        for activity in self.harmony.activities:
            if activity.name == name:
                return activity
        return None
        
    def track_harmony_activity(self, entity, attribute, old, new, kwargs):
        """ track the toggle button for this activity"""
        activity = self.get_activity_from_friendly_name(new)
        if activity: #if we have an activity
            if self.get_state(activity.entity) == "off":
                self.turn_on(activity.entity)
        
    def track_activity_toggle(self, entity, attribute, old, new, kwargs):
        """ tracks an activities toggle button. """
        self.log("{0} -> {2}".format(entity, old, new), level=DEBUG)
        if old == "off" and new == "on":
            # Turning toggled activity on
            self.turn_on_harmony_activity(entity) # Comment this out if you are using the self.sun_down()
            # Uncomment this section to add other automations when the activity is turned on.
            # if self.sun_down():
                # # if the sun is below the horizon.
                # self.turn_on( "light.living_room_d_level", brightness = 255 ) # turn on living room light to full
                # # turn on the harmony activity and sync the buttons with the activities states.
                # self.turn_on_harmony_activity(entity) 
            # else:
                # # turn on the harmony activity and sync the buttons with the activities states.
                # self.turn_on_harmony_activity(entity) 
                
        elif old == "on" and new == "off":
            # Turning toggled activity off.  
            #   This will not actually turn off the other activities because the harmony remote will do that natively.
            #   This is the whole reason this needed to be handled this way.
            activities = self.get_active_activities(entity) # get any other toggles that are on.
            if len(activities) == 0: # if no other activities are currently on...
                self._turn_on_harmony_activity(self.harmony.power_off) #turn off all activities
        else:
            # we shouldn't ever get here because the states should always be on & off or off & on.
            self.error("State change was unexpected!")
                
    def turn_on_harmony_activity(self, which):
        activity = self.get_activity_from_entity(which) # get the current activity to turn on.
        if activity: # if we have one...
            self._turn_on_harmony_activity(activity) #turn on.  made a separate function to play around.
            # Due to delays from the harmony polling, this log may not look correct when output.
            self.log(self.get_state("remote.living_room", "all"), level=DEBUG) 
            
            # Find all the toggles that are on and sync them up with the physical harmony remote.
            for active_activity in self.get_active_activities(activity.entity): #iterate through toggles that are currently on
                self.log("Syncing {0}".format(active_activity.entity), level=DEBUG)
                self.turn_off(active_activity.entity)

    def _turn_on_harmony_activity(self, activity):
        self.turn_on("remote.living_room", activity = activity.name)
        #self.call_service("remote/turn_on", entity_id = "remote/living_room", activity = activity.id) # turn on living room light to full

It’s not perfect, but it creates a series of toggles that behave like radio buttons. The buttons update with the state of the harmony remote if its used outside homeassistant.

wow, thats extremely complicated to switch 4 input booleans.
is that all that it does?

it’s so complicated because of the harmony remote. If you don’t have the automations set up correctly with booleans, the harmony remote can get into endless on/off cycling. its really annoying.

I’d love to know an easier way. I guess I could have just used home assistant to handle all the activities but then the remote wouldn’t work well with all the devices. I’m trying to get the best from all worlds.

The problems with harmony, input_booleans, and homeassistant are the following scenarios:

Scenario 1:

I’m currently watching TV.
I turn on the Xbox using an input_boolean.

  • Harmony changes from TV to Xbox, Xbox is on, TV is off
  • Home assistant has TV on and Xbox On

everything beyond this point is out of sync.

Scenario 2:

I am currently watching TV.
I turn on the Xbox from the Harmony Remote.

  • Harmony changes from TV to Xbox, Xbox is on, TV is off
  • Home assistant has TV on and Xbox off

Everything beyond this point is out of sync.

So you could use simple automations to handle turning off other booleans when the desired boolean is turned on. The problem with this is that you don’t want to turn the system off, just the booleans state. This is where all the complication comes from.

When I set mine up I used automations, it was really simple and all works perfectly, so I think the ‘problem’ is of your making. All you need to do is switch activity and then the state changes to reflect it, nothing for HA to get confused about. It’s just an ‘activate’ command for each which you can tidy up to be a drop down if you really want.

Only way I could see you having any problems is if you haven’t set the activities up properly on the Harmony, are you new to it?

The whole purpose of this was to get away from the dropdown so that it could be used with emulated hue.

you say:

Also, the only way to properly have this behavior represented in home assistant is with a input_select. Unfortunately, input_selects do not work with emulated hue

so you create an app for that with “radio buttons”

thats an quit easy app like:

init():
  for input_boolean in self.arg["input_booleans"]:
    listen_state(self.setradiobutton",input_boolean)

def set_setradiobutton(...):
  for input_boolean in self.arg["input_booleans"]:
    if input_boolean == entity:
      self.turn_on(entity)
    else:
      self.turn_of(input_boolean)
  do stuff based on the activated setting ....

you can set some extra checks to know if it is coming from the remote or not, but i cant see anything that complicated as you have written.
and i must say, respect for the code. its way more complicated as whatever i would have come up with for that solution. but i think you didnt use AD to the foolest and made things very overcomplicated.

That’s what this does. I use a list of objects generated from the config file instead of a static string that needs to be updated manually each time an activity is added. Over half of this was just reading the config and making objects that can be expanded on in the future. The meat of the code is ~80 lines…

I could rewrite it for you so that it has a static simple list of entity ids.

Why do you want to use emulated_hue in this? If it’s to use Alexa to switch activities, you’d be much better to use the harmony smart skill directly. Even so, it should work fine just triggering the automations, that’s how I used it before the smart skill came out.

The only problem I had was as all emulated_hue devices are seen as lights, if you say something like ”turn off lights” it’ll trigger all the activities too.

As stupid as it sounds, I hated saying “alexa ask harmony to turn on x”.

It’s easier to each people “alexa, turn off xbox” than to teach them to say “alexa, ask harmony to turn off xbox”.

Relatives don’t know what harmony is, so the phrase is odd to them and they don’t remember it. If I’m not home, I’d be bombarded with texts “what’s the phase for the tv?”.

Your not using the blue smart skill, you’re using the old red one. No need for triggers anymore.

What’s this? When did that come out?

oke, i just saw a simple task and a simple app and then saw your code and didnt understand why it was so complicated.
i cant really see an appstructure in it also. (as i see simple apps)
if i add something to my HA config, then an extra line to my apps.yaml is no problem (i rather have that i dont need to add at to my HA config, because that needs a restart)
i would never considering complicating things so much to avoid adding an extra line to my apps.yaml (but mine is 1300 lines anyway :wink: )

i guess im more for keep it simple.
my AD app to show youtube search on HAdashboard by saying “alexa, ask AD to search on youtube for …” is almost less complicated then your app.

like i said, the only thing i can say about your code is: i think it could be a bit more simple :wink:
still respect!

Ages ago in the US, more recently elsewhere. I posted the release on here to let anyone know who might have missed it.

Assume you’re capable of going into skills and searching for ”hamony” :slight_smile:

i also did programm my skill/intents pre smart skill possibilties :wink:
im afraid i have to start learning all over, and recreate everything i have created in AD :wink:
im glad that i still have emulated hue for most stuff :wink:

sometimes things are going so fast that when you finally have created a satifying app, its time to rewrite it whith the new possibilities. :wink:

Yeah, i was trying to make this “the least amount of files to add an activity”. Currently with this method, you only need to add an input boolean.

o boy, if i would go that way i would need to complicate my apps extremely :wink:
dont go to far with that.
AD 3.0 is way more integrated into HA. could be that you find that lots of work suddenly is obsolete :wink:

For what he wants it’s all redundant now, no need to add activities to HA at all. Just the state should pick up anything you’d want for any automations with no extra needed.

I would have rather imported a harmony module than put in effort towards parsing that file. I even tried looking though the documents for something like that and found nothing. My google foo isn’t great though.

I will probably keep this functionality for the dashboard. I’d rather have buttons than a dropdown any day on a touch panel.