HomeOS Software Architecture

This document explains the HomeOS software architecture. Using the HomeOS SDK "dummy" examples for reference, we'll walk through how all of the various elements are structured, and how they interact. These "dummy" examples not only illustrate how HomeOS functions, but can provide an excellent starting point when you are ready to develop your own solutions.

Note: If you have not already done so, you'll need to get the source code and configure your development environment prior to beginning this walkthrough.

The HomeOS software is structured like a plugin framework. As such, it has two core pieces – the host platform and the plugin modules. The platform is implemented by the (Visual Studio) project called the Platform, and each module (driver or application) is implemented as its own project.

Isolation between platform and modules and between modules is achieved using two mechanisms. The first is that each modules runs in its own application domain, which is a lightweight isolation mechanism provided by the .NET Framework.

The second mechanism is the System.AddIn framework which builds on top of application domains. It provides a model for developing plugin frameworks in .NET and means for expressing interfaces across modules as well as independent versioning of modules and platform. These benefits come at the cost of increased programming complexity and restrictions, i.e., programming discipline. We do not delve into the details of the System.AddIn framework, but focus on how HomeOS uses this framework.

Programming Abstractions

The programming model for HomeOS is service-oriented: all functionality provided by drivers and applications is provided via ports which export one or more services in the form of roles. Each role has a list of operations which can be invoked by applications. The role for a dimmer switch might have an operation called "setdimmer" which takes in an integer between 0 and 99 that represents the desired value for the dimmer.

Operations can also return values, so the same light switch may have an operation called "getdimmer" which returned an integer that corresponds to the current dimmer value. Further, some operations can be subscribed to allowing for later notifications concerning the operation. For instance, subscribing to the "getdimmer" operation might provide a callback whenever the dimmer's value changed.

Architecturally, HomeOS makes little distinction between drivers and applications. Both are referred to as modules. Usually, driver modules tend to communicate directly with devices and offer their services to other modules. Application modules tend to use the services of drivers. But a given module can both export its own services and use those of others. As mentioned above, HomeOS isolates modules from each other using application domains and the System.AddIn framework.

Scouts, Drivers and Applications

There are three main extensibility points in the HomeOS SDK:

  • Scouts discover devices on the network during the installation phase, and then facilitate selecting the appropriate driver to use. They also direct users to the appropriate UI for the selected device.
  • Drivers are entities which communicate with devices when HomeOS is running. They expose the functionality of devices so that apps can communicate with devices. Drivers are associated with roles, which define the services offered by a particular type of device.
  • Applications expose device functionality to users through a user interface. This is where users can interact with and control the device.

The dummy scout, driver, and app are developed for instructional purposes and are intentionally very simple. The "dummy" configuration runs two modules: DriverDummy, and AppDummy, and one role RoleDummy. These examples are a good reference for understanding how the HomeOS platform works. DriverDummy exports a port with the RoleDummy role, and two operations "echo" and "echosub", and sends out periodic notifications to other modules that subscribe to its echosub operation. AppDummy looks for all ports registered with role "dummy", invokes those ports' echo operation and subscribes to their echosub operation. The following table shows where to find the dummy components.

Module Source Location
DriverDummy Hub\Drivers\Dummy
AppDummy Hub\Apps\Dummy
RoleDummy Hub\Common\Common\role.cs

Dummy Scout

Scouts in HomeOS are used to automatically discover devices and integrate them into the running platform. When adding devices, the flow is conducted as follows:

  1. The scout discovers the devices in its environment.
  2. The scout makes the platform aware of the devices.
  3. The user can query the platform for discovered devices that are not already part of the system.
  4. The user selects a device to add.
  5. The device setup is accomplished via one or more HTML pages.

    a. The initial pages enable device configuration that is specific to the device type.
    b. The final page is the general HomeOS device addition page, where the device's location and associated apps can be configured.

The driver for the device is started as part of loading the final HTML configuration page. The scout is responsible to:

  1. Discover devices and report to the platform when queried.
  2. Host the UI (HTML pages backed by WCF services) for custom device configuration.
  3. Hand over control to the platform by pointing to the generic device addition page.

