Codecore Logo
Quick Search
»
Advanced Search »

Device Driver Development

RSS
Elve uses a plug-in architecture for device drivers. Third party developers can create new device drivers to use with Elve or can update existing Elve drivers.

Table of Contents [Hide/Show]


Requirements
   Development Environments
   Supported Languages
   Microsoft .NET Framework
   Platform
   Base Class
Basics
   Driver Lifecycle
   Required Class Library Reference
   Namespaces
   Visual Studio Intellisense Help
   Optional Interfaces
   Logging
   Recommended Best Practices
How to Debug Drivers
   Real Time Debugging
   Test Harness
   Logging
Minimal Driver Class Example
Configuration Settings
   Defining Settings
   Custom Driver Settings Editors
Scripting Datatypes
   Value Types
   Reference Types
Exposing Properties and Methods to Scripts
   Exposing Properties to Scripts
      Instance Property
      Arrays
      Read Only Array Property
      Writable Array Property - Using Callbacks
      Writable Array Property - Using Wrapper Object
      Property Binding
         Property Binding is used by the system in the following ways:
         How to implement Property Binding:
      Dynamic Instance Property
   Exposing Methods to Scripts
Exposing Properties and Methods to Action Lists
   Supported Datatypes
   Exposing Properties to Action Lists
      Macro Definitions
   Exposing Methods to Action Lists
      Macro Definitions
Events
   Auto-generated property change events
   Custom Events
      Whenever Filters
      Event Arguments
      Raising a Custom Device Event
      Executing A Custom Device Event Rule (DEPRECIATED)
   Auto-generated property change events -vs- Custom Events
When is the Device ready for use?
Specifying Configuration Files
Custom Driver Event Parameter Editors
Inter-Device Communication
   Example 1
   Example 2
Infrared Drivers
Driver Class Members
   Instance Methods
      StartDriver( Dictionary configFileData )
      StopDriver( )
      RaiseDeviceEvent(DriverEvent driverEvent, DriverEventParameterDictionary eventParametersToMatch, DriverEventArgDictionary eventArgs)
      ExecuteDriverRuleSafely(IRule rule)
      ExecuteDriverRuleSafely(IRule rule, DriverEventArgDictionary eventArgs)
      DevicePropertyChangeNotification(string propertyName)
      DevicePropertyChangeNotification(string propertyName, object newPropertyValue)
      DevicePropertyChangeNotification(string propertyName, int propertyIndex, object newPropertyValue)
      HandleDevicePropertyChangeNotification(DevicePropertyValueChange notification)
      HandleDeviceEventNotification(string deviceName, string eventName, Dictionary eventArgs)
      HandleAddedRule(DriverEvent driverEvent, IRule rule)
      HandleRemovedRule(DriverEvent driverEvent, IRule rule)
      InvokeDeviceMethod(string deviceName, string methodName, params IScriptObject parameterValues)
      GetDevicePropertyValue(string deviceName, string propertyName)
      GetDevicePropertyValue(string deviceName, string propertyName, int propertyIndex)
      SetDevicePropertyValue(string deviceName, string propertyName, IScriptObject value)
      SetDevicePropertyValue(string deviceName, string propertyName, int propertyIndex, IScriptObject value)
      ToggleBooleanDevicePropertyValue(string deviceName, string propertyName)
      ToggleBooleanDevicePropertyValue(string deviceName, string propertyName, int propertyIndex)
      OffsetNumericDevicePropertyValue(string deviceName, string propertyName, double offset)
      OffsetNumericDevicePropertyValue(string deviceName, string propertyName, int propertyIndex, double offset)
      RunScript(string scriptName, string script, bool throwError)
      RunScript(string scriptName, string script, bool throwError, bool runAsync, params object variables_Name_comma_ScriptObject)
      SetPropertyAfterDelayAsync(string key, TimeSpan duration, string propertyName, IScriptObject propertyValue)
      SetPropertyAfterDelayAsync(string key, TimeSpan duration, string propertyName, int arrayIndex, IScriptObject propertyValue)
      InvokeMethodAfterDelayAsync(string key, TimeSpan duration, string methodName, IScriptObject methodParameters)
   Instance Properties
      DeviceName
      DeviceDisplayNameInternal
      DriverDisplayNameInternal
      Logger
      DeviceStartTimeInternal
      IsReady
      ConfigurationFileNames
      LocalDeviceDataDirectoryPath
   Obsolete Members
Serial, TCP, UDP, & USB I/O Communication
   Connection Monitoring
   Text Encoding/Decoding
   Receiving Data
   Simulating Received Data
   Sending Data
   Internal Buffer
   Initialization Examples
      Delimited String Protocol
      Binary Protocol
File I/O and Impersonation
Deployment
   Compiled Assembly Class Libraries (.dll files)
   Uncompiled Source Code Files (.cs and .vb files)
      Troubleshooting
      Compiler.config file
Submitting your driver for Review


Requirements

Development Environments

There are several development environments available to use to create drivers, some of which are free.

  • Microsoft Visual Studio Express 2008 or 2010 (FREE)
    This is a free integrated development environment from Microsoft, which is a scaled down version of the full Microsoft Visual Studio product.

  • Microsoft Visual Studio 2008 or 2010
    This is Microsoft's flagship integrated development environment and is available for purchase. *

  • SharpDevelop (FREE)
    This is a free integrated development environment which supports attaching to a process, while Microsoft Visual Studio Express does not. *

  • Text Editor & Microsoft .NET built in compiler (FREE)
    The .NET framework ships with built-in compilers for several languages and an optional build tool (msbuild.exe). This method is not recommended for beginners and is the most tedious method.

* These two development environments support attaching a debugger to the DriverService.exe for real time debugging of the driver.

Supported Languages

  • Compiled Drivers
    Drivers that are deployed as assembly class libraries (.dll files) may use any .NET language.

  • Uncompiled Source Code Drivers
    Drivers that are deployed as source code files must use either the C# or VB.NET programming language (.cs or .vb files). Elve will compile these drivers on startup using the appropriate .net 3.5 language compiler.

C# is the recommended language and all examples here use C#.

Microsoft .NET Framework

Drivers must target the Microsoft .NET 2.0 to 3.5 Framework.

Platform

Drivers must be compiled to run on the "ANY CPU" platform when deploying to Elve.

Because Visual Studio does not support edit and continue in 64bit mode you may debug using the "x86" platform when running on a 64bit OS for convenience.

Base Class

All drivers must inherit from from the CodecoreTechnologies.Elve.DriverFramework.Driver class, which is located in the CodecoreTechnologies.Elve.dll assembly file.

Basics

Driver Lifecycle

  1. The driver is instantiated by the associated driver service.
  2. The driver's optional ConfigurationFileNames property is read by the driver service so it can later provide the driver with any extra configuration file data. Most drivers do not have configuration files.
  3. Driver settings that have been configured by the user are set. (Those properties with the DriverSetting attribute.)
  4. DriverEvent fields are set to new instances of DriverEvent.
  5. StartDriver() is invoked, which includes any extra configuration file data as a parameter. See the note below.
  6. DriverEvents.Rules collections are populated with configured rules and HandleAddedRule() is invoked for each rule.
  7. StopDriver() is called when the driver is to be stopped.

A note on StartDriver(): When using a SerialCommunication, TcpCommunication, or UsbHidCommunication object you should normally put any initialization commands or requests in the ConnectionEstablished event and not in the StartDriver method.  This is because the user may not have the device connected to the pc when the driver starts or it may get disconnected/reconnected later, etc.

A note on StopDriver(): It is very important to dispose all allocated resources, ESPECIALLY running timers. Failure to do so can produce an unstable system.

Required Class Library Reference

The Driver abstract class must be referenced from the CodecoreTechnologies.Elve.dll file.

Assembly: CodecoreTechnologies.Elve.dll

Namespace: CodecoreTechnologies.Elve.DriverFramework

Driver Base Class: CodecoreTechnologies.Elve.DriverFramework.Driver

Namespaces

At a minimum you should have the following using directives in your driver source code:


using CodecoreTechnologies.Elve.DriverFramework;
using CodecoreTechnologies.Elve.DriverFramework.Scripting;

If your driver uses any of the driver interfaces, communication classes, settings editors or other features you may use more namespaces, for example:


using CodecoreTechnologies.Elve.DriverFramework;
using CodecoreTechnologies.Elve.DriverFramework.Common;
using CodecoreTechnologies.Elve.DriverFramework.Communication;
using CodecoreTechnologies.Elve.DriverFramework.DeviceSettingEditors;
using CodecoreTechnologies.Elve.DriverFramework.DriverInterfaces;
using CodecoreTechnologies.Elve.DriverFramework.Extensions;
using CodecoreTechnologies.Elve.DriverFramework.Scripting;

For VB.Net, see the VB Imports statement documentation.

Visual Studio Intellisense Help

The CodecoreTechnologies.Elve.xml file provides Intellisense popup help for classes and members when using Visual Studio. This xml file is in the SDK zip file and must be placed in the same directory as the CodecoreTechnologies.Elve.dll that is referenced by your project.

Optional Interfaces

A list of predefined interfaces is provided to ensure compatibility across common device types. If you are developing a driver that matches one of the following interfaces, please use it. The System Script Object contains methods for retrieving devices implementing these interfaces for use in scripts.

Namespace: CodecoreTechnologies.Elve.DriverFramework.DriverInterfaces

  • IClimateControlDriver: For thermostats and climate control devices.
  • ILightingAndElectricalDriver: For lighting and electrical modules such as ZWave, UPB, X10, etc.
  • IX10Driver : For matrix lighting and electrical modules which use X10.(superset of ILightingAndElectricalDriver).
  • IOutputsDriver: For relays and outputs.
  • IInputsDriver: For input sensors.
  • IMediaPlayerDriver: For media players such as ITunes, Squeezebox, etc.
  • ISecurityDriver: For security panel devices such as HAI Omni, Elk M1, etc.
  • ITasksDriver: For devices that provide simple task activation.
  • IWeatherDriver: For drivers that provide weather information.
  • IIRBlasterDriver: For devices that can send infrared signals.
  • IIRRecieverDriver: For devices that can receive infrared signals.
  • IMediaLibraryDriver: For media libraries, such as a music library.
  • IMatrixSwitcherDriver : For matrix switchers such as A/V matrices.
  • IMultiroomAudioDriver : For multi-room audio devices such as A/V receivers and whole home audio devices.
  • IVideoDisplayDriver : For video display units such as video projectors.

Logging

The base Driver class contains a Logger property which should be used to log messages to the system log. Here is an example:


Logger.Debug("Nuvo Concerto Driver processing: " + response);

There are different levels of logging severity. The most common are Error, Warning, and Debug. Errors and Warnings are normally always logged while Debug messages are usually only logged when the the device is set to log diagnostics information.

If you would like to log an exception you may use the Log method override which accepts an exception object.


Logger.Error("Failed to retrieve xyz.", ex);

You can also pass a reference of the Logger to other objects such as the communication classes described later.

Recommended Best Practices

  • Driver class names should end with "Driver", e.g. "SqueezeCenterDriver".
  • Be sure to include adequate Log statements using the Debug logging severity to aid in diagnostics.
  • [ScriptObjectProperty] attribute property descriptions should start with "Gets or sets ..." or "Gets ...", e.g. "Gets or sets the squeezebox player's volume."
  • If there is a get property accessor for a value and the same value can also be set, use a set accessor as opposed to a new method. In other words if you have a 'Volume' property with a get accessor, don't add a SetVolume() method. Instead add a set accessor to the 'Volume' property.
  • In the [ScriptObjectMethod] and [ScriptObjectProperty] attributes, method and set property accessor action list/script builder support tags should be complete sentences, but get property accessor action list/script builder support tags should be sentence fragments.
  • Property names should generally be nouns, while method names should be verbs.
  • Public property and method names should be upper camel cased. Examples: IsConnected, CurrentTemperature, ActivateTask().
  • Private class fields names should start with an underscore and be lower camel cased. Examples: _isConnected, _currentTemperature.
  • Public method parameter names should be lower camel cased. Examples: deviceID, userName.
  • Expose value ranges that make sense. For example if an A/V receiver's underlying hardware protocol uses a volume range of -24 to 25, don't expose the range to the user with this range. Expose it as 0 to 50 in any properties and methods and do the necessary conversion in the driver.
  • Regarding arrays it is recommended you store your values in a local array that is base 0 (ie, the 1st element is at index 0) while you expose the elements to the user as base 1 since this is usually what user's expect. For example you would store zone 1's value in _zones[0].

How to Debug Drivers

This section describes three techniques to debug device drivers starting with the most powerful but most complicated to the least powerful but simplest.

The recommended approach for your initial driver development and unit testing is to use the Test Harness technique. Once your driver is ready to test within a running Elve system you would then use the Real Time Debugging or Logging technique.

Real Time Debugging

There are a variety of debuggers available which can be used to debug your driver while it is running within Elve by setting breakpoints, inspecting variables, stepping through lines of code, etc.

