Communication

The OSAS system provides two basic mechanisms for communication. The first one being regular point-to-point communication as we know from most applications in use today. The second built-in communication system is more data-driven. The communication tends to be one-to-many and end points are specified by means of content based addressing rather than (e.g. ip) addresses.

Next the various mechanisms for communication are explained.

High level constructs

WaSCoL exposes two "high-level" communication constructs for communication. These are the SendToSubscribers and SendMessage statements.

In a nutshell, SendMessage floods a message throughout a sensor network whereas SendToSubscribers routes a message to an endpoint (unicast).

Flooding

The SendMessage primitive can be used from any body of statements in the WaSCoL language. Syntactically it looks like this:

SendMessage(<destination>, <handler>, <parameters>*)

  • <destination> is either self (the message will be received by the transmitting node only) or a content based address.
  • <handler> is the ID of an event handler (known at the receiver side) which will be dispatched upon reception of this message (essentially, the ID of a callback function).
  • <parameters> can be any (possibly empty) comma separated sequence of integers. If the specified <handler> is executed, these <parameters> will be passed as the payload of the handler invocation.

In principle SendMessage generates a message which is flooded. There are some special cases which are an exception to this (see also: content based addresses). SendMessage(self,...) directly places the message in the incoming message buffer. SendMessage([Network|1|P(x)],...) does not flood, but just broadcasts the message (flooding is implemented by prefixing the message with a FloodHandler ID, the ID of the sending node and a sequence number).

A simple example (assuming handler ReportPresence exists):

1 service Example()
2   on event announce when True do
3     SendMessage([Network|1], ReportPresence, NodeID())
4
5 for [Network|*] do
6   SendMessage(self, (subscription start_announcing
7                      to Example()
8                      with(period=10s, deadline=1m, send="Normal", exec="Normal")))
When this service is activated (i.e. anyone subscribes to it), it will periodically broadcast its ID to any node in the 1-hop neighbourhood that provides the ReportPresence handler. Line three demonstrates the usage of SendMessage using a content based address. Line six demonstrates usage of self within SendMessage. Note that the shown subscription block in lines six up to eight is a special form which is compiled into a sequence of parameters not unlike the usage of SendMessage in line three.

Routing

The SendToSubscribers primitive can only be used in locations where a subscription context exists (i.e. only in event generators). Like SendMessage, SendToSubscribers takes a handler and parameters, but here the destination is taken from the subscription context.

SendToSubscribers(<handler>, <parameters>*)

SendToSubscribers unicasts the message to its destination, but requires a route to be defined in order to work properly. Such a route is constructed in either of the two following ways:

  • statically, as a side-effect of a subscription statement.
  • statically or dynamically by (periodically) flooding a setroute message.

Since setroute is used under the hood by the subscription statement as well we start with an explanation of the setroute handler.

Message format: [SetRouteHandlerID|<destination>|<cost>|<nexthop>]

  • <destination> is the NodeID of the node to which a route needs to be defined.
  • <cost> is the distance to the destination (number of hops)
  • <nexthop> is the ID of a node which already has a path towards the destination with the cost specified. The SetRouteHandler will increment <cost> by one and replace <nexthop> with the ID of the node which executes the handler. If any node wants to be a sink towards which a message can be routed using unicast communication, then that node needs to flood the message [SetRouteHandlerID|NodeID()|0|NodeID()].

Any subscription deployment of the following form has the side-effect that all nodes satisfying the content based address in the for clause will flood the SetRoute message described above. This ensures that the providers (specified in the on clause) can unicast the response back to the subscribers.

for [Network|*|P(n)]
  install <subscription name> on [Network|*|Q(x)]

The following program demonstrates how such a message can be sent periodically using the aforementioned SendMessage statement (see line 9). Furthermore it demonstrates the usage of SendToSubscribers (line 4) to notify a gateway node of temperature readings.

