-
Notifications
You must be signed in to change notification settings - Fork 81
Steward
When the main index.js runs on startup, it starts three modules: utility, database, and steward.
On startup, the utility module creates pubsub mailboxes for:
-
beacon-ingress: the utility module both publishes and subscribes to this mailbox to maintain a history of recent logging entries and then to republish the latest logging entry to beacon-egress.
-
beacon-egress: the console management API and any indicator actors subscribe to this mailbox to receive logging entries.
-
actors: all actors subscribe to this mailbox to receive requests to report current status information, observe events, and perform tasks.
-
discovery: all UPnP-based actors subscribe to this mailbox to receive notifications.
-
readings: any indicator actors subscribe to this mailbox to receive sensor readings.
-
status: any indicator actors subscribe to this mailbox to receive aggregate state information from the steward.
As with any pubsub service, these mailboxes are used when it is "inconvenient" for a publisher to enumerate the list of interested subscribers.
The utility module also houses the logging function, which is a wrapper around winston. The most important aspect of this is the local variable logconfigs which contains the logging configuration for each module.
Finally, there are a small number of convenience routines.
On startup, the database module creates, if needed, the file db/database.db and initializes it:
-
The devices table persists basic information about actors, most importantly the deviceID and deviceUID that are used to uniquely identify a particular actor.
-
The deviceProps table persists information about "virtual" actors (e.g., place/1) along device-specific information (e.g., pairing information for the Philips Hue bridge).
-
The groups table persists information about the grouping structure, and the members table persists membership information about each group.
-
The events, tasks, and activities tables are described in Activities.
Once the database is initialized, the database module_ starts the device module.
On startup, the device module creates an entry for the root of the actor tree ('steward.actors.device'), which is described in Actors.
Next, the category-specific prototypes are loaded by starting all files in the devices/ directory with a matching name of /^device-.*.js/. In turn, each of these category-specific files starts all files in an appropriately named subdirectory (e.g., devices/device-climate/.
The _device_module implements a device-generic driver:
-
children: returns an array of deviceIDs corresponding to the dynamic list of children associated with a drive (e.g., the Philips Hue driver). In most cases, this returns a zero-length array.
-
proplist: returns the six properties associated with a particular actor:
-
whatami: the prototype identifier, e.g., '/device/lighting/hue'
-
whoami: a pairing of the actor type (typically 'device') and a number, e.g., 'device/1'
-
name: the user-friendly name
-
status: the prototype-specific status
-
info: prototype-specific state information
-
updated: the time of the last change to any of the preceding three properties
-
In addition, there are a small number of utility functions that can be used by device-specific drivers.
On startup, the steward module examines the list of network interfaces on the system, ignoring those that are either local (i.e., the loopback interface) or associated with virtual machines. For each the remaining interfaces, it starts a packet capture session to examine ARP packets and primes the pump by attempting to connect to TCP port 8888 on five unpredictably chosen addresses. (Note that priming the pump is unnecessary when the steward is able to read the kernel's arp table.)
The steward then sets up the infrastructure to report aggregate state information and then re-schedules itself. This is done so the capture sessions can run to determine the MAC address of the machine that the steward is running on. Once that occurs, the steward computes its own UUID and starts the server module. It then loads the pseudo actors (e.g., 'place/1') and begins its observer-perform (or 'activity') loop.
In a sense, this is the heart of the steward. Once a second, the scan function in the steward module is run. This looks through the list of all events known to the steward. There are three possibilities:
-
The event is '.condition', which indicates that the steward should look at the actors' state information to see if the event should be considered observed.
-
The event isn't being observed, in which case, the steward publishes a request for the event to be observed. (When the actor that is supposed to observe the event is able to do so, in response it will call the report function.)
-
The event is being observed.
Next, the scan function looks through the list of armed activities, and if the event associated with an activity has been observed, then the associated task is marked for subsequent performance. The scan function then looks through the list of all non-conditional events known to the steward and resets their "observed" status.
Then, the _scan_function then looks through the list of all tasks known to the steward, and constructs a list of tasks to be performed. For each task it publishes a request for the task to be performed. NOTE THAT TEMPORAL EVALUATION IS NOT YET IMPLEMENTED
On startup, the server module listens for http: and wss: traffic on an unused port. (If an HTTP connection does not upgrade to WebSockets, then static files are served from the sandbox/ directory.) The server module then advertises itself using multicast DNS, loads the discovery modules, and then loads the routing modules.
There are four discovery modules that are started by the server module.
This module creates an SSDP instance, both to listen for SSDP announcements and to advertise itself as a basic device on each network interface discovered by the steward module. In addition to listening on port 1900 for multicast traffic, it also listens on an unused port for UPnP notifications. The module also creates a file called sandbox/index.xml that lists the (minimal) UPnP capabilities of the steward.
The device module is informed whenever a new device is discovered via UPnP.
For UPnP-based actors, the module provides routines for roundtrip'ing UPnP traffic and subscribing to notifications.
This module turns on the system's BLE module and starts scanning. Upon discovering a device, it scans the services and characteristics advertised by the device, then determines the prototype associated with those characteristics, and then informs the device module.
The module's register method is used to associate BLE characteristics with a particular device-specific driver.
The module's pairing method is used to associate port numbers (and optionally OUI prefixes) with a callback in a particular device-specific driver. This callback determines whether the device module should be informed.
Every five seconds, the module does a port scan of registered port numbers on IP addresses that have not responded to those port numbers. If a connection is made, then the device-specific callback is invoked.
The module's pairing method is used to associate OUI prefixes with a callback in a particular device-specific driver. This callback determines whether the device module should be informed.
Whenever the steward module captures an ARP request, it invokes a method in this module to see if either MAC address has not previously been examined and whether the device-specific callback should be invoked.
This module listens on traffic for UDP port 22602 on multicast address '224.0.9.1' which is where devices report information via the Thing Sensor Reporting Protocol. Upon receipt of a report, the module updates the steward accordingly.
At present, the steward has three API modules.
Authorized clients connect to the
/console
resource in order to receive logging entries from the steward as well as state updates. When a client first connects to this resource, most actors in the system will present a brief synopsis. Thereafter log entries will be sent to the client, e.g.,
{
"climate": [
{
"date": "2013-06-18T08:12:12.787Z",
"level": "info",
"message": "device\/44",
"meta": {
"status": "present"
}
},
{
"date": "2013-06-18T08:12:12.788Z",
"level": "info",
"message": "device\/45",
"meta": {
"status": "present"
}
},
{
"date": "2013-06-18T08:12:12.788Z",
"level": "info",
"message": "device\/49",
"meta": {
"status": "present"
}
}
]
}
State updates look at little different:
{
".updates": [
{
"whatami": "/device/lighting/blinkstick/led",
"whoami": "device/36",
"name": "Blinkstick #3",
"status": "on",
"info":
{ "color":
{ "model": "rgb",
"rgb": { "r":75, "g":0, "b":130 }
}
},
"updated":1374521033437
}
// other state updates, if any, go here ...
]
}
As you might expect, any traffic sent from the client is ignored.
Authorized client may connect to the
/
resource in order to inspect available resources and API calls, e.g.,
{
"requestID": 0,
"result": {
"\/console": {
},
"\/manage": {
"\/api\/v1\/activity\/create": {
"access": "write",
"required": {
"uuid": true,
"name": true,
"event": "actor",
"task": "actor"
},
"optional": {
"comments": true,
"armed": [
"true",
"false"
]
},
"response": {
},
"comments": [
"the uuid is specified as the create suffix",
"the event actor must resolve to an event or a group of events",
"the task actor must resolve to an event or a group of tasks"
]
},
},
...
"\/": {
}
}
}
Authorized clients connect to the
/manage
resource in order to manage devices or activities, events, tasks, and groups.