The most common choices are:

  • Visual Studio (but not the Express Edition)
  • SharpDevelop (free)

  • Pros:
    • The driver can be tested while running within the Elve system.

  • Cons:
    • Requires deploying driver to the Elve system.
    • Requires attaching the debugger to a process.
    • Requires the use of a debugger.

Follow these steps to debug your driver.

  1. Compile your driver as a class library assembly (dll) in the debug configuration. This will produce a DLL and PDB file. You will need both of these files in the next step.
  2. Follow the steps in the Compiled Assembly Class Libraries (.dll files) section to deploy your driver dll (and pdb) files. When copying the DLL file you should also copy the associated PDB file to the same directory.
  3. In your debugger of choice attach the debugger to the DriverService.exe process. Both Visual Studio (but not the Express edition) and SharpDevelop support attaching to a process:
    1. Select the following menu: Debug -> Attach to Process...
    2. Choose DriverService.exe
    3. Click the Attach button.
  4. Set any desired break points in your driver source code.
  5. The debugger will suspend the process's thread (or threads) while when a breakpoint is hit.
  6. To stop debugging the debugger should have a command or menu to Detach the debugger.

The driver source code must be in sync with the compiled class library to debug. If you make changes to your driver source code you will need to follow the steps above again.

Test Harness

Drivers can be run and debugged in a simulated driver service by using the Driver Test Harness. The following code example uses the Driver Test Harness to create a driver and start it. You would normally add the code below to the Main method of a Console application and either reference your driver project or include your driver class within the project for debugging. Since the driver will be running outside of the Elve system some driver methods will have no effect, such as setting a property on another device with SetDevicePropertyValue().

  • Pros:
    • Does not require deploying driver to the Elve system.
    • Does not require attaching to any processes.
    • Easy to set up and test.

  • Cons:
    • The device driver is running outside of a complete Elve system and therefore can not interface with a touch screen, send device property change notifications, receive device property change notifications, etc.
    • Requires the use of a debugger.

To create and test your driver, you simply invoke:

using CodecoreTechnologies.Elve.DriverFramework.DriverTestHarness;

MyDriver device = (MyDriver)DeviceFactory.CreateAndStartDevice( typeof(MyDriver) );

Here is a complete example including device settings, etc:

using System;
using System.Collections.Generic;
using CodecoreTechnologies.Elve.DriverFramework.DriverTestHarness;
using CodecoreTechnologies.Elve.DriverFramework.Scripting;


// Prepare any needed configuration files (this is rare).
Dictionary<string, byte[]> configFiles = new Dictionary<string, byte[]>();
//configFiles.Add("myfile.xml", ...);

// Prepare any settings (if the device requires that settings be set)
TestDeviceSettingDictionary settings = new TestDeviceSettingDictionary();
//settings.Add(new TestDeviceSetting("SerialPortSetting", "COM1"));

// Prepare any rules (if you wish to test with rules)
TestRuleDictionary rules = new TestRuleDictionary();
//rules.Add(new TestHarnessDriverRule("my rule", true, "TheEventMemberName", new StringDictionary()));

//**************************************************
// Create and Start the device.
//**************************************************
// TODO: Change the "MyDriver" type below below to the type name of your driver.
MyDriver device;
try
{
    device = (MyDriver)DeviceFactory.CreateAndStartDevice(typeof(MyDriver), configFiles, settings, rules, null);
}
catch (Exception ex)
{
    // An exception occurred while creating or starting the device.
    throw;
}

// Test any properties or method here.
ScriptNumber stage = device.DeviceLifecycleStage;

// Sleep until the user presses enter.
Console.ReadLine();

// Stop the device gracefully.
device.StopDriver();

Logging

The simplest way to gather debug information from a driver is to output data to the log using the Driver's Logger property and review the log using Elve Management Studio.

  • Pros:
    • Does not require the use of a debugger or attaching to a process.
    • The driver can be tested while running within the Elve system.
    • Logging information can be received from from other users who experience problems with the driver.

  • Cons:
    • Requires deploying driver to the Elve system.
    • Since no debugger is used you can't step through lines of source code.

Minimal Driver Class Example

This class is a barebones, "do nothing" driver. It has the bare minimum requirements to be a driver.


// The using statements vary depending on which class libraries you use,
// however you will usually have the following at a minimum.
using System;
using System.Collections.Generic;
using CodecoreTechnologies.Elve.DriverFramework;
using CodecoreTechnologies.Elve.DriverFramework.Scripting;

// It is recommended to specify a namespace, it can be anything you like.
namespace MyDrivers
{
    /// <summary>
    /// This driver does nothing, it's just a skeleton example.
    /// </summary>
    [Driver(
        "Empty (Template) Driver (SAMPLE SDK)",                     // Display Name
        "This is an empty/shell driver for example purposes only.", // Description
        "Joe Smith",                                                // Author
        "Example",                                                  // Category
        "",                                                         // Subcategory
        "example",                                                  // Default instance name
        DriverCommunicationPort.None,                               // Used Communication Ports
        DriverMultipleInstances.MultiplePerDriverService,           // Allow multiple instances
        0,                                                          // Major Version
        1,                                                          // Minor Version
        DriverReleaseStages.Development,                            // Release Stage
        "",                                                         // Manufacturer Name
        "",                                                         // Manufacturer Url
        null                                                        // Registration - for official drivers
        )]
    public class EmptyDriver : Driver
    {
        /// <summary>
        /// Starts the driver.  This typcially sets any class variables and hooks
        /// any event handlers such as SerialPort ReceivedBytes, etc.
        /// </summary>
        /// <param name="configFileData">Contains the contents of any configuration files specified in the ConfigurationFileNames property.</param>
        public override bool StartDriver(Dictionary<string, byte[]> configFileData)
        {
                // TODO: Add any necessary startup logic here.
                return true; // Indicates that the driver is ready to use, otherwise the driver needs to manually set ReadyInternal when appropriate.
        }

        /// <summary>
        /// Stops the driver by unhooking any event handlers and releasing any used resources.
        /// </summary>
        public override void StopDriver()
        {
                // TODO: Add any necessary cleanup logic here.
                //       Don't forget to dispose of any timers here!
        }
    }
}

Configuration Settings

Most drivers will have configuration settings, such as the serial port or network address that the driver instance should use. These settings are configured via the Elve Management Studio application when adding or editing a device.

Defining Settings

Driver Configuration Setting declaration requirements:

  • It must be a Property with a set accessor.
  • It must be decorated with the [DriverSetting] attribute.
  • The property must be of a primitive .net datatype, such as System.String, System.Boolean, System.Int32, etc. Elve uses System.Convert.ChangeType(strSetting, property_type) to convert the setting string to the datatype of the property.
  • To prevent property conflicts it is recommended that the property name be suffixed with "Setting".
  • Once the driver is in use, you may not change the property name since it would cause the setting to be lost.

The DriverSetting attribute has several overrides that allow you to specify the following:

  • The display name of the setting that will be displayed in the Elve Management Studio application and is for presentation purposes only.
  • A description of the setting that will be displayed in the Elve Management Studio application and is for presentation purposes only.
  • A value indicating if the setting is required for the driver to run.
  • The default value (formatted as a string) or null if none.
  • (optional) If the setting is numeric, a minimum and maximum value can specified.
  • (optional) If the setting is a string, a mask can be specified to enforce a particular format. For mask formats, see this document.
  • (optional) If the setting is a string, a predefined list of string values may be specified as string[].
  • (optional) If the setting is a custom type or requires special handling, a setting editor type may be specified. This is discussed later in this document.

The setting type can be any of the primitive .net types.

Example of a simple string setting:


[DriverSetting("The host name or ip address of the Elk M1 network interface.", null, false)]
public string HostNameSetting
{
    set { _tcpHostName = value; }
}

Example of an Int32 setting with a min/max range:


[DriverSettingAttribute("Port", "The un-secure Elk M1 port. Defaults to 2101.", 1, 65535, "2101", false)]
public int PortSetting
{
    set { _tcpPort = value; }
}

Example of an Int32 setting which uses a list of predefined settings which the user must chose from:


[DriverSetting("Serial Port Baud", "Baud rate for serial port. Anthem A/V Processors support: 2400, 4800, 9600, 19200, 38400, 57600 and 115200. The default is 19200.", new string[] { "2400", "4800", "9600", "19200", "38400", "57600", "115200" }, "19200", false)]
public int SerialPortBaudSetting
{
    set
    {
        _serialPortSpeed = value;
    }
}

Example of using the mask. At the time of this writing no driver uses the mask so the example below is fictitious. The input mask format is described here.


[DriverSetting("An upper case letter", "This is just a bogus example that shows how to use the mask field.", ">L", "B", false)]
public string UpperCasedLetterSetting
{
    set
    {
        _foo = value;
    }
}

Custom Driver Settings Editors

Driver configuration settings are edited using objects (forms) implementing the IDriverSettingEditor interface.

The standard (default) setting editor supports the following types: String, Int32, Double, Boolean.

To associate a driver parameter with a custom editor, specify the editor's type in the [DriverSetting] attribute.

The resulting value from the editor must be either a simple string or a valid xml string (without the xml declaration).

Example of a simple boolean setting using a the boolean custom setting editor:


[DriverSetting("Place a check in the checkbox to indicate that zone overrides are allowed.", typeof(BooleanDriverSettingEditor), "True", true)]
public bool AllowZoneOverridesSetting
{
     set { _allowZoneOverrides = value; }
}

Example of a string setting using the serial port custom setting editor:


[DriverSetting("The name of the serial port that the device is connected to. Ex. COM1", typeof(SerialPortDeviceSettingEditor), null, true)]
public string SerialPortNameSetting
{
     set { _serialPortName = value; }
}

Heres an example of an editor that returns xml as a string:


[DriverSetting("The IR Devices associated with this device.", typeof(IRDeviceListDriverSettingEditor), false)]
public string IRDevices
{
    set { _irDeviceConfig = value; }
}

Scripting Datatypes

The scripting language supports many datatypes such as Number, String, Boolean, DateTime, TimeSpan, and many more. All datatypes used with in the scripting language are instances of .Net datatypes which implement the IScriptObject interface. For example the scripting String datatype is actually the ScriptString.

The scripting datatype classes normally inherit from the ScriptObject or ScriptMarshalByReferenceObject abstract base classes. Classes which inherit from ScriptObject are passed between Application Domains by value, ie a copy of the object is passed. Classes which inherit from ScriptMarshalByReferenceObject (which inherits from MarshalByReferenceObject) are passed between Application Domains by reference, ie only a pointer is passed. By reference may seem faster but due to network overhead and callbacks, by reference object scripting objects can actually be about 10 times slower than by value when working with a network distributed application. Therefore if you are creating your own scripting object it is recommended that whenever possible inherit from ScriptObject.

Value Types

Variables that are based on value types directly contain values and immutable. Assigning one value type variable to another copies the contained value. This differs from the assignment of reference type variables, which copies a reference to the object but not the object itself. All Value Types are derived from the ScriptObject abstract base class.

  • String
  • Number
  • Boolean
  • DateTime
  • TimeSpan
  • Math
  • Array (ScriptArrayMarshalByValue)
  • Exception
  • RegEx

Value Type Object Lifetime:

Scripting objects which inherit from the ScriptObject abstract base type have an infinite lifetime since they are always passed by value (and not by reference).

How to get scripting object's primitive .net type value:

When developing an Elve device driver it is common to need to convert a script object to its .Net datatype equivalent, for example converting a ScriptString type to System.String. You can explicitly cast the common scripting objects to their's respective primitive .net type value:


int i = (int)aScriptNumberObject;
double d = (double)aScriptNumberObject;
bool b = (bool)aScriptBooleanObject;
string s = (string)aScriptStringObject;
DateTime d = (DateTime)aScriptDateTimeObject;
TimeSpan t = (TimeSpan)aScriptTimeSpanObject;
// ... you get the idea.

Reference Types

Variables of reference types, store references to the actual data. All Reference Types are derived from the ScriptMarshalByReferenceObject abstract base class.

  • Array (ScriptArrayMarshalByReference)
  • ByteArray
  • Image
  • Dictionary
  • FileSystem
  • Device Container

Reference Type Object Lifetime:

Scripting objects which inherit from MarshalByReferenceObject have a limited lifetime when passed between application domains (such as passing a ScriptArrayMarshalByReference object from one device to another or from a touch screen event to a device). Therefore you should always assume that the lifetime of the by reference scripting object will end when the script stops running and you should never hold a reference to a by reference script object which your class did not create. If you must have a reference to a scripting object then you can make a copy of the object by calling Serialize and Deserialize.

How to clone an IScriptObject:

You can simply get the primitive .Net type value and create a new script object or you can serialize and deserialize to a new script object.

Here's an example of cloning an IScriptObject:


// For simple types you can do something like the following:
ScriptString str1;
ScriptString str2 = new ScriptString((string)str1));

