How To: Create a Driver

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. This tutorial will show you how to create your own HomeOS driver by using the "dummy" example driver as a starting point.

Generally, writing a driver is done in 5 steps:

  1.        Instantiating Roles: A role can be instantiated using role = new Role("lightswitch"). Operations are instantiated by calling a constructor with operation = new Operations(name, ", retvalTypes, canSub), where " and retvalTypes are, respectively, lists of the types of parameters  and return values, and canSub denotes whether the operation can be subscribed to. Operations must be added to roles by calling role.AddOperation(operation). The task of instantiating a role can be embedded in a class that corresponds to the role. See the various class defined in Role.cs.
  2.        Instantiating Ports: A port in instantiated for each service that the driver wants to offer. The driver should first call portInfo = GetPortInfoFromPlatform(name), where name is unique across all ports of the module (not across the entire system), and then call InitPort(portInfo).
  3.        Binding Ports to Roles: This is done by calling BindRoles(port,roleList). The names of the role and operation as well as the argument are passed to this function. A separate handler for each operation can also be defined. See the code for the implementation of BindRoles() for details on how this can be done.
  4.        Registering the Ports: Registration tells HomeOS that this port is now open for business. It is accomplished using RegisterPortWithPlatform(port).
  5.        Implementing functions for handling operation invocations: Here the custom logic of handing operation invocation resides.

Note: Prior to beginning this tutorial you'll need to:
- Get the source code and configure your development environment
- Complete the How to: Create a Scout tutorial

Create a New Project for your Driver

  1. Open core.sln in Visual Studio.
  2. Right click the Drivers project and select Add, New Project.
  3. In the left pane select Visual C#, then choose Class Library from the list.
  4. Under Location click Browse.
  5. Click the Drivers folder, then click Select Folder.
  6. Under Name call the new project Dummy2.
  7. Click OK to create the project.

Use Dummy Driver as a Starting Point

  1. Copy the following files from Drivers\Dummy and paste them into the Dummy2 project.

    • App.config
    • DriverDummy.cs
    • icon.png
  2. Rename DriverDummy.cs to DriverDummy2.cs.

  3. In Solution Explorer right-click Dummy2\Class1.cs and select Delete.

Add references

  1. In Solution Explorer right-click the Dummy2 project and select Add Reference....
  2. In Reference Manager, click Browse. The assemblies can be found in \Hub\output\binaries\Platform.
  3. Select the following assemblies:

    • HomeOS.Hub.Common.dll
    • HomeOS.Hub.Common.Bolt.DataStore.dll
    • HomeOS.Hub.Platform.Views.dll
  4. Click Add, then click OK to add the references.

  5. In Solution Explorer under References for the Dummy2 project, right-click the reference for HomeOS.Hub.Platform.Views and select Properties.
  6. Set the Copy Local property to False. This will prevent the Views assembly from being output since doing that would prevent the project from running properly.
  7. In Solution Explorer right-click the Dummy2 project and select Add Reference... one more time.
  8. In the left pane click Assemblies, then click Framework and select the following item:

    • System.AddIn
  9. Click OK to add the reference.

Refactor Names

  1. Open DriverDummy2.cs and make the following changes:
    • Rename the namespace to HomeOS.Hub.Drivers.Dummy2
    • Above the main class declaration, rename the driver to [System.AddIn.AddIn("HomeOS.Hub.Drivers.Dummy2")].
    • Rename the main class to DriverDummy2 (it derives from ModuleBase).
  2. Save the file.

Create a New Role

  1. In Solution Explorer, expand Common.
  2. Open Role.cs.
  3. Locate the RoleDummy class, highlight and copy it.
  4. Paste the copy directly below the original RoleDummy class.
  5. Rename the newly pasted class to RoleDummy2. In this case, do not use Visual Studio's automatic refactoring feature, as it will also refactor within the original RoleDummy class, which you do not want to do.
  6. Change the value of the RoleName string to :dummy2:.
  7. Within the new RoleDummy2 class, rename each occurrence of RoleDummy to RoleDummy2. The rest of the code can stay the same.
  8. Press F6 to build the solution and ensure that there are no errors.

Test the New Driver and Role

  1. Return to DriverDummy2.cs.
  2. Use search and replace to rename all instances of RoleDummy to RoleDummy2.
  3. Scroll to where workThread is defined and update the log string to reference DriverDummy2 (this will help us determine whether things are working properly): workThread = new SafeThread(delegate() { Work(); } , "DriverDummy2 work thread" , logger);
  4. Set a breakpoint on the Start() function within the main class definition (around line 27).
  5. In Solution Explorer right-click the Dummy2 project and select Properties.
  6. Click the Application tab and change the Assembly name and Default namespace values to HomeOS.Hub.Drivers.Dummy2.
  7. Click the Build tab and update the Output path property to ..\..\output\binaries\Pipeline\AddIns\HomeOS.Hub.Drivers.Dummy2\.
  8. Right-click the Drivers\Dummy2 project and select Build.
  9. Verify that the correct output was generated by navigating to Hub\output\binaries\pipeline\AddIns. You should see the output folder for your new driver HomeOS.Hub.Drivers.Dummy2.
  10. Click Debug, Start Debugging or press F5.
  11. In the browser of your choice, navigate to: http://localhost:51430/guiweb/.
  12. Click Add Devices.
  13. In the list of devices, select dummy2device. This should result in the breakpoint being hit, letting you know that your driver is working properly so far.

 

Example Driver Code

The examples should clarify how drivers respond to operation invocations. The example class RoleDummy exports a role with two Operations, "echo" and "echosub". To initialize the module, we can do the following (in Hub\Drivers\Dummy\DriverDummy.cs):

// ..... initialize the list of roles we are going to export

List<VRole> listRole = new List<VRole>() {RoleDummy.Instance};

 

//.................instantiate the port

VPortInfo portInfo = GetPortInfoFromPlatform("port");

dummyPort = InitPort(portInfo);

 

//..... bind the port to roles and delegates

BindRoles(dummyPort, listRole);

 

//.................register the port after the binding is complete

RegisterPortWithPlatform(dummyPort);

The constructor for class DummyRole instantiates the role (in \Hub\Common\CommonRole.cs):

protected RoleDummy()

{

    SetName(RoleName);

     _instance = this;

 

    {

        List<VParamType> args = new List<VParamType>() {new ParamType(0)};

        List<VParamType> retVals = new List<VParamType>() {new ParamType(0)};

        AddOperation(new Operation(OpEchoName, args, retVals));

    }

 

    {

        List<VParamType> args = new List<VParamType>();

        List<VParamType> retVals = new List<VParamType>() { new ParamType(0) };

        AddOperation(new Operation(OpEchoSubName, args, retVals, true));

    }

 

}

The operation handler could be implemented as shown in Hub\Drivers\Dummy\DriverDummy.cs:

public override IList<VParamType> OnInvoke(string roleName, String opName, IList<VParamType> args)

{

 

    if (!roleName.Equals(RoleDummy.RoleName))

    {

        logger.Log("Invalid role {0} in OnInvoke", roleName);

        return null;

    }

 

    switch (opName.ToLower())

    {

        case RoleDummy.OpEchoName:

            int payload = (int)args[0].Value();

            logger.Log("{0} Got EchoRequest {1}", this.ToString(), payload.ToString());

 

            return new List<VParamType>() {new ParamType(-1 * payload)};

 

       default:

            logger.Log("Invalid operation: {0}", opName);

            return null;

    }

}

Let's also assume that OpEchoSub is a subscribable function that returns an internal counter. When the driver wants to notify its subscribers, it can do so by doing something like the following (Hub\Drivers\Dummy\DriverDummy.cs):

public void Work()

{

    int counter = 0;

    while (true)

    {

        counter++;

 

        //IList<VParamType> retVals = new List<VParamType>() { new ParamType(counter) };

 

        //dummyPort.Notify(RoleDummy.RoleName, RoleDummy.OpEchoSubName, retVals);

 

        Notify(dummyPort, RoleDummy.Instance, RoleDummy.OpEchoSubName, new ParamType(counter));

 

        System.Threading.Thread.Sleep(1 * 5 * 1000);

    }

}

Now you're ready for the final tutorial in the series: How to: Create an App.

Last edited Jul 30, 2014 at 9:01 PM by dannyh206, version 6

Comments

No comments yet.