Friday, September 8, 2017

fixing Hue persistence using async IO in Rust / Futures

Intro


I have two Philips Hue lamps and mostly like them when  they are in the very warm white (2000k) color temperature setting.
The problem is that the setting does not persist when the lamps are switched using a physical power switch. To solve this I wrote the Hue Persistence program last year.
This program runs on the LAN and basically polls and stores the lamp settings and when it it detects a lamp going from off to on it restores the last setting.

After using it for a few weeks it became clear that there is one big problem with it: the Philips Hue hub sometimes accepts a connection from my program but then does not respond, yet keeps the connection open.
As the program is written using synchronous IO, this causes the program to hang forever, and it doesn't work anymore.

Solution


The Hue Persistence program uses the Rust Philips Hue library which using Hyper as a HTTP client to talk to the Philips Hue hub via it's restful API.
This library still uses Hyper 0.10 which does not provide async IO yet.

Recently there has been a lot of work for asynchronous IO in rust using Tokio and Futures. Version 0.11 of Hyper uses this.

First thing I had to do is port the Rust Philips Hue library to use Hyper 0.11 and thus async IO. This turned out to be quite an undertaking. The result of this can be found on the hyper_0.11 branch of my fork of the library. It is still a bit ugly, but works.

So far I've found using Futures for async IO to have the following issues:

  • error handling can be very confusing; error types need to be the same in future composition and you have to explicitly do error type conversions to make things work
  • future composition results in complex types, returning to Box<Future> helps a bit, but it still can be rather confusing (I didn't try the not yet released impl Trait solution)
  • it's not so obvious which future composition methods to use when, docs are sparse
  • branching (if, ...) inside futures is tedious again because of types having to match perfectly
In the end though I got the satisfaction of a working async library, after which I updated the Hue Persistence app to have a timeout on it's requests. This again turned out to be really tedious.
I had to use select2 with a sleep instead of tokio_timer::Timer::timeout() because it was just impossible to get the types to work for the error types when using tokio_timer::Timer, due to the TimeoutError containing Future type as a containing type in the signature of timeout().

Conclusion


My lamps are behaving fine now!

Async IO in Rust using Futures/tokio is interesting but at this point it still feels like "getting the types right" is somewhat in the way of normal coding productivity.
Some ergonomic improvements/language support could surely be used.
To be revisited in the Future (pun intended ;) ).