// You can also use Serialize and Deserialize:
ScriptString str1;
byte b[] = str1.Serialize();
ScriptString str2 = (ScriptString)ScriptObject.Deserialize(b);

Exposing Properties and Methods to Scripts

In order for the system to control devices and retrieve property device information the device driver must expose propeties and/or methods to the scripting language.

Exposing Properties to Scripts

You must decorate the property with the [ScriptObjectProperty] attribute for it to be accessible via a script.

Properties must return a derived type of the IScriptObject type, such as ScriptString, ScriptNumber, ScriptBoolean, ScriptDateTime, IScriptArray, ScriptByteArray, etc.

Property descriptions should start with "Gets or sets ..." or "Gets ...".

Property names should generally be nouns, while method names should be verbs.

Public property names should be upper camel cased. Examples: IsConnected, CurrentTemperature.

Instance Property

Example Property:


[ScriptObjectProperty("Is Dark Outside", "Gets a value indicating if it is dark outside.", "a boolean indicating if it is dark outside", null)]
public ScriptBoolean IsDarkOutside
{
    get
    {
        bool result = ...
        return new ScriptBoolean(result);
    }
}

Arrays

Arrays implement the IScriptArray and IScriptObject interfaces. Elve ships with two types of arrays, one is a value type and the other is a reference type. This allows the device driver developer to choose how the array should be passed between application domains.

Method parameters, return types, and property types which return an array should use the IScriptArray type.

  • ScriptArrayMarshalByValue
    Use the ScriptArrayMarshalByValue for class immutable arrays or arrays which do not need to send a set index value back to the driver. For example a read only list of zone names. This gives the best performance (10 times faster than ScriptArrayMarshalByReference) but index value changes are not detected by the source driver.

  • ScriptArrayMarshalByReference
    Use the ScriptArrayMarshalByReference class for arrays which use write callbacks or subclass arrays which update the driver when an index value is set. For example use this for a writable array returned from a driver's ZoneVolume property. When an index value is set the driver will be notified of the change so that the actual volume of the associated device can be updated. Due to the negative performance impact of this class we will likely be moving away from the practice of using it.

Read Only Array Property

It is recommended you store your array values in a local array that is base 0 (ie, the 1st element is at index 0) while you expose the elements to the user as base 1 since this is usually what user's expect. For example you would store zone 1's state value in _zoneStates[0].

Read only array properties can simply return a populated ScriptArrayMarshalByValue object and do not need special treatment.

There are a variety of ScriptArrayMarshalByValue constructors which accept primitive IEnumerables such as a primitive array of strings (e.g. string[]). The constructor will automatically convert the enumeration of primitive .net objects to the equivalent IScriptObject objects. For example if you pass in an array of strings, the constructor will populate the ScriptArrayMarshalByValue with ScriptString's.


[ScriptObjectPropertyAttribute("Zone Names", "Gets the name of all zones.", "The {NAME} zone name for zone #{INDEX|1}.", null)]
public IScriptArray ZoneNames
{
    get
    {
        // In the 1st constructor parameter, _zoneNames is of type string[].
        // In the 2nd constructor parameter, 1 is the index of the 1st element in the create ScriptArrayMarshalByValue.
        return new ScriptArrayMarshalByValue(_zoneNames, 1);
    }
}

You can also manually populate the ScriptArrayMarshalByValueas follows if needed:

[ScriptObjectProperty("Light Names", "Gets the names for all lights.", "the name for {NAME} light #{INDEX|0}", null)]
public IScriptArray LightNames
{
	get
	{
		ScriptArrayMarshalByValue array = new ScriptArrayMarshalByValue(true, false);
		for (int i = 0; i < MaxLightCount; i++)
			array.SetItemInternal(i + 1, new ScriptString(_lightNames[i]));

		return array;
	}
}

Writable Array Property - Using Callbacks

If a property returns a ScriptArrayMarshalByValue object, a copy of the actual array elements are being returned, so setting one of the array elements in a script will only set the returned array element and NOT the underlying device property index.

To support writable array properties, ScriptArrayMarshalByReference callbacks can be used to invoke a method in your driver whenever an array element is set. This is the recommended approach for writable array properties.

There are a variety of ScriptArrayMarshalByReference constructors which accept a callback delegate as a parameter. When a property index element is set the callback method will be invoked to process the action. In the example below the setZoneVolume method is invoked automatically when an element in the ZoneVolumes array is set:


[ScriptObjectPropertyAttribute("Zone Volumes", "Gets or sets the current volume setting for the player zones. The scale is 0 to 100.", 0, 100, "the {NAME} volume for zone #{INDEX|1}", "Set {NAME} zone #{INDEX|1} volume to {value|100}.")]
[SupportsDriverPropertyBinding("Zone Volume Changed", "Occurs when the current volume setting for the player changes.")]
public IScriptArray ZoneVolumes
{
    get
    {
        // If you have an int[] array dedicated to volumes then you could do this:
        return new ScriptArrayMarshalByReference(_zoneVolumes, new ScriptArraySetInt32Callback(setZoneVolume), 1);

        // If instead, each zone is an object and Volume is a property of the object then you could use LINQ to do this:
        //return new ScriptArrayMarshalByReference(_zones.Select(zone => (int)zone.Volume).ToArray(), new ScriptArraySetInt32Callback(setZoneVolume), 1);
    }
}
void setZoneVolume(int zoneIDBaseOne, int volume)
{
    sendCommand("VOLUME " + zoneIDBaseOne - 1 + " " + volume.ToString());
}

Writable Array Property - Using Wrapper Object

The following approach is still supported and there may be reasons to still choose it but it is no longer recommended for typical usage since it is more tedious to implement.

The old way to do this is by returning a wrapper object that inherits from ScriptArrayMarshalByReference, but contains references to all objects necessary to get and set the property value at any index.

The driver property should only have a get accessor and should return the ScriptArrayMarshalByReference wrapper object. The example below demonstrates a ScriptArrayMarshalByReference wrapper object that supports getting and setting light levels on a driver that implements the ILightingAndElectricalDriver interface. You will need to customize the example for your own needs, such as changing the type of value collection that is passed in the constructor, etc.


int[] _lightLevels; // assume this is an array of light levels.

[ScriptObjectProperty("Light Levels", "Gets the percent on level of the lights.")]
public IScriptArray LightLevels
{
    get
    {
        // To support setting/writing values within the array, return a wrapper array object.
        return new ScriptLightLevelArray(this, _lightLevels);
    }
}

The ScriptArrayMarshalByReference wrapper object must have a get and set accessor and should return the appropriate value for the specified index.


public class ScriptLightLevelArray : ScriptArrayMarshalByReference
{
	ILightingAndElectricalDriver _parentDriver;

	public ScriptElkPlcLevelArray(ILightingAndElectricalDriver parentDriver, int[] levels)
	{
		_parentDriver = parentDriver;

		for (int i = 0; i < 256; i++)
			this.SetItemInternal(i + 1, new ScriptNumber(levels[i]));
	}

	[ScriptObjectProperty("", "Gets or sets the light level for the specified index.", 0, 99)]
	public new ScriptNumber this[int index]
	{
		get
		{
			if (index < 1 || index > 256)
				throw new ArgumentOutOfRangeException("index", "The index must be from 1 to 256.");

			// Get the light level.
			return (ScriptNumber)base[index];
		}
		set
		{
			if (index < 1 || index > 256)
				throw new ArgumentOutOfRangeException("index", "The index must be from 1 to 256.");

			// Set the light level in the driver.
			_parentDriver.SetLightLevel(new ScriptNumber(index), value);
		}
	}
}

Property Binding

Property binding allows the Elve system to be immediately notified and in some cases react when a device property's value changes. Device properties must be handled by the developer in a couple simple ways to be available for property binding, which are described below.

Property Binding is used by the system in the following ways:


  • Touch Screen Viewer

When the property value changes, some touch screen controls can automatically have their display value updated. For example when a media player's volume changes, a slider control which is bound to the Volume property would automatically adjust to indicate the current volume level.

Some controls support automatically updating a property when interacted with. For example if a slider control is bound to a media player device's Volume property, when the slider thumb is moved the Volume property will automatically be set to the corresponding value. No control event action list commands are necessary.

  • Rules System

A device property can optionally be enrolled as an auto-generated property change event, which is described later in the Events section of this document. The gist is that a property value change event is automatically created for which rules can be created for. An event for the property change can still be manually created by the developer if additional event arguments or whenever filters are needed.

  • Elve Management Studio

When viewing a device's properties, any properties that support property binding will display the current property value without the need of refreshing the device information.

How to implement Property Binding:


The requirements for implementing property binding are:

1. The property's data type must be ScriptString, ScriptNumber, ScriptBoolean, ScriptDateTime, ScriptTimeSpan, or ScriptByteArray.
2. The property must be decorated with a [SupportsDriverPropertyBinding] attribute.
3. DevicePropertyChangeNotification(...) must be invoked when the underlying property value may have changed.

For a device property to be participate in property binding it must be decorated with the [SupportsDriverPropertyBinding] attribute AND DevicePropertyChangeNotification(...) must be invoked each time the property value may have changed. The system will only be notified of the property value change when DevicePropertyChangeNotification(...) is called with the given property name as a parameter. So for every property which is decorated with the [SupportsDriverPropertyBinding] there must be an associated call to DevicePropertyChangeNotification(...) when the property's underlying value may have changed.

Implementation details:

1. Property Data Type.

Property binding is only supported for properties with the following datatypes: ScriptString, ScriptNumber, ScriptBoolean, ScriptDateTime, ScriptTimeSpan, or ScriptByteArray.

2. The [SupportsDriverPropertyBinding] attribute.

The [SupportsDriverPropertyBinding] attribute lets the Elve system know that property supports property binding. For example the attribute lets some touch screen controls know that the property can be bound to the control.

The example below shows a simple Volume property which is decorated by the [SupportsDriverPropertyBinding] attribute.


[ScriptObjectProperty("Volume", "Gets or sets the current volume. (0-99)", 0, 99, "the {NAME} volume", "Set the {NAME} volume to {value|99}.")]
[SupportsDriverPropertyBinding]
public ScriptNumber Volume
{
    get
    {
        return new ScriptNumber(_volume);
    }
    set
    {
        // ... set volume ...
    }
}

3. DevicePropertyChangeNotification(...)

Each time the device property may have changed, the developer must invoke the DevicePropertyChangeNotification(...) method to let the system know what the property's current value is. The method automatically keeps track of the most recent property value (when it was last invoked) and if the value has not changed then the driver will not send out a notification. If the value has changed then the driver will notify the Elve system of the new property value.

When invoking DevicePropertyChangeNotification(...) the name of the property that may have changed must be specified. There are several method overrides to choose from. You may pass in just the property name for non-IScriptArray properties or you can also specify the new property value which is more efficient since the system does not need to use reflection to get the new property value. For IScriptArray properties, the index must also be specified when invoking DevicePropertyChangeNotification.

If you choose to pass in the property value to DevicePropertyChangeNotification(...), the allowed datatypes are: any numeric type, Enum, String, DateTime, Boolean, Byte[], ScriptString, ScriptNumber, ScriptBoolean, ScriptDateTime, ScriptTimeSpan, or ScriptByteArray.

In the example below, assume the driver has received an incoming data stream that includes the current volume value. The driver's local _volume value is set and then DevicePropertyChangeNotification("Volume", _volume) is invoked to notify the system of the current Volume property value. Invoking DevicePropertyChangeNotification does not change the driver's local internally stored property values and does not affect the publicly exposed properties, it simply notifies the system if the new value is different then the last time DevicePropertyChangeNotification was called for the same property. So in the example below, invoking DevicePropertyChangeNotification("Volume", _volume) does not change the value of the local _volume variable and it does not invoke the set accessor of the Volume property, it only reports the value to the system. The system will then determine if the volume value is different from the previous call to DevicePropertyChangeNotification("Volume" ...) and notify the system if the value is different.


// We'll use the class variable _volume to store the current volume. This is what the 
// Volume property will return.
int _volume; 

// Assume the logic below is in driver where an incoming data stream has been read
// and the data is in the receivedData byte array waiting to be processed.

// Get the current volume from the incoming binary data stream. This sets _volume which
// is the Volume property's underlying value, ie the Volume property's get accessor
// returns _volume.
_volume = (int)receivedData[3];

// Notify the system that the Volume property's value may have changed. Note that you don't
// need to check if the _volume value actually changed or if it was just set the to the same
// value that it was previously set to. You can just invoke DevicePropertyChangeNotification(...)
// and it will know since it keeps track each time it is called.
DevicePropertyChangeNotification("Volume", _volume);

// Alternative: You could have invoked DevicePropertyChangeNotification("Volume") instead. This is
// simpler but out of habit I usually pass in the current value since it is a little quicker 
// performance-wise because Elve doesn't need to use reflection to get the current property value.

Dynamic Instance Property

You may also override the base GetProperty() and SetProperty() methods to provide custom property handling within the driver.

