Packets and Messages

Packet

A packet is the basic unit of communication in Merle. A packet is a container for a JSON-encoded message.

Thing sends and receives packets on Thing's message bus.

Message

A message in Merle is defined with a Go struct. The Go struct must have a Msg string field. Msg is the message type, and should be unique for each message type in your Thing message space. Additional fields and sub-structures are defined per-message and must conform to Go's encoding/json package rules for data structures. The message is JSON encoded when stored in a packet.


type MyMsg struct {

Msg string // message type; all messages must have this field

... // additional fields

}

Only exported fields will be considered when JSON-encoding/decoding, so First-Letter-Capitalize fields to export per Go convention.

Note: Merle system messages are prefixed with "_" (e.g. "_CmdInit").

Receiving Messages

To receive a message, Thing subscribes to the message type. Thing subscribes to messages with the Subscribers() method, associating a message type with a handler. Example, call myHandler() for any received packets containing "MyMsg" message:


func (t *thing) Subscribers() merle.Subscribers {

return merle.Subscribers{

"MyMsg": t.myHandler,

...

}

}


On packet receipt, the contained message type (Msg) is matched against the subscribers list. If a match, the matching handler is called, passing the packet to the handler. Continuing with the example:


func (t *thing) myHandler(p *Packet) {

// do something

}


To extract the message contained within the packet, use packet.Unmarshal():


func (t *thing) myHandler(p *Packet) {

var msg MyMsg

p.Unmarshal(&msg)

// msg = {Msg: "MyMsg", ...}

}


Default Match

A "default" entry in the subscribers list will catch any packets that don't match any of the other entries. Similarly to the default in a switch-case statement.


func (t *thing) Subscribers() merle.Subscribers {

return merle.Subscribers{

...

"default": t.defaultHandler,

}

}


Drop Packet

To drop a packet, use nil for the message handler in the subscribers list.


func (t *thing) Subscribers() merle.Subscribers {

return merle.Subscribers{

...

"default": nil, // silently drop everything else

}

}

Sending Messages

The only way to send a message from within Thing is to receive one. More correctly: the only way to send a packet is to receive one, since a message is contained within a packet and it's the packet that's sent and received. That sounds a little weird that Thing can't just arbitrarily originate and send a packet. But there is one trick up our sleeve: CmdRun handler is passed a packet, like all message handlers. Since CmdRun handler is our main loop (running forever), we always have a packet for sending state changes: we just keep reusing the packet that was passed in, and updating the contained message. Same with the other message handlers: each one is passed in a packet which can be used to send.

Let's look at the two options for sending packets from message handlers: broadcast or reply.

Broadcast

Broadcast sends a copy of the packet to each connection on the message bus. Broadcasts are used to notify all of state changes. A broadcast from CmdRun handler goes to all connections. A broadcast from any other handlers goes to all connections except the originating connection (we don't want an echo back to the sender).

A broadcast from CmdRun will send the packet out on all connections. In the example below, an "Update" message is sent once a second to all connections.


func (b *blink) run(p *merle.Packet) {

for {

b.led.Toggle()

b.State = b.led.State()

b.Msg = "Update"

p.Marshal(b).Broadcast()

time.Sleep(time.Second)

}

}

A broadcast from a message handler will send the packet on all connections but the originating connection. In the example below, we'll save state and broadcast state.


func (b *Bmp180) update(p *merle.Packet) {

b.saveState(p)

p.Broadcast()

}

Reply

Reply is in response to a request sent in on the packet.

A reply will send a response packet back on the requesting connection. The example below is a reply to GetState.


func (b *Bmp180) getState(p *merle.Packet) {

b.Msg = merle.ReplyState

p.Marshal(b).Reply()

}

Encoding Message

Use Packet.Marshal() to JSON-encode the message into the packet before sending.

Sending Messages from UI

As we saw earlier, there is no way to originate messages from within Thing, but from Thing's user interface (UI) we can send new messages to Thing. To send a new message from JavaScript, use the WebSocket connection send(). This will send the message back to Thing. We'll JSON-encode the message before sending.

In this example, we have a UI radio button element. If the user clicks the button (toggling its state), we'll send a message to Thing. Thing will receive the message, process the state change, and broadcast it to others.

We have this UI element, a radio button. When clicked by user, it will call sendClick():

<input type="checkbox" id="relay1" disabled=true onclick='sendClick(this, 1)'>


The sendClick() function will JSON-encode the message before sending. The message captures the new state:

function sendClick(relay, num) {

conn.send(JSON.stringify({Msg: "Click", Relay: num,

State: relay.checked}))

}


Thing will subscribe and receive "Click" messages and call click() function:

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

return merle.Subscribers{

...

"Click": r.click,

}

}


click() function will store state and then broadcast the message to others on the message bus to they can update their state as well:

func (r *Relays) click(p *merle.Packet) {

var msg MsgClick

p.Unmarshal(&msg)


// Save state from message

r.States[msg.Relay] = msg.State


// Update hardware to reflect state change

if p.IsThing() {

if msg.State {

r.drivers[msg.Relay].On()

} else {

r.drivers[msg.Relay].Off()

}

}


// Broadcast state change to others

p.Broadcast()

}


To complete the circle, other connections will receive the broadcast and will update their state. For instance, another UI instance will handle the "Click" message by updating the UI radio button element:


conn.onmessage = function(evt) {

msg = JSON.parse(evt.data)


switch(msg.Msg) {

...

case "Click":

relays[msg.Relay].checked = msg.State

break

}

}


The following animation shows the process of sending a "Click" message when the user clicks a radio button. Included is Thing Prime, to show how the broadcasts propagates through the network.

Carousel imageCarousel imageCarousel imageCarousel imageCarousel image