Ambient lighting controlled by Siri

In this post I will walk you through the moving parts required to build an ambient light system. The light will be controlled by any of the following:

  • Siri (via HomeKit) – voice activation and configuration (power state, brightness, colour)
  • HomeKit – software activation and configuration (power state, brightness, colour)
  • Touch sensor – hardware activation and configuration (power state, brightness)

This is not a step-by-step guide but rather a description of the components involved and how the whole thing is put together.

Architecture

The image bellow describes the overall architecture depicting the MQTT server as the central integration point.

The communication pattern is designed to adhere to the MQTT Smart Home Architecture, described here.

Architecture diagram illustrating the ambient light system

Ambient lights architecture

Hardware

The Arduino is the controlling unit and uses the ESP8266 chip to achieve wireless connectivity. There will be only one power supply (5v 10a) powering the Arduino, ESP8266 and the LED strip. Therefore, a 3.3v voltage regulator is necessary for the ESP8266 chip, a capacitor should be used together with the regulator to smooth out any ripples.

The WS2812B strip supports addressing every LED individually, however, this functionality was not used and the strip is controlled as a whole. The strip is by far the most power hungry of all components and special care should be taken to pair it with a suitable power supply.  Keep in mind that each LED is actually composed out of 3 coloured LEDs (red, green, blue) that draw up to 60 milliamps at maximum brightness white.

The capacitive touch switch is used to manually turn the strip on or off, or to adjust the brightness level. A short sensor touch will cause the power to be toggled on/off while keeping the sensor touched will cause the brightness to gradually increase.

The Raspberry Pi does not play an active role in the hardware setup. It is used to host various software programs and can easily be replaced by a desktop, laptop or NAS.

Software

Controlling the ambient light system is performed by a suite of software:

  • LEDStripController – runs on the Arduino and is responsible for communicating with the LED strips, publishing and responding to MQTT messages, and responding to the touch sensor inputs.
  • Espduino – firmware and software for the ESP01 chip to avoid working with AT commands.
  • Mosquitto – MQTT server, running on the Raspberry Pi.
  • Homekit2mqtt – HomeKit to MQTT bridge, running on the Raspberry Pi.

If you are going for a similar setup, keep in mind that Espduino is discontinued! I recommend using an ESP12 instead of the Arduino+Esp01 combination.

In case of network loss ESP01 automatically tries to reconnect to the wifi, however there are times when the chip stops responding entirely. To mitigate this issue a “watchdog” was set in place to hard reboot the chip (power cycle) when it stops responding.

In the general usage pattern a burst of messages are sent to the controlling pair (ESP01+Arduino) and processing those messages is prioritised over updating the hardware (actually turning the LED strip on and configuring each LED according to the chosen colour and brightness). This debounce is controlled by the HARDWARE_UPDATE_DEBOUNCE parameter.

Touching the touch sensor for a short period of time triggers a power toggle, while keeping the sensor touched causes it to cycle the brightness in steps. It takes 10 steps to go from the lowest to the highest brightness level.

Assembly

Here are a few pictures from the assembly process

Assembly process #1

Assembly process #2

Assembly process #3

Demo

MQTT on QNAP via Moquette

Message Queuing Telemetry Transport (MQTT) is a lightweight pub/sub messaging protocol designed for minimal bandwidth and device resource requirements. This makes it ideal for IoT projects, where you generally have limited processing power and memory space (read more about MQTT).

There are several MQTT broker/server implementations out there (hopefully exhaustive list), however most of then cannot be ran on my QNAP TS221 Network Attached Storage because it uses a custom version of Linux and it is run by an ARM processor.

On the other hand, Moquette is a MQTT broker written in Java, so I gave it a try.

  1. Install the Java Runtime Environment for ARM – for some reason I could not find it in the App Center, so I had to download the package from here (dead link) and install it via the Install Manually button.
  2. SSH into the QNAP (guide)
  3. Follow the steps listed in the Moquette repo
    1. wget https://bintray.com/artifact/download/andsel/generic/distribution-0.8-bundle-tar.tar.gz –no-check-certificate
    2. tar xvfz distribution-0.8-bundle-tar.tar.gz
    3. By default Moquette uses port 8080 for WebSocket which is also used by the QNAP administration interface, so I changed the Moquette port to 9090 by navigating to /config and setting websocket_port to 9090 in moquette.conf
    4. navigate to /bin and run moquette.sh
  4. By now you should have a working MQTT broker, you can test it with this online tool.
  5. Optionally you can configure your moquette.sh to be run at startup

Arduino – DHT22 temperature and humidity sensor driver

DHT22/AM2302  is an inexpensive temperature and relative humidity sensor. The sensor, which can be bought on ebay, implements a digital communication protocol by pulling high or low on a data line. The protocol is described here and it has three states:

  1. transmission start – initiated by a host and acknowledged by the sensor
  2. data transfer – binary transmission
  3. transmission end – performed by the sensor

Setup

The communication protocol instructs that the data line needs to always be kept high using a pull up resistor (this is because the sensor will pull low on the line to transmit data). However, in this specific implementation, an external pull up resistor is not needed because the driver makes usage of the internal pull up resistor of the Arduino pin.

The cables need to be connected as follows:

  • pin1 = VDD
  • pin2 = data
  • pin3 = not used
  • pin4 = GNDarduino_dht22

Usage

  • First you need to download the driver source code from here or you can browse it here.
  • Next, copy the unzipped contents to your Arduino libraries directory in a subdirectory named DHTSensor.
  • Restart your IDE