The following is an example of the GetProperty method from the Global Variables driver:


public override ScriptObjectInvokeResult GetProperty(string propertyName, out IScriptObject result)
{
	Dictionary<string, IScriptObject> dict = _variables;
	if (dict.ContainsKey(propertyName))
	{
		result = dict[propertyName];
		return ScriptObjectInvokeResult.Success;
	}
	else
		return base.GetProperty(propertyName, out result);
}

Exposing Methods to Scripts

Method requirements:

  • You must decorate the method with the [ScriptObjectMethod] attribute for it to be accessible via a script.
  • Methods must have a return type of void or a derived type of the IScriptObject type, such as ScriptString, ScriptNumber, etc.
  • Method parameter types must be a derived type of the IScriptObject type, such as ScriptString, ScriptNumber, etc.

Because methods can be overridden, when calling a method from a script the parameter types must match.

Method names should generally be verbs, while property names should be nouns.

Public method names should be upper camel cased. Examples: ActivateTask(), TurnOnLight().

Public method parameter names should be lower camel cased. Examples: deviceID, userName.

Example Method:


[ScriptObjectMethod("Get Status", "Gets the status.")]
[ScriptObjectMethodParameter("ZoneID", "Zone ID (1-10)", 1, 10)]
public ScriptNumber GetStatus(ScriptNumber zoneID)
{
    return new ScriptNumber(status[zoneID]);
}

You may also override the base InvokeMethod() method to provide custom method handling within the driver as follows:


public override ScriptObjectInvokeResult InvokeMethod(string methodName, IScriptObject[] parameters, out IScriptObject result)
{
    // If the method name is "ZERO" then return a new ScriptNumber with a value of 0.
    // Otherwise allow the base object to process the method call normally.
    if (methodName.ToUpper() == "ZERO") 
    {
        result = new ScriptNumber(0);
        return ScriptObjectInvokeResult.Success;
    }
    else
        return base.InvokeMethod(methodName, parameters, out result);
}

Exposing Properties and Methods to Action Lists

The action list is a user friendly point-and-click interface alternative to scripting allowing the user to specify a list of actions to take, such as turning on a light or changing a tv channel. For ease of use it is highly recommended that you make as many properties and methods accessible to the action list as possible.

Earlier the [ScriptObjectProperty] and [ScriptObjectMethod] attributes were described as being required for a property or method to be accessible from a script. By adding a few parameters to these attributes the property or method will be accessible from the action list as well.

Supported Datatypes

The data types are supported by the action list editor are:

  • String
  • Number
  • Boolean
  • DateTime
  • TimeSpan

Other data types (including but not limited to Array and Byte Array) are not supported by the action list editor, except when using the scripting language.

Exposing Properties to Action Lists

To expose a property to the action lists you need to specify the [ScriptObjectProperty] attribute's scriptBuilderGetText and scriptBuilderSetText parameters.

In the example below both parameters are specified since the Volume can get retrieved and set:


[ScriptObjectProperty("Volume", "Gets or sets the current volume setting for the player. (0-100)", 0, 100, "the {NAME} volume", "Set the {NAME} volume to {value|100}.")]
[SupportsDriverPropertyBinding("Occurs when the current volume setting for the player changes.")]
public ScriptNumber Volume
{
  // get and set accessors...
}

As you can see there are a couple of macros in the newly added string parameters. These are described below.

The scriptBuilderGetText is always used as a parameter to a Set Accessor or Method since it only returns a value, in this case the volume. So for example a user using the action list could select a Text To Speack device's Speak action. The Speak action will have a parameter for what to speak. The user could then select the Squeezebox's volume to speak. In that case the action list text would look something like:

Speak the Squeezebox volume from the Text To Speech device.

The scriptBuilderSetText would look something like this in the action list:

Set the Squeezebox volume to 100.

Macro Definitions

  • NAME
    This macro will be replaced by the name of the current device.


  • INDEX
    This macro is only used for properties that return a IScriptArray or IScriptArray based object and identifies the index in the array

    Sytntax: { INDEX | <initial index> }

    initial index: This will be the initial index shown in the action list.


  • VALUE
    This indicates where to insert the set value into the displayed text.

    Non-ScriptBoolean Type Syntax: { VALUE | <initial value>}

    ScriptBoolean Type Syntax: { VALUE | <initial value> | boolean true text | boolean false text }

    initial value: This will be the initial value shown in the action list for the parameter

    For the ScriptBoolean type set this to true or false.

    For ScriptTimeSpan type use the format days.hours:minutes:seconds.milliseconds or hours:minutes:seconds for short.

    boolean true text: Specify this only if the type is boolean. This is the text that will display when the value is true.

    boolean false text: Specify this only if the type is boolean. This is the text that will display when the value is false.


Exposing Methods to Action Lists

To expose a method to the action lists you need to specify the [ScriptObjectMethod] attribute's scriptBuilderText parameter.

Let's update the GetStatus method's [ScriptObjectMethod] shown earlier to expose the method to the action list:


[ScriptObjectMethod("Get Status", "Gets the status.", "Get the {NAME} status for Zone {PARAM|0|1}.")]

As you can see there are a couple of macros in the newly added string parameter. When the text is displayed in the action list it would look something like:

Get the Anthem Receiver status for Zone 1.

Here's an example with 2 method parameters:


[ScriptObjectMethod("Set light level", "Sets the specified light's level to the specified percent.", "Set {NAME} light #{PARAM|0|1} to {PARAM|1|99}%.")]
[ScriptObjectMethodParameter("NodeID", "The id of the light.", 1, 255, "LightNames")]
[ScriptObjectMethodParameter("PercentOn", "The percent level to set the light to. Valid values: 0 to 99 where 0 is typically off and 99 is fully on.", 0, 99)]
public void SetLightLevel(ScriptNumber nodeID, ScriptNumber percentOn)
{
    // ...
}

When the text is displayed in the action list it would look something like:

Set Elk M1 light 1 to 99%.

Macro Definitions

  • NAME
    This macros will be replaced by the name of the current device.


  • PARAM
    This indicates which method parameter to insert and the initial value.

    Syntax: { PARAM | <method parameter index> | <initial value> | [boolean true text] | [boolean false text] }

    method parameter index: This specifies which method parameter to insert. The 1st method parameter is at index 0.

    initial value: This will be the initial value shown in the action list for the parameter.

    For boolean types set this to true or false.

    For TimeSpan types use the format days.hours:minutes:seconds.milliseconds or hours:minutes:seconds for short.

    boolean true text: Specify this only if the type is boolean. This is the text that will display when the value is true.

    boolean false text: Specify this only if the type is boolean. This is the text that will display when the value is false.


Events

Events bind device state changes to user defined actions. For example, you could use the SqueezeCenter driver's TrackChanged event as a trigger to update display text with the newly playing song name. Events are associated with Rules by the user via the Elve Management Studio application.

There are two types of events that can be declared in a driver:

  1. Auto-generated property change events.
  2. Custom events that are completely defined by the developer.

Auto-generated property change events

Any device property that is implements property binding can also have a property changed event generated by specifying a description for the event in the [SupportsDriverPropertyBinding] attribute. If the [SupportsDriverPropertyBinding] attribute has an event display name and description specified then when you create a Rule you will see an event for the device property. For example if a Volume property implements property binding and specifies an event display name and description then when creating a Rule you will see an event with the specified display name and description text.

So as you may have guessed this is just an extension to property binding. All the requirements for property binding still apply, you just have to specify a description for the event in the [SupportsDriverPropertyBinding] attribute.

The requirements are:

  1. The property must be decorated with a [SupportsDriverPropertyBinding] attribute.
  2. The [SupportsDriverPropertyBinding] attribute must specify an event description.
  3. DevicePropertyChangeNotification(...) must be invoked when the underlying property value may have changed.

See the Property Binding section of this document for more information.

1. To enroll a device property, it must be decorated with the [SupportsDriverPropertyBinding] attribute and include a display name and description for the event. The example below is the same as the example from the Property Binding section of this document but specifies an event description. The [SupportsDriverPropertyBinding] attribute optionally takes event display name and descriptions parameter which indicates to the driver that this property should auto-generate a property changed event and it should be triggered when DevicePropertyChangeNotification(...) is invoked and the value has changed. If the description is not specified, no event will be generated. If DevicePropertyChangeNotification(...) is not properly invoked, no event will be generated.


[ScriptObjectProperty("Volume", "Gets or sets the current volume. (0-99)", 0, 99, "the {NAME} volume", "Set the {NAME} volume to {value|99}.")]
[SupportsDriverPropertyBinding("Volume Changed", "Occurs when the volume changes.")] // notice the event description
public ScriptNumber Volume
{
    get
    {
        return new ScriptNumber(_volume);
    }
    set
    {
        // ... set volume ...
    }
}

2. When the DevicePropertyChangeNotification(...) method is invoked for the property and the underlying property value has changed, the event will be triggered and any rules that are associated with the event will be executed. The system will also be notified of the device property value change. See the Property Binding section of this document for more information on how to use the DevicePropertyChangeNotification(...) method.

The event arguments will include:

  • Index: The index in the array if the property is of type IScriptArray.
  • NewValue: The new property value.
  • PreviousValue: The previous property value.

The Index only exists if the property is an array.
The NewValue and PreviousValue only exist if the value type is a string, number, boolean, or date.

Example:


DevicePropertyChangeNotification("Volume", newVolume);

Custom Events

Custom Events are those events that are completely defined and controlled by the software developer, as opposed to the auto-generated property change events which are optionally associated with driver properties.

Declaring custom events in your driver is as simple as declaring a DriverEvent field and decorating with a [DriverEvent] attribute.

Event declaration requirements:

  • It must be a Field.
  • It's type must be of type DriverEvent.
  • It must be decorated with the [DriverEvent] attribute.

Event names should be upper camel cased. Examples: OutputChanged, LightLevelChanged.

Here is an example of a very simple custom device event declaration:


[DriverEvent("New Song", "Occurs when the song changes.")]
public DriverEvent NewSong;

After a device is started. the system will assign any DriverEvent fields and the HandleAddedRule method will be invoked for each rule added to the events.

Whenever Filters

Events can also have filters which allow the installer to filter when a Rule's action list is run. These filters appear in the Whenever tab when editing a Rule.

You can add filters to your event by decorating the event declaration with the [DriverEventParameter] attribute. This attribute can also indicate if the filter is optional or required. If the optional filter is not specified then it should be ignored, if it is specified then your driver should compare the specified value with the actual value before executing the rule.

If you use filters, the driver will compare the filter value with the appropriate driver value to determine if the rule should be executed. See the examples below.

Event Arguments

Sometimes you will want to make data related to the event accessible from a Rule's action list or script. This data is known as event arguments.

This is done by:
  1. Decorate the event declaration with the [DriverEventArg] attribute.
  2. Pass a DriverEventArgDictionary object to the RaiseDeviceEvent method.

When scripting, you can reference the event arguments from the EventArgs Object.

See the examples below.

Raising a Custom Device Event

The RaiseDeviceEvent method raises an event by executing associated rules with matching event parameters and notifying other devices and components that the event has occurred.

The following example uses both filters and event arguments. Filters and Event Arguments are not required and the use of one does not require the use of the other. However when you specify a filter it is customary to also add an event argument with the same name.

In the example the OutputID filter is required and the State filter is optional.

Event Declaration:

[DriverEvent("Output State Change", "Occurs when an output changes state.")]
[DriverEventParameter("OutputID", "Specifies the output id that this rule applies to (1-208).", 1, 208, true)]
[DriverEventParameter("State", "The event will be triggered if the new output state matches.", new string[]{"true", "false"}, false)]
[DriverEventArg("OutputID", "The ID of the output that changed state (1-208).", typeof(ScriptNumber))]
[DriverEventArg("PrevState", "The previous output state.", typeof(ScriptBoolean))]
[DriverEventArg("NewState", "The new output state.", typeof(ScriptBoolean))]
public DriverEvent OutputStateChange;

To raise the OutputStateChange event declared above you must invoke the RaiseDeviceEvent method. Since the event declaration also specified 3 event args and 2 event parameters we must also manually pass those to the RaiseDeviceEvent.

Raising the Event:

// Define the event arguments which will be available in the script for any rules which are executed for the event.
// These must use the same names as specified in the [DriverEventArg] attributes for the event.
DriverEventArgDictionary eventArgs = new DriverEventArgDictionary();
eventArgs.Add("OutputID", new ScriptNumber(outputID));
eventArgs.Add("PrevState", new ScriptBoolean((bool)prevOutputState));
eventArgs.Add("NewState", new ScriptBoolean((bool)_outputStates[outputID - 1]));

// Define the current event parameters which will be compared to the 'whenever filters' in any rules to determine which rules qualify for execution.
// These must use the same names as specified in the [DriverEventParameter] attributes for the event.
DriverEventParameterDictionary eventParameters = new DriverEventParameterDictionary();
eventParameters.Add("OutputID", outputID);
eventParameters.Add("State", (bool)_outputStates[outputID - 1]);

