State

Stateful Things

Thing state is a central concept in Merle. Thing state refers to the software state of Thing, which reflects the hardware state of Thing. Let's look at the system messages a Thing will implement to manage state.

CmdInit

CmdInit is the first call into Thing so Thing state is initialized here. The system generates CmdInit automatically when instantiating a Thing. On the return of CmdInit, Thing may get a GetState request, so it's important to fully initialize Thing's state in CmdInit.

CmdRun

CmdRun is Thing's main loop. In CmdRun, Thing will keep software state synchronized with hardware state, and will broadcast any state updates to listening clients.

GetState/ReplyState

GetState returns Thing's state in a ReplyState message. Listening clients will send GetState and expect a ReplyState to initialize their version of Thing's state.

User Interface State

Thing's user interface reflects Thing's current state. The UI is served up as a Single-page application (SPA) web page, which opens a WebSocket back to Thing. The UI requests Thing's state with GetState and stores the results of ReplyState in the UI state. As Thing's state changes, Thing will send the UI update messages with the state changes. In response, the UI will update its state. Likewise, if the user makes a state change at the UI, a message is sent to Thing to update its state (and to any other clients to Thing).

// Example JavaScript to open WebSocket back to

// Thing to maintain state between Thing and UI


conn = new WebSocket("{{.WebSocket}}")


conn.onopen = function(evt) {

conn.send(JSON.stringify({Msg: "_GetState"}))

}


conn.onmessage = function(evt) {

var msg = JSON.parse(evt.data)


switch(msg.Msg) {

case "_ReplyState":

refresh(msg)

break

case "Update":

refresh(msg)

break

}

}

Thing Prime State

Thing Prime is a proxy for Thing and therefore state is maintained between the two. Thing Prime opens a WebSocket back to Thing (just like the UI above) and requests GetState and stores the results of ReplyState. Thing Prime does not get a CmdInit or a CmdRun, so it relies on ReplyState and subsequent state update messages to stay synchronized with Thing.

Since Thing and Thing Prime are built from the same code base, the only additional message to handle is ReplyState for Thing Prime, since Thing already handles CmdInit, CmdRun, GetState, and other messages necessary to maintain state between Thing and its UI.

Example message handlers from examples/relays/relays.go :


func (r *Relays) Subscribers() merle.Subscribers {

return merle.Subscribers{

merle.CmdRun: r.run, // Thing only

merle.GetState: r.getState, // Thing and Thing Prime

merle.ReplyState: r.saveState, // Thing Prime only

"Click": r.sendClick,

}

}

State Implementation

It is convienent to use Thing's type struct (the Thinger) as the container for Thing's state. Just include a Msg member and export any other state members (with an uppercase leading letter). Then the whole type struct can be passed in p.Marshal() to form the JSON response.

Here is an example Thing struct with two state variables and a Msg string so the struct can be turned into a JSON message:


type thing struct { // A Thinger

Msg string

StateVar0 int

StateVar1 bool

// non-exported members

}


func (t *thing) init(p *merle.Packet) {

t.StateVar0 = 42

t.StateVar1 = true

}


func (t *thing) getState(p *merle.Packet) {

t.Msg = merle.ReplyState

p.Marshal(t).Reply()

}


Will send JSON message:


{

"Msg": "_ReplyState",

"StateVar0": 42,

"StateVar1": true,

}

State Protection

Thing state may be written to and read from different but concurrent Go functions, so synchronization is needed to ensure there are no data races in accesses to Thing state.

Thing's main loop (CmdRun) is a Go function. The message handlers are Go functions; therefore care must be taken to enforce synchronization of read and write state access between concurrent Go functions. The simplest method is to use a mutual exclusion lock (mutex) to protect read and write state accesses. The mutex is a (non-exported) member of Thing's type struct.


type thing struct { // A Thinger

Msg string

StateVar0 int

StateVar1 bool

sync.Mutex

}


The synchronization rules are:

  1. Hold the lock on the mutex when reading state data.

  2. Hold the lock on the mutex when writing state data.

  3. Don't hold the lock when calling Packet.Broadcast() or Packet.Reply().


For example, our GetState example is now:


func (t *thing) getState(p *merle.Packet) {

t.Lock()

t.Msg = merle.ReplyState

p.Marshal(t) // Reading from state while mutex lock held

t.Unlock()

p.Reply()

}

State API

Thing state is available on all Things by adding /state to Thing's URL. This is the instantaneous state of Thing; the browser must be refreshed to get the state again.

External programs can get Thing state using curl or similar. The output is in JSON format:


$ curl https://linode.merliot.org/state

{

"Msg": "_ReplyState",

"Relays": {

"Id": "00_16_3e_14_3f_69",

"Online": true,

"States": [

false,

true,

false,

false

]

},

"Sensors": {

"Id": "dc_a6_32_7a_a6_d0",

"Online": true,

"Temp": 70

},

"SetPoint": 68

}