To learn more about how scouts work, let's take a look at our dummy scout.

  1. In Visual Studio, open the HomeOS core solution (core.sln).
  2. In Solution Explorer, open Scout\Dummy\DummyScout.cs.

The DummyScout class implements the IScout interface, which has three functions:

  • Init() initializes the current scout instance (in this case DummyScoutService).
  • Dispose() disposes the instance when it is finished.
  • GetDevices() is called when apps are searching for devices on the network. This is where the core functionality is.

The DummyScout class contains a very basic implementation of GetDevices().

public List<Device> GetDevices()
{
    // Create the device instance.
    Device device = new Device("dummydevice", "dummydevice", "", DateTime.Now, "HomeOS.Hub.Drivers.Dummy", false);

    // Initialize the parameters for this device.
    device.Details.DriverParams = new List<string>() { device.UniqueName };

    // Return the device instance.
    return new List<Device>() { device };
}

Note: For the dummy scout, driver, and app, there is no actual device (such as a sensor, light switch, or webcam). In real-world usage there is always a device.

The device instance is created by specifying the following parameters:

  • Friendly name string
  • Unique name string
  • Device IP address (an empty string can be passed if, as in this example, there is no device IP)
  • DateTime.Now (to provide a timestamp for initialization)
  • Binary name of the corresponding driver

In the next step, parameters for the device are initialized by assigning a list of string parameters to device.Details.DriverParams. These parameters are then passed to the driver upon discovery. The dummy scout then passes a list of the device names that it has discovered.

The job of the Init() function is to start the scout service (in this example DummyScoutService).

scoutService = new DummyScoutService(baseUrl + "/webapp", this, platform, logger);

Open Scout\DummyScoutSvc.cs to see the code for the DummyScoutService class. The primary function of DummyScoutService is to redirect users to the appropriate UI for the selected device. Open Scout\Dummy\Index.html to see the redirection code for the dummy scout. It is essentially a redirect page that is called when the device that is discovered by the scout is loaded. To see how the default HTML page appears for a scout that has an actual physical device, take a look at Scout\WebCam\Index.html. Several additional examples are available on CodePlex (look under Hub\Scouts).

Dummy Driver

In HomeOS both drivers and applications are referred to as "modules", and they extend the ModuleBase class. Drivers and applications communicate by using ports. Drivers declare the ports and associated roles and export them to the core platform. Applications can then query for ports and select the appropriate one.

To see the source code for DriverDummy:

  1. In Visual Studio, open the HomeOS core solution (core.sln).
  2. In Solution Explorer, open Drivers\Dummy\DriverDummy.cs.

When a driver is started, the Start() function is called. The Start() function does the following:

  • Gets the device's unique name as passed by the scout (in this example it is the string dummyDevice).
  • Instantiates the port class.
  • Declares a list of roles that are backed by the port. These will be exported and bound to the port.
  • Registers the port once binding is complete.
  • Creates a new work thread.
  • Starts an image server to provide icons, etc.

Drivers are associated with roles, which define the services offered by a particular type of device. For example, the role for a video camera with pan and tilt capabilities would include definitions for "up", "down", "left", and "right" while a binary sensor might just have definitions for "on" and "off". Roles are not specific to a particular device or app. Instead each role represents a set of capabilities which may be shared by many devices and apps. When writing or extending device drivers and apps, you can use the predefined roles or create a custom role class if necessary.

In the Hub project, some example roles are defined in Hub\Common\Common\role.cs. You can add a custom role class anywhere, as long as that class can be referenced by the driver and application modules that use it. The RoleDummy class was created to demonstrate the preferred pattern for creating a role. Scroll down through the code for the roles to see additional examples.

The driver's main functionality is contained in the Work() function. For DriverDummy it consists of a loop that sends notifications to all subscribing applications.

OnInvoke()is called when an application calls any operation on a port. In DriverDummy it takes the request payload (an integer value), performs an operation (multiply by -1) and returns the result to the caller.

Stop() is called when the platform unloads the driver. This stops the worker threads that were in use by the driver.

PortRegistered() and PortDeregistered() are callbacks that the platform makes to applications and drivers whenever ports are added or removed.