// Raise the event.
RaiseDeviceEvent(OutputStateChange, eventParameters, eventArgs);

The DriverEventParameterDictionary class has several helper Add() override methods which simplify adding items to it so the developer doesn't have to instantiate the appropriate IDriverEventParameter implementation. The helper methods support the most typical parameter comparisons such as numberic comparisons, string comparisons (both case-sensitive and case-insensitive), boolean comparisons, and date/time comparisons. These helper methods are really just instantiating the appropriate IDriverEventParameter object and adding it to the dictionary. If you need to do more advanced or custom comparisons you can define your own IDriverEventParameter class and add an instance of it to the dictionary as well.

The standard IDriverEventParameter types provided with Elve to use for comparison of rule whenever filters with actual current event parameters are:

  • StringDriverEventParameter : Provides case-sensitive, case-insensitive comparisons of strings as well as partial (Contains) comparisons of event parameters and rule whenever filters.
  • BooleanDriverEventParameter : Provides boolean comparisons of event parameters and rule whenever filters. The rule's whenever filter value is converted to a boolean using Convert.ToBoolean().
  • NumericDriverEventParameter : Provides numeric comparisons of event parameters and rule whenever filters. The rule's whenever filter value is converted to a double using Convert.ToDouble(). -1 can also optionally mean any numeric value is allowed.mean any numeric value is allowed.
  • DateTimeDriverEventParameter : Provides date/time comparisons of event parameters and rule whenever filters. The rule's whenever filter value must be in an ISO format.
  • DaysOfWeekDriverEventParameter : Provides support for comparisons of event parameters and rule whenever filters using DaysOfWeekDriverEventParameterEditor.
  • RegexDriverEventParameter : Provides support for comparisons of event parameters and rule whenever filters using regular expressions.

Executing A Custom Device Event Rule (DEPRECIATED)

This approach has been depreciated in favor of using the RaiseDeviceEvent method which is simpler to use and also sends out event notifications to other Elve devices and components.

Rules should be executed when the associated event occurs in the driver. So inside the event handler, call base.ExecuteDriverRuleSafely(...).

ExecuteDriverRuleSafely() will detect if the master server could not be contacted and will restart the slave server.

The following example uses both filters and event arguments. Filters and Event Arguments are not required and the use of one does not require the use of the other. However when you specify a filter it is customary to also add an event argument with the same name.

In the example the OutputID filter is required and the State filter is optional.

Event Declaration:

[DriverEvent("Output State Change", "Occurs when an output changes state.")]
[DriverEventParameter("OutputID", "Specifies the output id that this rule applies to (1-208).", 1, 208, true)]
[DriverEventParameter("State", "The event will be triggered if the new output state matches.", new string[]{"true", "false"}, false)]
[DriverEventArg("OutputID", "The ID of the output that changed state (1-208).", typeof(ScriptNumber))]
[DriverEventArg("PrevState", "The previous output state.", typeof(ScriptBoolean))]
[DriverEventArg("NewState", "The new output state.", typeof(ScriptBoolean))]
public DriverEvent OutputStateChange;

Filtering and executing the Rules associated with the event:

foreach (IRule rule in OutputStateChange.Rules)
{
    // If the OutputID matches and the old output value is not unknown.
    if (int.Parse(rule.EventParameters["OutputID"]) == outputID)
    {
        bool trigger = true;
        if (rule.EventParameters.ContainsKey("State"))
            if (bool.Parse(rule.EventParameters["State"]) != _outputStates[outputID - 1])
                trigger = false;

        if (trigger)
        {
            Dictionary<string, IScriptObject> eventArgs = new Dictionary<string, 	>();
            eventArgs.Add("OutputID", new ScriptNumber(outputID));
            eventArgs.Add("PrevState", new ScriptBoolean(prevOutputState));
            eventArgs.Add("NewState", new ScriptBoolean(_outputStates[outputID - 1]));

            base.ExecuteDriverRuleSafely(rule, eventArgs);
        }
    }
}

Here is an example from the DateTimeDriver:

 csharpvoid everyXMinutesTimer_Elapsed(object sender, ElapsedEventArgs e)
{
    Timer t = sender as Timer;
    IRule rule = _timers[t];

    base.ExecuteDriverRuleSafely(rule);
}

You can also pass event arguments to the rule as follows. The event arguments are then available in the script as properties of the EventArgs object.

 csharpvoid everyXMinutesTimer_Elapsed(object sender, ElapsedEventArgs e)
{
    Timer t = sender as Timer;
    IRule rule = _timerAndRules[t];

    Dictionary<string, IScriptObject> parameters = new Dictionary<string, IScriptObject>();
    parameters.Add("Color", new ScriptString("Blue")); // just a bogus event argument for example purposes
    parameters.Add("Ten", new ScriptNumber((double)10)); // just a bogus event argument for example purposes

    base.ExecuteDriverRuleSafely(rule, parameters);
}

Here's an example of using optional event parameters from the Nuvo Concerto driver:


// Declaration of the MacroExecuted event.
[DriverEvent("Macro Executed", "Occurs when a macro is executed, which can be caused by a button press on a display pad, or the sending of an rs-232 macro command.")]
[DriverEventParameter("Source", "The source number (1-6).", 1, 6, false)]
[DriverEventParameter("Zone", "The zone number (1-20).  For macros in which the zone # does not apply, the zone will be zero (0).", 1, 20, false)]
[DriverEventParameter("Macro", "The name of the macro.", new string[]{"PLAY", "PAUSE", "STOP", "RWD", "FWD", "FRWD", "FFWD", "ENTER", "CONT", "SHUF", "GRP", "DISC", "ZERO", "ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX", "SEVEN", "EIGHT", "NINE", "+TEN"}, false)]
[DriverEventArg("Source", "The source number (1-6).", typeof(ScriptNumber))]
[DriverEventArg("Zone", "The zone number (1-20).  For macros in which the zone # does not apply, the zone will be zero (0).", typeof(ScriptNumber))]
[DriverEventArg("Macro", "The name of the macro.", typeof(ScriptString))]
public DriverEvent MacroExecuted;

...

// Create event arguments
Dictionary<string, IScriptObject> eventArgs = new Dictionary<string, IScriptObject>();
eventArgs.Add("SOURCE", new ScriptNumber(sourceNumber));
eventArgs.Add("ZONE", new ScriptNumber(zoneNumber));
eventArgs.Add("MACRO", new ScriptString(macro));

// Execute the rules
foreach (IRule rule in MacroExecuted.Rules)
{
	bool trigger = true;

	if (rule.EventParameters.ContainsKey("SOURCE"))
		if (int.Parse(rule.EventParameters["SOURCE"]) != sourceNumber)
			trigger = false;

	if (rule.EventParameters.ContainsKey("ZONE"))
		if (int.Parse(rule.EventParameters["ZONE"]) != zoneNumber)
			trigger = false;

	if (rule.EventParameters.ContainsKey("MACRO"))
		if (string.Compare(rule.EventParameters["MACRO"], macro, true) != 0)
			trigger = false;

	if (trigger)
		base.ExecuteDriverRuleSafely(rule, eventArgs);
}

Auto-generated property change events -vs- Custom Events

There are sometimes situations where you have a property that could be set up as an auto-generated property change event but you would like to be able to filter the event or expose event arguments. In this situation you should just use a custom event.

There are other times when an auto-generated property change event might give unexpected results. See below for an example:

A media player driver might have a TrackTitle property that is decorated with the SupportsDriverPropertyBinding attribute. If for example you provided the event description text to automatically create an event the user might think that the the event would be triggered everytime the track changed. HOWEVER, this would not be the case. Imagine the situation where you have 2 tracks with the same title that play back to back, the autogenerated Track Title Changed event would not fire after the first song changed because the track name did not change. In this situation it is better to create a custom TrackChanged event.

When is the Device ready for use?

There are periods of time when the device properties may not contain valid information and invoking device methods may not generate expected results, such as when the device is first starting up or when the hardware is disconnected.

The read-only Ready property indicates if the driver is in a state in which it can be used. The Ready property will always return false when the device is starting up and stopping, but the driver developer can control the return value at other times by setting the ReadyInternal property value from the driver.

The boolean return value of StartDriver() should return the initial ready state of the driver. This is usually true for virtual devices and usually false for drivers which communicate with external hardware because they will likely still be pending a connection and query responses.

The ReadyInternal property should usually be set to false when a connection to the hardware is lost, and should be set to true after a connection is established and all properties have been updated.

Specifying Configuration Files

Drivers can use an auxiliary configuration file(s) when necessary. For example the Caller Id Modem driver uses the "Contacts.xml" file to read contact information (which can be shared with other drivers as well).

The master server will pass the file contents to the driver via the StartDriver() method.

Example Method from the Caller Id Modem driver:


public override string[] ConfigurationFileNames
{
	get
	{
		// This driver needs the contacts.xml file to cross reference a phone numbers to contact info.
		return new string[] { "Contacts.xml" };
	}
}

Custom Driver Event Parameter Editors

Driver event parameters are edited using objects (forms) implementing the IDriverEventParameterEditor interface.

To associate a driver event parameter with a custom editor, specify the editor's type in the [DriverEventParameter] attribute.


[DriverEventParameter("IRCodeName", "The IR device and IR code name to filter on using the following format: irdevicename.ircodename", typeof(IRDeviceCodeRuleParameterEditor), false)]

The resulting value from the editor must be either a simple string or an xml string.

Inter-Device Communication

Inter-device communication is implemented through the use of device methods, properties, event handling, and device property notifications.

Any driver can invoke methods on other drivers using the following Driver class method:

  • InvokeDeviceMethod

Drivers can also get and set other driver properties using the following Driver class methods:

  • GetDevicePropertyValue
  • SetDevicePropertyValue
  • ToggleBooleanDevicePropertyValue
  • OffsetNumericDevicePropertyValue

Drivers can even run scripts using the following Driver class method:

  • RunScript

Any driver can also respond to events which occur in other drivers, or property changes by overriding either of the following Driver class methods:

  • HandleDevicePropertyChangeNotification
  • HandleDeviceEventNotification

Example 1

An example use of inter-device communication would be creating an X10 thermostat device driver which uses an existing X10 device driver (such as Insteon PLM or ACT TI103) for sending and receiving X10 commands. Instead of implementing the X10 hardware communication layer within the thermostat driver we can associate the thermostat driver with an existing X10 driver via a device setting... you only need to know the name of the X10 device.

  • Sending X10 commands:
    We know that X10 drivers implement the IX10Driver interface so that guarantees that the X10 driver will have SendX10PCommand and SendX10PresetDimCommand methods. These methods allow our thermostat driver to send X10 commands using any X10 driver which implements the IX10Driver interface.

  • Receiving X10 data:
    While the IX10Driver interface does not enforce events (since they are fields), X10 drivers should include events named ReceivedX10Command and ReceivedX10PresetDim. To receive incoming X10 data from the X10 driver, we can override the HandleDeviceEventNotification method in the thermostat driver class and listen for ReceivedX10Command and ReceivedX10PresetDim events to receive X10 data from the thermostat.

Example 2

We could create a device driver which creates timeline line-graph images of device property values of other drivers. The graphing driver could use the GetDevicePropertyValue method to retrieve device property values at timed intervals and then generate graph images based on the property value history.

Infrared Drivers

Be sure to read the Infrared section of the Elve Management Studio Application manual.

When implementing an IR driver, the class must inherit from IRDriverBase instead of Driver. The IRDriverBase provides the following:

  • It automatically adds an IR Devices device configuration setting to your driver. You'll see it in the device settings tab.
  • Access to the ir devices assigned to the driver from the IR Library through the getIRCodeByIRDeviceDotIRCodeName() method.
  • It automatically creates dynamic driver properties for each IR device so the scripting language can use the following syntax: uirt.MyTV.PowerOn();

When the driver is notified of an incoming IR code, in order to process any Rules which specify an ir code formatted as irdevicename.ircodename, you will need to call base.getIRCodeByIRDeviceDotIRCodeName( "mysonytv.PowerOn" ). It basically gets a collection of named ir codes for the specified irdevicename.ircodename. You can then compare the incoming ir code to the filter's ir command name's ir code.

Driver Class Members

Instance Methods

StartDriver( Dictionary configFileData )

Starts the driver. This typically sets any class variables, sets up communications ports, and hooks any event handlers such as SerialCommunication ReceivedBytes, etc. This method should be overridden in every device driver.

Syntax

System.Boolean StartDriver( Dictionary configFileData )

Parameters

configFileData : The supplementary configuration files that this driver uses.

Return Value

A value indicating if the driver is ready for use when StartDriver() exists. If not, return false and then set the IsReady property to true when the driver is ready for use (which is usually after all properties are set by retrieving them from a serial or tcp connection).

StopDriver( )

Stops the driver by unhooking any event handlers and releasing any used resources. This method should be overridden in every device driver.

Syntax

