The Internet of Things at Scale with F# and Actors

This post is in the FSAdvent series of blog posts. See more at https://sergeytihon.wordpress.com/2014/11/24/f-advent-calendar-in-english-2014/

It’s Christmas time which means only one thing – It’s socially acceptable to have enough lights flashing at all hours of the day to make the runway staff at Heathrow jealous. But how do I go about managing potentially thousands of chains of fairy lights all attached to my house?

First I’m going to start with a warning. If you plan to do this yourself, be very careful 230V AC is enough to cause permanent long lasting damage to you or your surroundings.

This sounds like an Internet of Things project to me but what is the Internet of Things? Well, answers to this question in the past have included such responses as “A security nightmare”, “Pointless” and “Does this mean my toaster can like my Facebook status?”. It’s essentially the interconnectedness of as much as possible to create a smarter living environment. But where’s the fun in that? I want to control my Christmas tree from my phone.

So, how are we going to manage to write something like this with the intention of running it at massive scale? Thankfully some really smart people solved that problem in the 1970s with the introduction of the actor model, which basically says that we can treat each of our entities in the world as a completely independent item. If we want to change it’s state, then we send it a message telling it to change it’s state. Actors can do a couple of other things as well, things like spawning new actors and sending messages to other actors.

So in our case how can we think about our Christmas lights using the actor model? Well we’ve got 2 options depending on our usage.  We could have multiple strings of lights all on a single parent like a Christmas tree. We could also choose to treat each of our Arduinos as an independent actor with a single string of lights attached. We’ll be choosing the second because I’ll be looking to control lights which aren’t necessarily on a tree and it’s also easy.

I’ve always found it easier to understand a full system when I see a crudely drawn diagram on the back of a napkin and so to address this, here’s a full diagram. The rest of this post just focuses on the actors part.

IoTDiagram

To control Christmas lights, we only need a single direction of data, from server to client. Let’s use websockets to do the job through the help of Pusher. Pusher basically gives us a really simple API on top of a raw websocket. It’s also really cheap (free for 100,000 messages per day). They also provide a web hook API for some useful bits and pieces, most notably channels having users in or users leaving a channel. As we get unlimited channels, we’ll represent each of our Christmas lights as a channel with the same name as the actor instance.

I think that’s enough of the cruft surrounding the application, let’s get into the real meat of the application, most importantly our actors. Our actors will only exist in memory when the lights are known to be online and available to the internet, otherwise they’ll be unloaded. We’ll be storing our state in Azure table storage, because it’s A) Highly scalable and B) REALLY cheap (you might be spotting a pattern in my purchasing for hobby projects).

I usually find it easiest to model the messages that I’ll be sending first so let’s do that now, we’ll use a discriminated union to represent our message types.

type ChristmasLightsMessage =
| ClientDisconnected
| ToggleOnOff
| ToggleFlashPattern

We’ll be wanting to store some state within our actors so that we’re able to tell whether it’s switched on or flashing.

type LightsSet =
    { [<PartitionKey>] ApplicationId : string
      [<RowKey>] LightsId : string
      IsAvailable : bool
      ToggleStatus : ToggleStatus }

Let’s now create our actor which maintains our state and handles our messages but wrap it in a reusable function capable of spawning new ones with new names. Our actors will be created using the great Cricket library.

let pusher = PusherServer.Pusher("","","")
let storageAccount = CloudStorageAccount.Parse(STORAGE_ACCOUNT_KEY)

let tableClient = storageAccount.CreateCloudTableClient ()
let spawn_iotactor lightsid =
    let spawn_actor actorName state =
        actor {
            name actorName
            body (let rec loop (state,etag) =
                      messageHandler {
                          let! msg = Message.receive ()
                          match msg with
                          | ToggleOnOff -> let state = { state with ToggledOn = not state.ToggledOn }
                                           let! res = (state,etag) |> Replace |> inTableAsync tableClient "lights"
                                           pusher.Trigger(actorName, "toggleOnOff", "{}")
                                           return! loop (state,res.Etag)
                          | ClientDisconnected -> let state = { state with IsAvailable = false; ToggledOn = false }
                                                  let! res = (state,etag) |> Replace |> inTableAsync tableClient "lights"
                                                  let! ctx = Message.context ()
                                                  ctx <-- Shutdown
                          | ToggleFlashPattern -> return! loop (state,etag)
                      }
                  loop state)
        }

Now we’ll need a way of interfacing with our actor system, to do this I’ll create a couple of simple Web API endpoints to toggle our lights and flash pattern on or off by simply sending our message types to our actors.

[<RoutePrefix("lights")>]
type LightsController () =
    inherit ApiController ()

    [<HttpPut>]
    [<Route("{lightsid}/toggleonoff")>]
    member this.ToggleOnOff(lightsid:string) =
        !!lightsid <-- ToggleOnOff
        this.Ok()

    [<HttpPut>]
    [<Route("{lightsid}/toggleflashpattern")>]
    member this.ToggleFlashPattern(lightsid:string) =
        !!lightsid <-- ToggleFlashPattern
        this.Ok()

For the level of application which I’ve shown here, the actor model is probably verging on overkill but in cases where we’ve been using remote nodes with attached sensors where nodes and sensors have loads of associated state, the actor model has let us build applications quicker and easier.

Advertisements

2 comments

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s