Several driver examples are available on CodePlex (look under Hub\Drivers).

Dummy Application

Applications provide a UI for users to interact with the associated device. Like drivers, applications are also AddIns and derive from ModuleBase.

[System.AddIn.AddIn("HomeOS.Hub.Apps.Dummy")]
public class Dummy :  ModuleBase
{
    ...

To see the code for the dummy application:

  1. In Visual Studio, open the HomeOS core solution (core.sln).
  2. In Solution Explorer, open Apps\Dummy\Dummy.cs.

The Start() function defines DummyService which does the following:

  • Exposes a RESTful interface that hosts the UI.
  • Holds a list of available ports.
  • Opens a data stream for logging.
  • Starts a worker thread.

DummyService has methods for POST and GET. DummyService is backed by IDummyContract which facilitates getting received messages (see Apps\Dummy\DummyService.cs)

In the Work() thread the dummy app starts a loop which sends an echo request to all accessible ports every ten seconds (SendEchoRequest()), then logs the results. SendEchoRequest() performs an invoke on the port it was called with, checks for errors, then logs the return values.

OnNotification() is called by the platform when subscription notifications are sent by drivers. It also gets called with role name and notification name. It then parses and logs the results.

ProcessAllPorts() calls PortRegistered() for each registered port. PortRegistered() determines whether the registered port has the appropriate role (in this case RoleDummy.RoleName), then calls GetCapability() which contacts the platform and returns permission to interact with the port. A null result indicates that the required permissions do not exist. If the result is non-null the port is added to the accessibleDummyPorts list, the port is subscribed and the results are logged.

To handle ports that have disappeared, PortDeregistered() is called. If a port is listed but cannot be found it is removed in this function.

The dummy service is called with the logger object and the dummy app object, which it stores in memory. Once GetReceivedMessages() is called it gets the messages and returns them to the caller.

Running the Platform

The platform uses a set of configuration files to store settings that define aspects of how it runs. We will run the Dummy app using DummyConfig (Platform\Configs\DummyConfig), which contains the following configuration files:

  • Devices: A list of the devices in use.
  • Globals: Some global settings.
  • Locations: A list of locations (kitchen, bedroom, etc.).
  • Modules: A list of modules in use.
  • Rules: Access control rules.
  • Scouts: A list of scouts in use.
  • Services: A list of ports that are in use.
  • Settings: More global settings (Home ID, data store account information, etc.).
  • Users: A list of authorized users.

In the DummyConfig, only the DummyScout and WebCam scout run by default.

To Set the Start Option

  1. In Visual Studio Solution Explorer, right-click the Platform project (Platform\Platform) and select Properties. Click the Debug tab. Under Start Options, Command line arguments enter the following:

    -c ..\..\Configs\DummyConfig -l:stdout -p false

The -l option specifies where logging should take place.
The -p option specifies whether to use access rule checking (it is set to false in this example). The -p flag is useful when debugging.

To Run the Platform

  1. In Visual Studio, click Debug, Start Debugging (or press F5).
  2. Open a browser and go to http://localhost:51430/Guiweb/.
  3. Click Add Devices.
  4. Select dummydevice from the list.
  5. Under Final Device Setup enter a name.
  6. Under Install these applications, check AppDummy.
  7. Return to the dashboard and click AppDummy
  8. To see the results, click Update. You should see the messages displayed in the interface.

Last edited Apr 11, 2014 at 11:35 PM by dannyh206, version 5

Comments

dannyh206 Apr 11, 2014 at 11:34 PM 
Did you build the AppDummy? If you are still not seeing it, could you private msg me the log so I can take a look? The log is located: output\Data\Platform\homeos.log

Yes, it should be ..\..\Configs\DummyConfig. Thanks for catching and calling it out.

piyushw Mar 29, 2014 at 12:58 PM 
I did the last step, and I do see the dummy device. But I cannot find the corresponding AppDummy in the list of apps.

Secondly,
I did use the dummy config using the option : -c ....\Configs\DummyConfig -l:stdout -p false
This did not work for me. I had to provide complete path for DummyConfig. And on the same note what do four dots mean? as in "....\Configs\DummyConfig". Is it a typo instead of ..\.. ?