void StopDriver( )

RaiseDeviceEvent(DriverEvent driverEvent, DriverEventParameterDictionary eventParametersToMatch, DriverEventArgDictionary eventArgs)

Raises the event by executing associated rules with matching event parameters and notifying other devices and components that the event has occurred.

Syntax

void RaiseDeviceEvent(DriverEvent driverEvent, DriverEventParameterDictionary eventParametersToMatch, DriverEventArgDictionary eventArgs)

Parameters

driverEvent : The event to raise.
eventParameters : The actual parameters for the event which will be compared with the rule's event parameter filters.
eventArgs : The event argument values which will be available via the scripting language when executing any rules.

ExecuteDriverRuleSafely(IRule rule)

This method has been depreciated in favor of the RaiseDeviceEvent method.

Execute a rule and handles communications problems with the master server.

Syntax

void ExecuteDriverRuleSafely(IRule rule)

Parameters

rule : The rule to execute.

ExecuteDriverRuleSafely(IRule rule, DriverEventArgDictionary eventArgs)

This method has been depreciated in favor of the RaiseDeviceEvent method.

Execute a rule and handles communications problems with the master server.

Syntax

void ExecuteDriverRuleSafely(IRule rule, DriverEventArgDictionary eventArgs)

Parameters

rule : The rule to execute.
eventArgs : The parameters to pass to the rule for use in the script EventArgs object.

DevicePropertyChangeNotification(string propertyName)

Notifies the system that the specified device property value may have changed and triggers the associated event if applicable. The system will automatically compare the value with the property's previous value so this method can be called even when the value did not change. Touch Screen Client applications are notified of changes so the display can be updated if needed. The property must be a member of this driver and must be decorated with the [SupportsPropertyBinding] attribute.

Calling this with the name of a property of type IScriptArray will send notifications for each array element (enumerating the entire array).

The property's value will automatically be retrieved using this overload.

Syntax

void DevicePropertyChangeNotification(string propertyName)

Parameters

propertyName : The name of the property. The property must be a member of this driver and must be decorated with the [SupportsPropertyBinding] attribute. If the property is of type IScriptArray, notifications will be sent for each array element.

Remarks

The spelling of the name of the property that may have changed must match the property name exactly (it is case-insensitive). There are several method overrides to choose from. You may pass in just the property name for non-IScriptArray properties or you can also specify the new property value which is more efficient since the system does not need to use reflection to get the property value. For IScriptArray properties, the array index parameter must also be specified.

DevicePropertyChangeNotification(string propertyName, object newPropertyValue)

Notifies the system that the specified device property value may have changed and triggers the associated event if applicable. The system will automatically compare the value with the property's previous value so this method can be called even when the value did not change. Touch Screen Client applications are notified of changes so the display can be updated if needed. The property must be a member of this driver and must be decorated with the [SupportsPropertyBinding] attribute.

Syntax

void DevicePropertyChangeNotification(string propertyName, object newPropertyValue)

Parameter

propertyName : The name of the property. The property must be a member of this driver and must be decorated with the [SupportsPropertyBinding] attribute.

newPropertyValue : The value of the property. Valid datatypes are String, numeric types, Boolean, DateTime, byte[], ScriptString, ScriptNumber, ScriptBoolean, ScriptDateTime, ScriptByteArray, ScriptImage.

Remarks

The spelling of the name of the property that may have changed must match the property name exactly (it is case-insensitive). There are several method overrides to choose from. You may pass in just the property name for non-IScriptArray properties or you can also specify the new property value which is more efficient since the system does not need to use reflection to get the property value. For IScriptArray properties, the array index parameter must also be specified.

DevicePropertyChangeNotification(string propertyName, int propertyIndex, object newPropertyValue)

Notifies the system that the specified device property array element value may have changed and triggers the associated event if applicable. The system will automatically compare the value with the property's previous value so this method can be called even when the value did not change. Touch Screen Client applications are notified of changes so the display can be updated if needed. The property must be a member of this driver and must be decorated with the [SupportsPropertyBinding] attribute.

Syntax

void DevicePropertyChangeNotification(string propertyName, int propertyIndex, object newPropertyValue)

Parameters

propertyName : The name of the property. The property type must implement IScriptArray, be a member of this driver, and must be decorated with the [SupportsPropertyBinding] attribute.

propertyIndex : The index of the value in the IScriptArray.

newPropertyValue : The value of the property. Valid datatypes are String, numeric types, Boolean, DateTime, byte[], ScriptString, ScriptNumber, ScriptBoolean, ScriptDateTime, ScriptByteArray, ScriptImage.

Remarks

The spelling of the name of the property that may have changed must match the property name exactly (it is case-insensitive). There are several method overrides to choose from. You may pass in just the property name for non-IScriptArray properties or you can also specify the new property value which is more efficient since the system does not need to use reflection to get the property value. For IScriptArray properties, the array index parameter must also be specified.

HandleDevicePropertyChangeNotification(DevicePropertyValueChange notification)

The method can be overridden to handle property change notifications which occur in any device. This method should never be called by the driver. Whenever any device calls DevicePropertyChangeNotification(), this method will be called to give any devices a chance to handle the change notification.

Syntax

virtual void HandleDevicePropertyChangeNotification(DevicePropertyValueChange notification)

Parameters

notification : Contains information about the device property value change.

HandleDeviceEventNotification(string deviceName, string eventName, Dictionary eventArgs)

This method can be overridden to handle events which occur in any device. This method should never be called by the driver.

Syntax

virtual void HandleDeviceEventNotification(string deviceName, string eventName, Dictionary eventArgs)

Parameters

deviceName : The unique name (aka Scripting Identifier) of the device in which the event occurred.
eventName : The member name of the event which occurredwithin the specified device.
eventArgs : The event arguments.

HandleAddedRule(DriverEvent driverEvent, IRule rule)

This method can be overridden to handle rule a rule which has been added to a driver event. Most drivers do not need to override this. This gets invoked for each rule that is added to the driver when the device starts up and when a rule is changed or added to the system later.

Syntax

virtual void HandleAddedRule(DriverEvent driverEvent, IRule rule)

Parameters

driverEvent : The event which the rule was added to. You can compare this reference to the events in the driver.
rule : The added rule.

HandleRemovedRule(DriverEvent driverEvent, IRule rule)

This method can be overridden to handle rule a rule which has been added to a driver event. Most drivers do not need to override this. This gets invoked when a rule is deleted or changed.

Syntax

virtual void HandleRemovedRule(DriverEvent driverEvent, IRule rule)

Parameters

driverEvent : The event which the rule was removed from. You can compare this reference to the events in the driver.
rule : The rule which was added.

InvokeDeviceMethod(string deviceName, string methodName, params IScriptObject[] parameterValues)

Invokes the specified device method. This allows a device to invoke a method on any other device. Do not use this to invoke a method in the current device as that would be very inefficient. This method will throw an exception if the device method is inaccessible, for example if the device is disabled, the driver service isn't running, or the device doesn't exist. Syntax

IScriptObject InvokeDeviceMethod(string deviceName, string methodName, params IScriptObject[] parameterValues)

Parameters

deviceName : The name of the device (also known as scripting identifier or alias) (case insensitive).
methodName : The name of the device method (case insensitive).
parameterValues : The parameter values to pass to the method in the order they are declared.

Returns

The result of the method invocation.

Example:

try
{
    InvokeDeviceMethod("lighting", "TurnOnLight", new ScriptNumber(3));
}
catch
{
    // failed to retrieve property. (The device may not exist, may be disabled, the driver service might not be connected, etc.)
}

GetDevicePropertyValue(string deviceName, string propertyName)

Gets the value of the specified device and property. This allows a device to retrieve a property value from any other device. Do not use this to get a property value from the current device as that would be very inefficient. This method will throw an exception if the device property is inaccessible, for example if the device is disabled, the driver service isn't running, or the device doesn't exist.

Syntax

IScriptObject GetDevicePropertyValue(string deviceName, string propertyName)

Parameters

deviceName : The name of the device (also known as scripting identifier or alias) (case insensitive).
propertyName : The name of the device property (case insensitive).

Returns

The value of the device property.

Example:

try
{
    ScriptBoolean sb = (ScriptBoolean)GetDevicePropertyValue("lighting", "DeviceIsReadyForUse");
    bool b = (bool)sb;
}
catch
{
    // failed to retrieve property. (The device may not exist, may be disabled, the driver service might not be connected, etc.)
}

GetDevicePropertyValue(string deviceName, string propertyName, int propertyIndex)

Gets the value of a property array element from a device. This allows a device to retrieve a property value from any other device. Do not use this to get a property value from the current device as that would be very inefficient. This method will throw an exception if the device property is inaccessible, for example if the device is disabled, the driver service isn't running, or the device doesn't exist.

Syntax

IScriptObject GetDevicePropertyValue(string deviceName, string propertyName, int propertyIndex)

Parameters

deviceName : The name of the device (also known as scripting identifier or alias (case insensitive)).
propertyName : The name of the device property array (case insensitive).
propertyIndex : The index of the array element to get.

Returns

The value of the device property.

Example:

try
{
    ScriptNumber sb = (ScriptNumber)GetDevicePropertyValue("lighting", "LightLevels", 12); // get the light level value for light #12.
    int = (int)sb;
}
catch
{
    // failed to retrieve property. (The device may not exist, may be disabled, the driver service might not be connected, etc.)
}

SetDevicePropertyValue(string deviceName, string propertyName, IScriptObject value)

Sets the value of the specified device property. This allows a device to set a property value in any other device. Do not use this to set a property value in the current device as that would be very inefficient. This method will throw an exception if the device property value can not be set, for example if the device is disabled, the driver service isn't running, or the device doesn't exist.

Syntax

void SetDevicePropertyValue(string deviceName, string propertyName, IScriptObject value)

Parameters

deviceName : The name of the device (also known as scripting identifier or alias) (case insensitive).
propertyName : The name of the device property (case insensitive).
value : The value to set the property to.

SetDevicePropertyValue(string deviceName, string propertyName, int propertyIndex, IScriptObject value)

Sets the value of the specified device property array element. This allows a device to set a property value in any other device. Do not use this to set a property value in the current device as that would be very inefficient. This method will throw an exception if the device property is inaccessible, for example if the device is disabled, the driver service isn't running, or the device doesn't exist.

Syntax

void SetDevicePropertyValue(string deviceName, string propertyName, int propertyIndex, IScriptObject value)

Parameters

deviceName : The name of the device (also known as scripting identifier or alias) (case insensitive).
propertyName : The name of the device property (case insensitive).
propertyIndex : The index of the array element to set.
value : The value to set the property to.

ToggleBooleanDevicePropertyValue(string deviceName, string propertyName)

Toggles the specified boolean device property's state. Do not use this to toggle a boolean property value in the current device as that would be very inefficient. This method will throw an exception if the device property is inaccessible, for example if the device is disabled, the driver service isn't running, or the device doesn't exist.

Syntax

void ToggleBooleanDevicePropertyValue(string deviceName, string propertyName)

Parameters

deviceName : The name of the device (also known as scripting identifier or alias) (case insensitive).
propertyName : The name of the device property (case insensitive).

ToggleBooleanDevicePropertyValue(string deviceName, string propertyName, int propertyIndex)

Toggles the specified boolean device property array element's state. Do not use this to toggle a boolean property value in the current device as that would be very inefficient. This method will throw an exception if the device property is inaccessible, for example if the device is disabled, the driver service isn't running, or the device doesn't exist.

Syntax

void ToggleBooleanDevicePropertyValue(string deviceName, string propertyName, int propertyIndex)

Parameters

deviceName : The name of the device (also known as scripting identifier or alias) (case insensitive).
propertyName : The name of the device property (case insensitive).
propertyIndex : The index of the boolean array element to toggle.

OffsetNumericDevicePropertyValue(string deviceName, string propertyName, double offset)

Increments or Decrements the specified numeric device property's value. The property type must be ScriptNumeric. Do not use this to offset a numeric property value in the current device as that would be very inefficient. This method will throw an exception if the device property is inaccessible, for example if the device is disabled, the driver service isn't running, or the device doesn't exist.

Syntax

void OffsetNumericDevicePropertyValue(string deviceName, string propertyName, double offset)

Parameters

deviceName : The name of the device (also known as scripting identifier or alias) (case insensitive).
propertyName : The name of the device property (case insensitive).
offset : The positive or negative value to offset the current property value by.

OffsetNumericDevicePropertyValue(string deviceName, string propertyName, int propertyIndex, double offset)

Increments or Decrements the specified numeric device property's value. The property type must be ScriptNumeric. Do not use this to offset a numeric property value in the current device as that would be very inefficient. This method will throw an exception if the device property is inaccessible, for example if the device is disabled, the driver service isn't running, or the device doesn't exist.

Syntax

void OffsetNumericDevicePropertyValue(string deviceName, string propertyName, int propertyIndex, double offset)

Parameters