You are now ready to do a reading

As it can be observed from the example, the temperature of the measurement result can be interpreted in Celsius, Fahrenheit or Kelvin degrees, while the humidity is percentual.

Furthermore, the sensor can also be powered by a digital pin using the following construct

Keep in mind that a sensor measurement will take around a second and during this time the execution is not yielded back (your board will stop processing anything else). The one second is due to how the sensor is engineered to communicate and the non-yielding execution is due to the implementation of the driver.

Disclaimer

Implementing the driver was performed as an educational project and it comes with no guarantees. You are free to use and modify it in any way it makes sense for you.

Autofac – None of the constructors found can be invoked

The bug

A common pattern used when working with Autofac (or any other IoC containers) is to register multiple implementations of an interface, then resolve them all and call a method on each implementation (e.g.: an IEventHandler interface that you use notify all registered handlers that something happened).

Take the following example:

Trying to resolve all implementors of IHaveDeferredEnumerable via .Resolve<IEnumerable<IHaveDeferredEnumerable>>() will throw

None of the constructors found with ‘Autofac.Core.Activators.Reflection.DefaultConstructorFinder’ on type ‘Autofac.Test.Scenarios.ScannedAssembly.HasDeferredEnumerable+<Get>d__0’ can be invoked with the available services and parameters:
Cannot resolve parameter ‘Int32 <>1__state’ of constructor ‘Void .ctor(Int32)’.

However, trying to resolve a single implementation of the interface via .Resolve<IHaveDeferredEnumerable>() will work as expected. This means that HasDeferredEnumerable can be built independently, however for some reasons it cannot be constructed when trying to get all implementations of IHaveDeferredEnumerable.

The journey

Studying the error message further reveals some strange behavior, Autofac is complaining that the type 'Autofac.Test.Scenarios.ScannedAssembly.HasDeferredEnumerable+<Get>d__0' cannot be resolved because of some 'Int32 <>1__state' parameter required in a 'Void .ctor(Int32)' constructor. Totally weird, as I am indirectly asking for the type 'Autofac.Test.Scenarios.ScannedAssembly.HasDeferredEnumerable' which only has the default parameter-less constructor, weird.

To be studied and understood, a problem must be first isolated, therefore, I stripped all other code doing container work and was only left with IHaveDeferredEnumerableHasDeferredEnumerable, and the container registration cb.RegisterAssemblyTypes(ScenarioAssembly).AsImplementedInterfaces();

To get even better isolation, I changed the container registration from registering all the types found in my test assembly (RegisterAssemblyTypes) with cb.RegisterType<HasDeferredEnumerable>().As<IHaveDeferredEnumerable>(). Now everything worked as expected, I was able to get the single implementation via .Resolve<IHaveDeferredEnumerable>() and all implementations via .Resolve<IEnumerable<IHaveDeferredEnumerable>>(). It was now obvious that the problem occurred sometime during type registration and only when bulk registering types via the RegisterAssemblyTypes method.

Since Autofac is an open source project I checked out the repository, wrote a failing test and step-by-step debugged my way through the type registration process. This is how I found out that a concrete implementation was discovered for IEnumerable<IHaveDeferredEnumerable>, even though there is none in my scanned assembly.

Since this voodoo magic needs to be cleared out, I fired up dotPeek and decompiled my assembly, to find the following:

My HasDeferredEnumerable class has been enriched with a private class, which implements IEnumerable<IHaveDeferredEnumerable> and not surprisingly requires an integer to be constructed. How did that class ended-up in there without me typing it you ask? Well, the .NET compiler does in fact changes your code and generates new types as needed. In this particular case, it was because of the deferred execution in the Get() method – the compiler generates a helper state machine class to handle your deferred execution.

The fix

Fixing the issue was fairly trivial as the helper class is decorated with the [CompilerGenerated] attribute and it was just a matter of filtering out these compiler generated classes when running bulk type discovery. You can find the accepted pull request here and it will probably only end-up in version 4.0.0.

If you cannot wait until version 4.0.0 you can either remove the deferred execution (remove yield return/break) from the classes affected by the bug, or just make sure to manually register them into the container.

How to debug NuGet package scripts

You can step by step debug your Init.ps1, Install.ps1, or Uninstall.ps1 using NuGetDebugTools. It won’t be the best debugging experience you’ll ever have in your life, but it will surely get the job done!

At first you need to open the Package Manager Console in Visual Studio and:

  1. install the NuGetDebugTools package
  2. add the debugger
  3. set breakpoints for the scripts  you want to debug

Now you’re all set and you can begin installing your own package. Depending on which breakpoints you’ve set with the Set-PSBreakpoint command, when a break point gets hit you will be prompted with a dialog. This is where you will be entering your commands debugging commands (e.g. s for step into, v for step over)

 

NuGetDebugTools_cmdprompt

Dialog for entering debugging commands

The basic commands that you can enter are

  • s – StepInto (Step to the next statement into functions, scripts, etc)
  • v – StepOver (Step to the next statement over functions, scripts, etc)
  • o – StepOut (Step out of the current function, script, etc)
  • c – Continue (Continue operation, also on Cancel or empty)
  • q – Quit (Stop operation and exit the debugger)
  • ?, h – Help (Write this help message)
  • k – CallStack (Write call stack, aka Get-PSCallStack)
  • K – CallStackDetailed (Write detailed call stack using Format-List)

And you will be receiving feedback and additional information in the Package Manager Console

NuGetDebugTools_output
You can find an exhaustive list of commands and more detailed information on the NuGetDebugTools github page