Resolving Home Assistant upgrade errors in custom Python Components

Resolving Home Assistant upgrade errors in custom Python Components

Upgrading to Home Assistant v0.69.0 broke some of my custom components that use the MQTT Python module. The reason for this is a small change in the module API. This post explains how to fix your custom components to work with the latest version of Home Assistant!

A complete Guide to Motion Lighting in Smart Homes - Introducing AppDaemon

I have previously proposed a motion lighting implementation and discussed some of the problems Home Assistant’s YAML based configuration creates for people who are used to the flexibility of fully fledged, battle-tested, object-oriented programming languages. Some automation concepts actually require object instances per automation entity to track automation-internal state for different physical devices.

Introducing AppDaemon - a Python-based way to define automations and interact with your smart home in Home Assistant. AppDaemon allows you to define Python “apps” that can be reused with different parameters.

Motion Activated Lighting

Consider the surprisingly complex example of motion activated lights in a smart home environment. It’s a simple concept to understand, however, it can cause a number of subtle problems that are difficult and messy to solve without object-oriented programming ideas and reusability of code. What we are doing is creating a virtual motion light that uses physical devices as inputs and outputs. I have attempted to solve these issues before and it was less than pretty. To refresh our memory, motion lights have the following requirements (R):

  1. turn on when motion is detected
  2. turn off when no motion is detected after some timeout
  3. Do not interfere with manually activated lights (tricky and less than obvious)

That last one can be broken into the following two requirements:

  • 3.1 A light that is already on should not be affected by time outs.
  • 3.2 A light that is switched on within the time-out period should have its timer cancelled, and therefore stay on.

That last point is less obvious, but very important to handle as the following scenario should demonstrate:

Say you switched on your lights manually because you are reading a book in the lounge. You get up to grab a drink and your motion sensor is triggered. This — in turn — starts the sequence of events which ultimately ends in the lights turning off (due to motion sensor timeout), leaving you sitting in the dark. Very awkward. This happened even though you switched them on manually before the motion sensor was activated.

In other words, we do not want motion lighting to override manually controlled lights. If the light is already on, then appropriate lighting has already been taken care of and is not a problem we need to solve in automations!

We can achieve this by implementing this simple logic:

When motion is detected, turn light on if it is off (R1) and only turn it off (R2) if it was turned on by the motion automation (R3.1) (and nothing else). If the light was controlled by another entity during the time out period (R3.2), then cancel the timer and do not send any further commands to the light—as to not alter its state. (R3.1). Next time motion is triggered, the light is already on and is therefore ignored.

The motion light comes back into action when the light is turned off by something else (manual or another automation).

The previous implementation attempt was messy because I did not know of the existence of AppDaemon. It allows automations to be defined as Python objects, which essentially allows us to create virtual devices defined by Python classes. Each motion sensor would get its own MotionLight object which contains some internal state, tracked individually **for each motion sensor**.

In essence, a MotionLight is a virtual device that takes a set of motion sensors as input, and a set of lights as output. The MotionLight itself is not a physical entity and only exists within the Python environment.

This allows us to implement very specific automations. The only way to achieve this in HA’s YAML configuration is by duplicating the automation for different motion sensors.1

Python code follows:

import appdaemon.plugins.hass.hassapi as hass

#
# App to turn lights on when motion detected then off again after a delay
#
# Use with constrints to activate only for the hours of darkness
#
# Args:
#
# sensor: binary sensor to use as trigger
# entity_on : entity to turn on when detecting motion, can be a light, script, scene or anything else that can be turned on
# entity_off : entity to turn off when detecting motion, can be a light, script or anything else that can be turned off. Can also be a scene which will be turned on
# delay: amount of time after turning on to turn off again. If not specified defaults to 60 seconds.
#


class MotionLights(hass.Hass):
  handle = None
  isOn = False
  delay = 60# default delay
  def initialize(self):

    self.handle = None

    if "delay" in self.args:
      self.delay = self.args["delay"]
    # Check some Params

    # Subscribe to sensors - change this to passed in RF code and MQTT topic subscription.
    if "sensor" in self.args:
      self.listen_state(self.motion, self.args["sensor"])
    else:
      self.log("No sensor specified, doing nothing")



  def motion(self, entity, attribute, old, new, kwargs):
    if new == "on":
        state = self.get_state(self.args["entity_on"])
        self.log("state is {}".format(state))
        if state == "off":
          if "entity_on" in self.args:
            self.log("Motion detected: turning {} on and starting timer for {} seconds".format(self.args["entity_on"], self.args["delay"]))
            self.turn_on(self.args["entity_on"])
            self.isOn = True

          self.cancel_timer(self.handle)
          self.handle = self.run_in(self.light_off, self.delay)
        else:
            self.log("Entity is already switched on. Motion trigger ignored.")

  def light_off(self, kwargs):
    self.log("isOn {}".format(self.isOn))
    if "entity_off" in self.args:
        if self.isOn:
            self.log("Turning {} off".format(self.args["entity_off"]))
            self.turn_off(self.args["entity_off"])
            self.isOn = False


  def cancel(self):
    self.cancel_timer(self.handle)

And this is how you configure it inside AppDaemon’s config file:

motion_lights:
  module: motion_lights
  class: MotionLights
  sensor: input_boolean.sense_motion
  entity_on: light.living_room_floor_lamp
  entity_off: light.living_room_floor_lamp
  delay: 5

  1. Or implementing complex templates… not worth it, given a python script will be way more flexible and maintainable. ↩︎

Maven Project Design Guidelines

There are some items to keep in mind when creating a maven build script for your application:

  • A maven script should not change source code at build time. If you are using maven filters, then this is probably the case!
  • Once checked out, a maven application should build without errors, first try. It should not require specific set ups.
  • A maven project should not contain generated code. Code must be generated in a separate maven project, published to Nexus (artifact store) and then imported into your project as a dependency. For example, say you have a module that generates a service contract from a wsdl file. This generated code is not checked into version control (should not!). On a different machine, the maven build will fail initially due to compile errors. One would have to run the code generator, and then run the maven build for the build to pass. This violates point number 2.
  • Never mix business logic and configuration in the same artifact. Environment specific configuration should go into a config module.
  • EJB libraries must not be bundled into the applications whose services they expose. This is due to versioning. A change in the application that does not affect the API’s external interface, should not result in a version bump of the EJB library. In other words, the library version should only change when the API changes. Clients using the EJB library do not care about application-internal changes, which is why it is such bad practise to bundle the library with the application. (Though it does make sense on first thought).

Thanks to Tor for sharing his knowledge and giving me these pointers! Will come in handy.

EJBs

Funky Shit Explained in Simple Terms — Today: EJB Libraries

EJBs provide a Java specific way for two Java applications to talk to one another. From your application’s perspective it does not matter whether they are running on the same machine or on different hosts.

If Java server A has to interact with server B, it can inject server B’s EJB library and use it as if it were a local object on server A. This mechanism is not a protocol like HTTP because it is not universal. EJBs are specific to the server on which they are running. JBoss EJBs are not compatible with Weblogic EJBs for example.

Continue reading EJBs

Pagination