deviceName : The name of the device (also known as scripting identifier or alias) (case insensitive).
propertyName : The name of the device property (case insensitive).
propertyIndex : The index of the boolean array element to offset.
offset : The positive or negative value to offset the current property value by.

RunScript(string scriptName, string script, bool throwError)

Run a script.

Syntax

IScriptObject RunScript(string scriptName, string script, bool throwError)

Parameters

scriptName : The name of the script to use when logging errors.
script : The script contents to run.
throwError : Indicates if an exception should be thrown if an error occurs when running the script.

Returns

The return value of the script.

RunScript(string scriptName, string script, bool throwError, bool runAsync, params object[] variables_Name_comma_ScriptObject)

Run a script.

Syntax

IScriptObject RunScript(string scriptName, string script, bool throwError, bool runAsync, params object[] variables_Name_comma_ScriptObject)

Parameters

scriptName : The name of the script to use when logging errors.
script : The script contents to run.
throwError : Indicates if an exception should be thrown if an error occurs when running the script.
runAsync : Indicates if the script should be run asynchronously and the method should be non-blocking.
variables_Name_comma_ScriptObject : An alternating list of variable name and variable values. Ex: "var1", var1, "var2", var2. Where var1 and var2 are of type IScriptObject.

Returns

The return value of the script.

SetPropertyAfterDelayAsync(string key, TimeSpan duration, string propertyName, IScriptObject propertyValue)

Schedules a device property to be set asynchronously after a specified amount of time. Subsequent sets of the same property using the same key parameter will reset the timer if the timer has not yet elapsed.

Syntax

void SetPropertyAfterDelayAsync(string key, TimeSpan duration, string propertyName, IScriptObject propertyValue)

Parameters

key : A unique key so that subsequent calls can reset the timer if the key is the same.
duration : The amount of time to wait after setting the initial value to set the subsequent value.
propertyName : he property to set.
propertyValue : The value to set the property to after the duration of time has passed.

SetPropertyAfterDelayAsync(string key, TimeSpan duration, string propertyName, int arrayIndex, IScriptObject propertyValue)

Schedules a device property array element to be set asynchronously after a specified amount of time. Subsequent sets of the same property using the same key parameter will reset the timer if the timer has not yet elapsed.

Syntax

void SetPropertyAfterDelayAsync(string key, TimeSpan duration, string propertyName, int arrayIndex, IScriptObject propertyValue)

Parameters

key : A unique key so that subsequent calls can reset the timer if the key is the same.
duration : The amount of time to wait after setting the initial value to set the subsequent value.
propertyName : The property to set.
arrayIndex : The property's array index to set.
propertyValue : The value to set the property to after the duration of time has passed.

InvokeMethodAfterDelayAsync(string key, TimeSpan duration, string methodName, IScriptObject[] methodParameters)

Schedules a device method to be invoked asynchronously after a specified duration of time. Subsequent calls to the same method using the same key parameter will reset the timer if the timer has not yet elapsed.

Syntax

void InvokeMethodAfterDelayAsync(string key, TimeSpan duration, string methodName, IScriptObject[] methodParameters)

Parameters

key : A unique key so that subsequent calls can reset the timer if the key is the same.
duration : The amount of time to wait after invoking the first method to invoke the second method.
methodName : The name of the second method to invoke after the duration of time has passed.
arrayIndex : The property's array index to set.
methodparameters : The method parameters to pass to the second method after the duration of time has passed.

Example

In the example below, TurnOnLightForDuration() will turn on a light, then it invokes InvokeMethodAfterDelayAsync which schedules the TurnOffLight method be invoked after a certain amount of time on a background thread. InvokeMethodAfterDelayAsync is non-blocking. If TurnOnLightForDuration() is called again for the same light id (see the key parameter) before the timer elapses then the timer will be reset to the specified amount of time.

A great use of this is to use it in a motion detected rule to turn on a light for 30 seconds. Each time motion is detected the light will be turned on for 30 seconds... if it is called before the light turns off then it effectively extends the amount of time that the light stays on.


[ScriptObjectMethodAttribute("Turn On Light For Duration", "Turns a light fully on for a duration of time and then turns the light off.", "Turn on {NAME} light #{PARAM|0|1} for a duration of {PARAM|1|00:00:30}.")]
[ScriptObjectMethodParameter("ID", "The id of the light.", 1, 256, "LightNames")]
[ScriptObjectMethodParameterAttribute("Duration", "The amount of time to wait before turning off the light.", 1, int.MaxValue)]
public void TurnOnLightForDuration(ScriptNumber id, ScriptTimeSpan duration)
{
    TurnOnLight(id);
    InvokeMethodAfterDelayAsync("TurnOffLight" + (int)id, (TimeSpan)duration, "TurnOffLight", new IScriptObject[] { id });
}

Instance Properties

DeviceName

Gets the unique device identifier name.

DeviceDisplayNameInternal

Gets the display name of the device.

DriverDisplayNameInternal

Gets the display name of the driver.

Logger

Gets an ILogger object which is used to write to the Elve log and it can also be passed to other objects to log within the context of this device.

DeviceStartTimeInternal

Gets the date and time when the driver started.

IsReady

Gets or sets whether all properties are valid and all methods are ready to be used. This value is initially set to the return value of StartDriver(), then the driver developer can change this property based on the whether the device is connected to the hardware and it's ready to be used.

ConfigurationFileNames

Returns a list of configuration file names that the driver requires. The files will be read from the same folder that the master server configuration file exists in. If your driver needs configuration file(s), override this property. By default this returns an empty array.

LocalDeviceDataDirectoryPath

Returns the directory path that the driver can use to store data on the machine running the driver. The driver is responsible for creating the directory before reading/writing to it.

Obsolete Members

Properties and Methods which should no longer be available for use but can not be removed because they would break backwards compatibility should be decorated with the [Obsolete] attribute.

Properties and Methods which are marked with the [Obsolete] attribute will still continue to work and will still show up in action lists if they were previously added to the action list, however they will not be shown in the "Add Action" menu of the action list or other user interfaces.

Serial, TCP, UDP, & USB I/O Communication

If you are creating a driver that communicates with hardware via the serial port, TCP, UDP or USB(HID) it is HIGHLY recommended that you utilize the following communication classes which derive from ICommunication:

  • CodecoreTechnologies.Elve.DriverFramework.Communication.TcpCommunication
  • CodecoreTechnologies.Elve.DriverFramework.Communication.UdpCommunication
  • CodecoreTechnologies.Elve.DriverFramework.Communication.SerialCommunication
  • CodecoreTechnologies.Elve.DriverFramework.Communication.UsbHidCommunication

If you use one of these classes in your driver class please assign the object to the ICommunication type.

These classes offer the following benefits:

  • The classes are interchangeable since they implement the same interface.
  • Automatic connection monitoring and reconnection with events for connections and lost connections. (Excludes UDP since it is a connection-less protocol).
  • Automatic incoming message parsing based on a specified end of line delimiter.
  • Supports binary and non-binary protocols.
  • Automatic string decoding for ASCII, UTF8, or other non-binary based protocols.
  • Has a built in received data buffer.

Connection Monitoring

Connection monitoring detects when a connection is lost and automatically attempts to reconnect. If no data has been received within a specified amount of time the connection monitor can send a query to the host which should cause a response. If there is no response then the connection is assumed to have been lost. This works for serial, usb and tcp connections. TCP connection drops are also detected. The UdpCommunication class does not support connection monitoring since UDP is a connection-less protocol.

To enable connection monitoring:

1. Set the ConnectionMonitorTimeout property to the number of milliseconds of no data received before the test request/heartbeat is sent to request a response.

2. Assign the query command to be sent as a query to request data from the host, or use the ConnectionMonitorTest event for more complicated queries. The query command should contain a complete command including any end of line marker.

ConnectionMonitorTestRequest: Assign this for ASCII, UTF-8, or other non-binary based protocols.
ConnectionMonitorTestBytes: Assign this for binary protocols.
ConnectionMonitorTest : For more request/heartbeat control you can subscribe to this event instead of setting ConnectionMonitorTestRequest or ConnectionMonitorTestBytes.

3. Subscribe to the ConnectionEstablished event. This event should contain any initialization commands or requests needed to retreive the initial state from the host. These command should NOT be placed in the StartDriver method. This is because the user may not have the device connected to the pc when the driver starts or it may get disconnected/reconnected later, etc.

4. Subscribe to the ConnectionLost event.

5. Call StartConnectionMonitor() to start monitoring. This will also attempt to open the connection in the background.

If not using connection monitoring, you may call Open to open the connection. You may also subscribe to the ConnectionAttemptFailed event to detect when and why a connection attempt failed.

Text Encoding/Decoding

The CurrentEncoding property should be set according to the type of protocol used. This property is used to decode incoming bytes and encode outgoing string data to bytes. The default is System.Text.Encoding.ASCII which is what most hardware devices use.

  • Completely Text Based Protocols
    The default uses ASCII but in some cases you may be to chose UTF-8, etc.


  • Binary Protocols:
    You must set CurrentEncoding property to null to avoid triggering the text based receivied data events.

Receiving Data

There are 3 events which can be used to process incoming data. You would usually only use one of them based on the type of protocol.

  • ReceivedDelimitedString
    This event occurs when using a text based protocol (such as ASCII) which uses a consistent end of line marker and a full message has been received. A delimited string is detected by using the value in the Delimiter property. If the Delimiter property is null this event will not occur. The EventArgs contain the delimited string. The IncludeDelimiterInRawResponse property indicates if the delimited string will include the delimiter.

  • ReceivedString
    This event occurs when using a text based protocol (such as ASCII) and incoming data has been decoded to a string based on the Encoding property. The received string will be in the EventArgs. This event is rarely used since most protocols use a delimited string or are binary.

  • ReceivedBytes
    This event occurs whenever bytes are received. This is useful for binary protocols (non-ASCII/UTF8, etc) or when there is no incoming message delimiter. The received bytes will be in the EventArgs. If the ReadBufferEnabled property is true, you may also process the ReadBuffer property.

Simulating Received Data

The following methods in the SerialCommunication and TcpCommunication classes can be used to simulated incoming data from the serial port or tcp connection. This is helpful when testing a driver which you do not have the hardware for.

public override void SimulateReceivedData(string data);
public override void SimulateReceivedData(byte[] data);

A developer can expose a driver method which lets you pass incoming data into the driver from Elve similar to the following:


[ScriptObjectMethod("Simulate Received Data", "Simulate data being received from the serial port. The data will be processed as if it were actually received as incoming data from the serial ports. This is useful for testing.", null)]
[ScriptObjectMethodParameter("Data", "The data to simulate being received.")]
public void SimulateReceivedData(ScriptString data)
{
    string s = (string)data + "\r";
    _serial.SimulateReceivedData(s);
}

Sending Data

There are several methods provided to queue text and binary data to be sent and you optionally can specify a minimum amount of time to wait to send the data after the prior send has occurred.

Queue a byte array to be sent:


byte[] b = new byte[]{ 0, 1, 2, 3 };
Send(b); // send the data as soon as all previously queued items have been sent.
Send(b, 1000); // send the data 1 second after the previously queued item has been sent.

Queue a string to be sent:


Send("test\r"); // send the data as soon as all previously queued items have been sent.
Send("test\r", 1000); // send the data 1 second after the previously queued item has been sent.

The queueing system provides a way to throttle outgoing data by optionally specifying the amount of time that must pass between the previously sent data and the next data.

Internal Buffer

An internal buffer can be used to cache incoming data until enough data has been received to process. Although each of the received data events contains the data that was just received, the data is not guaranteed to contain a complete message except for the case of the ReceivedDelimitedString which always contains a full message.

The buffer is not enabled by default and must be enabled to use. To enable the buffer, set the ReadBufferEnabled property to true. Data will be added to the buffer whenever it is received and it is the driver's responsibility to process the the buffer data to prevent it from overflowing. The ReadBufferOverflow occurs when the ReadBufferEnabled property is true and the buffer size limit has been exceeded. The overflowed data will be lost.

The buffer can be accessed by the ReadBuffer property which returns a BufferReader object. The BufferReader object is used to read data from the buffer and is generally accessed from within the ReceivedBytes or ReceivedString events. In these events you would usually check the buffer length, use the IndexOf() method to search for delimiters, or use the indexer to peek at the data to determine if an entire message has been received. If there is enough data for a complete message you would process the message and loop until there are no more complete messages.

BufferReader Members:

  • indexer[]
    Gets the byte at the specified index in the buffer. The byte will not be removed from the buffer.

  • Length
    Gets the number of bytes in the buffer.

  • Clear
    Clear the buffer.

  • IndexOf()
    Reports the index of the position of the first occurrence of the specified byte sequence or string in the buffer.

  • ReadByte
    Read and remove the first bytes in the buffer.

  • ReadBytes()
    Read and remove the specified number of bytes in the buffer.

  • ReadString()
    Read and remove the specified number of characters in the buffer.

  • ReadChars()
    Read and remove the specified number of characters in the buffer.