01 # A service for eventing temperature
02 service TemperatureService($Handler)
03     on event measure when True do
04       SendToSubscribers($Handler, NodeID(), Temp())
05
06 # A route reconstruction service
07 service SinkService()
08     on event build_route when True do
09       SendMessage([Network|1], SetRoute, NodeID(), 0, NodeID())
10
11 # enable route reconstruction
12 subscription SinkNodes
13 to SinkService
14 with (period=1m, deadline=1m, send="Normal", exec="Normal")
15
16 # subscribe to temperature
17 subscription TemperatureSubscription
18 to TemperatureService($Handler=print)
19 with (period=1m, deadline=1m, send="Normal", exec="Normal")
20
21 for [Network|*|HasSysCall(Temp)]
22   install TemperatureService
23
24 for [Network|*]
25   install SinkService
26
27 for [Network|*|NodeType()=="Gateway"]
28   # turn all temperature nodes into sinks
29   install SinkNodes on [Network|*|HasSysCall(Temp)]
30   install TemperatureSubscription on [Network|*|HasSysCall(Temp)]
31 .
In case we do not care about dynamic routes (e.g. because the nodes are immobile) then we can simply leave out the SinkService and the SinkNodes subscription (lines 6 up to 15 and line 29), since the TemperatureSubscription will already do an initial route construction as a side-effect.

Low level construct

Beside the higher-level communication construct described above, there is a standard system call (Send) for transmission of MAC-layer packets. This system call can be used to roll out your own communication primitives or implementing other low-level communication. The Send call takes two arguments:

Send(<targetID>, <mcontext>)

  • <targetID> is the ID of a node. It will be put into the MAC header such that only this node receives the message (use 255 for broadcast).
  • <mcontext> is a message context. Contrary to SendMessage and SendToSubscribers, this message context MUST contain the WSP header fields (ApplicationID, QoS, Deadline, Hopcount) where the upper 3 bits of QoS indicate priority, the following 3 bits indicate reliability and the last 2 bits indicate encoding (00: 8bit, 01: utf-8 like, 10: 16bit).

A message context can be constructed within WaSCoL using a Message expression, e.g.

msg := Message($Handler, NodeID(), Temp())

Message contexts can be composed using the concatenation operator:

header := Message(66, 30, 10, 0); msg := header ++ Message($Handler, NodeID(), Temp())

The following program is a fairly large example which demonstrates how to use the low level construct to implement the higher level constructs (or something very similar).

service FloodService()
  define
    seqnr := 0

  action SetMsgRoute(dest, cost, next) do
    # update route to source
    if cost < InitProperty(dest, 255, "CostTable") then
      SetProperty(dest, cost, "CostTable");
      SetProperty(dest, next, "RoutingTable")
    fi;
    cost := cost + 1;
    next := NodeID()

  action FloodMsgHandler(source, seqnr) do
    last := InitProperty(source, 255, "FloodingTable");
    if (last < seqnr && seqnr < last + 128) || (seqnr < last - 128) then
      SetProperty(source, seqnr, "FloodingTable");

      # Needs to be invoked here! Requires a reentrant interpreter.
      InvokeHandler(self.message, self.handler_start + 3, self.handler_end);

      # update hopcount
      if self.message[3] != 1 then
        if self.message[3] then
          self.message[3] := self.message[3] - 1
        fi;
	# Broadcast self.message
	Send(255, self.message)
      fi
    fi

  action RouteMsgHandler(destination) do
    if destination == NodeID() then
      InvokeHandler(self.message, self.handler_start + 2, self.handler_end)
    else
      next := GetProperty(destination, "RoutingTable");
      hopcount := self.message[3];
      if hopcount != 1 then
        if hopcount then
          self.message[3] := hopcount - 1
        fi;
        # Send self.message to next hop
        Send(next, self.message)
      fi
    fi

  # API

  action FloodMsg(message) do
    # format: [appID|QoS|DL|HC|<payload>]
    Send(255, Message(66,0,0,0, FloodMsgHandler, NodeID(), seqnr) ++ message);
    seqnr := (seqnr + 1) % 256

  action RouteMsg(dest, message) do
    next := GetProperty(dest, "RoutingTable");
    Send(next, Message(66,0,0,0, RouteMsgHandler, dest) ++ message)

service TestService($Handler, $target)
  on event e when True do
    RouteMsg($target, Message($Handler, Temp()))

for [Network|*]
  install FloodService

for [Network|*|HasSysCall(Temp)]
  install TestService

# Message generated:
# CBA 2 0 0 INIT 26 TwoHandler 5 SetRoute NodeID() 1 NodeID() CBA 3 0 Subscription TestService test 130 NodeID() 16 2 2 1 16

for [Network|*|NodeType()=="Gateway"] do
  FloodMsg(
    Message(TwoHandler, 6, SetMsgRoute, NodeID(), 1, NodeID())
    ++ [Network|*|HasService(TestService)]
    ++ Message((subscription test
                to TestService($Handler=print, $target=34) # see (*) below
 		with (period=2s, deadline=1m, send="normal", exec="normal"))))
    # (*) 34 is ID of the gateway, todo make compiler accept: NodeID()
.