Initialization Examples

Delimited String Protocol


ICommunication _tcp;
// ...
_tcp = new TcpCommunication(_hostName, _port);
_tcp.Logger = this.Logger;
_tcp.Delimiter = "\r"; // change the delimiter since it defaults to "\r\n".
_tcp.CurrentEncoding = System.Text.ASCIIEncoding.ASCII;  // this is unnecessary since it defaults to ASCII
_tcp.ReceivedDelimitedString += new EventHandler<ReceivedDelimitedStringEventArgs>(_tcp_ReceivedDelimitedString);

// Set up connection monitor.
_tcp.ConnectionMonitorTimeout = 60000;
_tcp.ConnectionMonitorTestRequest = "VER\r";
_tcp.ConnectionEstablished += new EventHandler<EventArgs>(_tcp_ConnectionEstablished);
_tcp.ConnectionLost += new EventHandler<EventArgs>(_tcp_ConnectionLost);
_tcp.StartConnectionMonitor();

Binary Protocol


ICommunication _serial;
// ...
_serial = new SerialCommunication(_serialPortName, 19200, System.IO.Ports.Parity.None, 8, System.IO.Ports.StopBits.One);
_serial.Logger = this.Logger;
_serial.Delimiter = null; // set to null to disable the ReceivedDelimitedString
_serial.CurrentEncoding = null; // do not decode strings
_serial.ReceivedBytes += new EventHandler<ReceivedBytesEventArgs>(_insteon_DataReceived);
_serial.ReadBufferEnabled = true; // If the ReadBufferEnabled property is true, you may process the ReadBuffer property in the ReceivedBytes event. This is optional... you may choose to create your own receive buffer if you like.

// Set up connection monitor.
_serial.ConnectionMonitorTimeout = 60000;
_serial.ConnectionMonitorTestRequest = new byte[] { 0x00, 0x00, 0x00 };
_serial.ConnectionEstablished += new EventHandler<EventArgs>(_insteon_ConnectionEstablished);
_serial.ConnectionLost += new EventHandler<EventArgs>(_insteon_ConnectionLost);
_serial.ReadBufferOverflow += _serial_ReadBufferOverflow;
_serial.StartConnectionMonitor();

File I/O and Impersonation

The Driver Service runs in the background as a windows service, so it is running even when you are not logged into Windows. Because of this the driver service runs under the Windows SYSTEM user account. This is a built in Windows user account that most windows services run under.

The SYSTEM user account does not always have access to all local files, and it often does not have access to network file shares (or UNC paths).

If the driver does any file I/O (such as reading media metadata or enumerating a directory of filenames) the driver must implement impersonation. Elve provides a fairly simple way to do this as descrived below.

Add these class level fields to your driver class:

string _userDomainName;
string _userName;
string _userPassword;
System.Security.Principal.WindowsIdentity _windowsIdentity;

Add these setting properties to your class:

// TODO: Update each setting's description text below to be applicable to your driver.
[DriverSetting("User Domain Name", "If required, enter the domain name for the user with access to the folder/files. If the folder/share was set up to allow 'anonymous' sharing then this is not required.", null, false)]
public string UserDomainNameSetting
{
	set { _userDomainName = value; }
}

[DriverSetting("User Name", "If required, enter a valid user name with access to the folder/files. If the folder/share was set up to allow 'anonymous' sharing then this is not required.", null, false)]
public string UserNameSetting
{
	set { _userName = value; }
}

[DriverSetting("User Password", "If required, enter the password for the user with access to the folder/files. If the folder/share was set up to allow 'anonymous' sharing then this is not required.", typeof(HiddenTextDriverSettingEditor), null, false)]
public string UserPasswordSetting
{
	set { _userPassword = value; }
}

Add the following code to the StartDriver() method:

// Get the Windows Identity to use when accessing the files.
if (string.IsNullOrEmpty(_userDomainName) == false && string.IsNullOrEmpty(_userName) == false && string.IsNullOrEmpty(_userPassword) == false)
{
	try
	{
		_windowsIdentity = CodecoreTechnologies.Elve.DriverFramework.Impersonation.GetWindowsIdentity(_userDomainName, _userName, _userPassword);
	}
	catch (Exception ex)
	{
		Logger.Error("Failed to logon using the user account information provided in the device settings. The driver will attempt use the account which the Driver Service is running under: " + WindowsIdentity.GetCurrent().Name);
		_windowsIdentity = WindowsIdentity.GetCurrent();
	}
}
else
	_windowsIdentity = WindowsIdentity.GetCurrent();

Then ANYTIME you access folder or file I/O, wrap the logic as follows:

using (_windowsIdentity.Impersonate())
{
    // Do your file access here, such as:
    // * Opening a list of files, reading them, and then closing them.
    // * Using a 3rd party library to ready meta data from a file or do any other file I/O.
    // * Or even simply getting a list of files in a directory.
}

Deployment

Device drivers can be deployed in the form of compiled assembly class libraries (.dll files) or can be uncompiled source code files in which case Elve will dynamically compile them at startup.

Compiled Assembly Class Libraries (.dll files)

Compiled assembly class libraries (.dll files) should placed in the %Common Application Data%\Codecore Technologies\Elve\DeviceDrivers\Compiled directory and the Master Service will automatically find it on start-up.

Windows Vista & Windows 7 Example Path:

C:\ProgramData\Codecore Technologies\Elve\DeviceDrivers\Compiled\MyDriver.dll

Windows 98, Windows Me, Windows 2000, Windows 2003, and Windows XP Example Path:

C:\Documents and Settings\All Users\Application Data\Codecore Technologies\Elve\DeviceDrivers\Compiled\MyDriver.dll

The following steps should be followed when deploying a compiled driver assembly:

  1. Stop the Elve services.
  2. Copy your class library (dll file) to the directory specified above.
  3. Start the Elve services.
  4. Optionally run Elve Management Studio for driver testing and diagnostics.

This can be automated using a script similar to below:


net stop "Elve Web Server Service"
net stop "Elve Touch Service"
net stop "Elve Driver Service"
net stop "Elve Master Service"

copy mydriver.dll C:\ProgramData\Codecore Technologies\Elve\DeviceDrivers\Compiled\*.*

net start "Elve Master Service"
net start "Elve Driver Service"
net start "Elve Touch Service"
net start "Elve Web Server Service"

"C:\Program Files\Elve\ElveManagementStudio.exe"

Be sure to update the script above with the appropriate driver dll file path. Starting Elve Management Studio after installing your driver is optional but is a common task. You may also provide username and password command line parameters when starting Elve Management Studio.

Compiled driver assemblies (dlls) are REQUIRED to be placed on the Master Service machine. If the Driver Service which runs the driver is running on a different machine than the Master Service then you will get better performance and reliability if you ALSO place the driver on the driver service machine. If you only place the dll on the Master Service machine then each time the Driver Service attempts to start the driver it will need to determine that the appropriate assembly is located on the Master Service machine, download it, and load it into memory. This can be a lot of overhead depending on the size of the assembly and network performance, etc. If you place a compiled driver assembly (dll) on both the Master Service and Driver Service machine, be sure they are the same file (same version, etc) or you will get unexpected results.

Uncompiled Source Code Files (.cs and .vb files)

1. Place the .cs or .vb files for the device driver in it's own own sub-directory of the DeviceDrivers directory.

Windows Vista & Windows 7 Example Path:

C:\ProgramData\Codecore Technologies\Elve\DeviceDrivers\Uncompiled\Acme\AcmeDriver.cs

Windows 98, Windows Me, Windows 2000, Windows 2003, and Windows XP Example Path:

C:\Documents and Settings\All Users\Application Data\Codecore Technologies\Elve\DeviceDrivers\Uncompiled\Acme\AcmeDriver.cs

2. If there is a compiler.config file, place it in the same directory.

3. Place any supporting or supplemental source code files in the same directory.

4. Restart the Elve Services.

5. Use Elve Management Studio to verify the device driver was found and compiled without errors by checking the driver list when adding a device. If errors occurred when compiling they will recorded to the Elve log.

DO NOT place any driver files within the C:\Program Files\Elve directory or any directory under it.

Troubleshooting

  • Check the Elve log for compilation errors.
  • You can view the entire compiler output by using a Compiler.config file and the CompilerOutputPath setting.
  • Uncompiled driver files are required to use C# or VB.NET programming languages.
  • Make sure you placed the driver is the correct location and the driver's file extension is .cs or .vb.
  • A driver must use only one programming language, ie a driver can not have both .cs and .vb files.
  • Do not place the driver source code files or supporting files directly in the DeviceDrivers or DeviceDrivers\Uncompiled directory, they must be in a subdirectory under DeviceDrivers\Uncompiled.
  • It is recommended that each device driver be a single file to make installation easier.
  • Changes to the driver source code will not be recognized until the Elve Services have been restarted.
  • Make sure that your text editor does not change are append a .txt file extension to the file. Windows XP's notepad.exe is know to do this.

Compiler.config file

The optional compiler.config file provides configuration options for compiling the driver. This file is optional and each setting in this file is optional. Most driver will not use this file.

Example compiler.config file:

# This is a comment line in a compiler.config file.
CompilerOutputPath=c:\Users\jhughes\Desktop\MyDriverOutput.txt
#CompilerOption=
ReferencedAssembly=sample1.dll
ReferencedAssembly=sample2.dll
#WarningLevel=
#IncludeDebugInformation=false
#LinkedResource=
#TreatWarningsAsErrors=false

File Option Descriptions:

  • CompilerOutputPath: The full path to save the compiler output text to. This is useful when diagnosing compiler errors.
  • CompilerOption: Optional additional-command line arguments string to use when invoking the compiler. The "/optimize" option is set automatically.
  • ReferencedAssembly: A path to an assembly referenced by the current project. More than one ReferencedAssembly line may be specified. The following assemblies are automatically referenced: System.dll, System.Data.dll, System.Data.Linq.dll, System.Management.dll, System.Web.dll, System.Web.Services.dll, System.Xml.dll, System.Xml.Linq.dll, System.Core.dll, System.Management.dll, System.Runtime.Serialization.dll
  • WarningLevel: The numeric warning level at which the compiler aborts compilation.
  • IncludeDebugInformation: "true" indicates to include debug information in the compiled assembly.
  • LinkedResource: A path to a .NET Framework resource file that is referenced in the current source. More than one LinkedResource line may be specified.
  • TreatWarningsAsErrors: "true" indicates whether to treat warnings as errors. Defaults to false.

Submitting your driver for Review

If you plan to participate in the device driver incentive program, please read the program details as well as the following.

Here are the things we check:

  • The DriverAttribute decription should be verbose, describe what hardware is required, what the driver is for, and provide any necessary instructions.
  • The driver class name should end with the word "Driver" such as "SqueezeboxServerDriver".
  • The driver should implement the appropriate interfaces. For example lighting drivers should implement ILightingAndElectrical.
  • The DriverSetting attributes should be verbose.
  • The DriverSetting attributes should use the appropriate Device Settings Editor. For example a serial port setting should use SerialPortDeviceSettingEditor.
  • The DriverSetting field/property names should end with the work "Setting" such as "SerialPortSetting".
  • Any non-interface scripting member names should be consistant with other drivers when applicable.
  • If the driver uses a communications port such as Serial or TCP:
    • StartDriver() should return false.
    • The IsReady property should be set to true after a connection has been made and after any initialization commands are sent/received.
    • The IsReady property should be set to false after a disconnection.
    • The communication object should have its Logger property set to the driver's Logger property.
    • The driver should use one of the built-in communication classes, such as SerialCommunication, TcpCommunication, etc.
    • All properties should return locally cached values and should not query the hardware on demand.
  • StopDriver() should dispose all disposable managed resources, most importantly any Timers.
  • All applicable properties should be decorated with a the SupportsDriverPropertyBinding attribute.
  • All properties decorated with the SupportsDriverPropertyBinding attribute should also have corresponding calls to DevicePropertyChangeNotification with the property name spelled correctly and the correct property index if applicable. (Indexes should be the same as what is exposed to the user, not however they are stored internally.)
  • The driver should log errors and debug information.
  • Array property indexes exposed to the user should normally start at 1 and not 0.
  • Properties and methods should contain script builder text when applicable.
  • The ScriptObjectPropertyAttribute's scriptBuilderGetText parameter should NOT be a complete sentence because the Action List Editor will insert the text into the middle of an existing sentence as a parameter. Example: "the {NAME} power level"
  • Public property and method names should be upper camel cased. Examples: IsConnected, CurrentTemperature, ActivateTask().
  • Exposed value ranges should make sense. For example if an A/V receiver's underlying hardware protocol uses a volume range of -24 to 75, don't expose the range to the user with this range. Expose it as 0 to 100 in any properties and methods and do the necessary conversion in the driver.
  • Has the developer used Elve Management Studio to test every driver method and property?
Privacy Policy | Conditions Of Use

Copyright ©2014 Codecore Technologies, All rights reserved.