/*       Copyright (c) Eric Ledoux.  All rights reserved.       */
/* See http://www.dwell.net/terms for code sharing information. */

// Cm11.cs
//
// Implements the DwellNet.Cm11 class.
//

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.IO.Ports;
using System.Threading;
using System.Text;
using System.Text.RegularExpressions;
using DwellNet;
using DwellNet.Properties;

namespace DwellNet
{

Cm11 Class

Controls a CM11 device (or an equivalent, such as CM11A) connected through a serial port.

Remarks

See Dwell.Net CM11 Introduction for information about how to use the Cm11 class.

Example

The following example turns on the X10 device with address "A1" (i.e. house code "A", device code "1"), and then waits until the command completes (successfully or not).

C#
using (Cm11 cm11 = new Cm11())
{
    cm11.Open("COM1");
    cm11.Execute("A1 On");
    cm11.WaitUntilIdle();
}
The following code is equivalent -- it uses the higher-level TurnOnDevice method.
C#
using (Cm11 cm11 = new Cm11())
{
    cm11.Open("COM1");
    cm11.TurnOnDevice("A1");
    cm11.WaitUntilIdle();
}

See Also
[ToolboxBitmap(typeof(ResourceFinder), "DwellNet.Properties.16x16w.ico"),
 Description("Controls X10 devices using CM11 hardware.")]
public class Cm11 : Component, IDisposable
{
    //////////////////////////////////////////////////////////////////////////
    // Private Constants and Statics
    //

    
Cm11.s_codesToNibbles Field

Used to convert a house code ('A' through 'P') or a device code (1 through 15) to a nibble value used in the X10 protocol.

    static byte[] s_codesToNibbles = new byte[]
    {
        0x6,    // binary 0110: house code "A" or device code "1"
        0xE,    // binary 1110: house code "B" or device code "2"
        0x2,    // binary 0010: house code "C" or device code "3"
        0xA,    // binary 1010: house code "D" or device code "4"
        0x1,    // binary 0001: house code "E" or device code "5"
        0x9,    // binary 1001: house code "F" or device code "6"
        0x5,    // binary 0101: house code "G" or device code "7"
        0xD,    // binary 1101: house code "H" or device code "8"
        0x7,    // binary 0111: house code "I" or device code "9"
        0xF,    // binary 1111: house code "J" or device code "10"
        0x3,    // binary 0011: house code "K" or device code "11"
        0xB,    // binary 1011: house code "L" or device code "12"
        0x0,    // binary 0000: house code "M" or device code "13"
        0x8,    // binary 1000: house code "N" or device code "14"
        0x4,    // binary 0100: house code "O" or device code "15"
        0xC,    // binary 1100: house code "P" or device code "16"
    };

    
Cm11.s_nibblesToCodes Field

Used to convert a nibble value used in the X10 protocol to a house code ('A' through 'P') or a device code (1 through 15). Maps a nibble value to the a zero-based index

    static byte[] s_nibblesToCodes = new byte[]
    {
        12, // 0x0 binary 0000: house code "M" or device code "13"
        4,  // 0x1 binary 0001: house code "E" or device code "5"
        2,  // 0x2 binary 0010: house code "C" or device code "3"
        10, // 0x3 binary 0011: house code "K" or device code "11"
        14, // 0x4 binary 0100: house code "O" or device code "15"
        6,  // 0x5 binary 0101: house code "G" or device code "7"
        0,  // 0x6 binary 0110: house code "A" or device code "1"
        8,  // 0x7 binary 0111: house code "I" or device code "9"
        13, // 0x8 binary 1000: house code "N" or device code "14"
        5,  // 0x9 binary 1001: house code "F" or device code "6"
        3,  // 0xA binary 1010: house code "D" or device code "4"
        11, // 0xB binary 1011: house code "L" or device code "12"
        15, // 0xC binary 1100: house code "P" or device code "16"
        7,  // 0xD binary 1101: house code "H" or device code "8"
        1,  // 0xE binary 1110: house code "B" or device code "2"
        9,  // 0xF binary 1111: house code "J" or device code "10"
    };

    
Cm11.X10Function Enumeration

X10 function code nibble values.

Members
Name Description
AllLightsOff

0110 = All Lights Off

AllLightsOn

0001 = All Lights On

AllOff

0000 = All Units Off

Brighten

0101 = Brighten

Dim

0100 = Dim

ExtCode

0111 = Extended Code

ExtDataXfer

1100 = Extended Data Transfer

HailAck

1001 = Hail Acknowledge

HailReq

1000 = Hail Request

Off

0011 = Off

On

0010 = On

PresetDim1

1010 = Preset Dim 1

PresetDim2

1011 = Preset Dim 2

StatusOff

1110 = Status Off

StatusOn

1101 = Status On

StatusReq

1111 = Status Request

    enum X10Function
    {
        
Cm11.X10Function.AllOff Enumeration Value

0000 = All Units Off

        AllOff = 0,

        
Cm11.X10Function.AllLightsOn Enumeration Value

0001 = All Lights On

        AllLightsOn = 1,

        
Cm11.X10Function.On Enumeration Value

0010 = On

        On = 2,

        
Cm11.X10Function.Off Enumeration Value

0011 = Off

        Off = 3,

        
Cm11.X10Function.Dim Enumeration Value

0100 = Dim

        Dim = 4,

        
Cm11.X10Function.Brighten Enumeration Value

0101 = Brighten

        Brighten = 5,

        
Cm11.X10Function.AllLightsOff Enumeration Value

0110 = All Lights Off

        AllLightsOff = 6,

        
Cm11.X10Function.ExtCode Enumeration Value

0111 = Extended Code

        ExtCode = 7,

        
Cm11.X10Function.HailReq Enumeration Value

1000 = Hail Request

        HailReq = 8,

        
Cm11.X10Function.HailAck Enumeration Value

1001 = Hail Acknowledge

        HailAck = 9,

        
Cm11.X10Function.PresetDim1 Enumeration Value

1010 = Preset Dim 1

        PresetDim1 = 10,

        
Cm11.X10Function.PresetDim2 Enumeration Value

1011 = Preset Dim 2

        PresetDim2 = 11,

        
Cm11.X10Function.ExtDataXfer Enumeration Value

1100 = Extended Data Transfer

        ExtDataXfer = 12,

        
Cm11.X10Function.StatusOn Enumeration Value

1101 = Status On

        StatusOn = 13,

        
Cm11.X10Function.StatusOff Enumeration Value

1110 = Status Off

        StatusOff = 14,

        
Cm11.X10Function.StatusReq Enumeration Value

1111 = Status Request

        StatusReq = 15
    };


    
Cm11.s_addressRegex Field

A regular expression that matches an X10 address or *address command*, e.g. "A1" or "P16". Note that some invalid address, such as "A99", are also matched.

    Regex s_addressRegex = new Regex(@"^([A-P])(\d{1,2})$");

    
Cm11.s_dimRegex Field

A regular expression that matches a command of the form "Dim<percent>", with an optional house code prefix; for example, "A.Dim0", "Dim50", and "Dim100". Note that some invalid commands, such as "Dim999", are also matched.

    Regex s_dimRegex = new Regex(@"^(?:([A-P])\.)?Dim(\d{0,4})$");

    
Cm11.s_brightenRegex Field

A regular expression that matches a command of the form "Brighten<percent>", with an optional house code prefix; for example, "A.Brighten0", "Brighten50", and "Brighten100". Note that some invalid commands, such as "Brighten999", are also matched.

    Regex s_brightenRegex = new Regex(@"^(?:([A-P])\.)?Brighten(\d{0,4})$");

    
Cm11.s_functionRegex Field

A regular expression that matches a *function command* with an optional house code prefi; for example, "A.On" or "On". However, this is very loose match; "Abcde" is also matched, for example.

    Regex s_functionRegex = new Regex(@"^(?:([A-P])\.)?([A-Za-z0-9]+)$");

    
Cm11.s_hexRegex Field

A regular expression that matches a command of the form "Hex<hex-digits>"; for example, "Hex046E". Note that some invalid commands, such as "Hex1A2" (odd number of hexadecimal digits), are also matched.

    Regex s_hexRegex = new Regex(@"^Hex([0-9a-fA-F]*)$");

    
Cm11.SERIAL_TIMEOUT Field

The maximum number of milliseconds to wait for a response from the CM11 device.

    const int SERIAL_TIMEOUT = 5000;

    //////////////////////////////////////////////////////////////////////////
    // Private Fields
    //

    
Cm11.m_lock Field

m_lock is locked while a thread accesses any state of this object, to serialize access to the object. Exception: Access to the command queue (m_commandQueue) is serialized using lock (m_commandQueue) so that the application isn't blocked from queuing new commands while an existing message is being transmitted.

    object m_lock = new object();

    
Cm11.m_isOpen Field

Holds the value of the IsOpen property.

    bool m_isOpen;

    
Cm11.m_workerThreadId Field

The managed thread ID of the worker thread.

    int m_workerThreadId;

    
Cm11.m_workerThreadWaitHandle Field

The WaitHandle of the worker thread. This WaitHandle is set when the WorkerThread method exits.

    WaitHandle m_workerThreadWaitHandle;

    
Cm11.m_wakeWorkerThread Field

Signaled when it's time to "wake up" the worker thread (if it's sleeping).

Remarks

For example, m_wakeWorkerThread.Set is called when serial data is received from the CM11, or a new command is added to m_commandQueue, or Close is called. Also, when the worker thread is initially created, m_wakeWorkerThread is used in the opposite way: the application temporarily blocks on m_wakeWorkerThread until the worker thread begins running, so that we can be sure that m_workerThreadId is set before Open returns.

    AutoResetEvent m_wakeWorkerThread = new AutoResetEvent(false);

    
Cm11.m_idleEvent Field

Signaled when m_commandQueue becomes empty.

    ManualResetEvent m_idleEvent = new ManualResetEvent(true);

    
Cm11.m_idle Field

True while no commands are being processed.

    bool m_idle = true;

    
Cm11.m_quitting Field

Set to true when it's time to dispose of this Cm11 object. Once set to true, further calls to Execute will silently fail, and messages already in the message queue won't be sent.

    bool m_quitting;

    
Cm11.m_serialPortName Field

Holds the value of the SerialPortName property.

    string m_serialPortName;

    
Cm11.m_serialPort Field

The serial port used to communicate with the CM11 device.

    DnSerialPort m_serialPort;

    
Cm11.m_commandQueue Field

The queue of DwellNet.Cm11 commands to execute.

    Queue<string> m_commandQueue = new Queue<string>(20);

    
Cm11.m_commandQueueChangeCount Field

Incremented each time the command queue is modified. Used to track whether the command queue may have changed between two points in time.

    long m_commandQueueChangeCount = 0;

    
Cm11.m_addressTracker Field

Tracks which X10 devices are currently addressed. (See Cm11.doc and AddressTracker for more information.)

    AddressTracker m_addressTracker = new AddressTracker(false);

#if DEBUG
    /// <summary>
    /// Tracks time from when this object was created, for debugging purposes.
    /// </summary>
    Stopwatch m_traceStopwatch = Stopwatch.StartNew();
#endif

    
Cm11.m_invokeEventsUsing Field

Holds the value of the InvokeEventsUsing property.

    ISynchronizeInvoke m_invokeEventsUsing;

    //////////////////////////////////////////////////////////////////////////
    // Public Properties
    //

    
Cm11.SerialPortName Property

Gets the serial port that the CM11 is connected to; for example, "COM1".

    [Browsable(false)]
    public string SerialPortName
    {
        get
        {
            return m_serialPortName;
        }
    }

    
Cm11.IsOpen Property

Gets a value indicating the open or closed status of the Cm11 object.

    [Browsable(false)]
    public bool IsOpen
    {
        get
        {
            return m_isOpen;
        }
    }

    
Cm11.InvokeEventsUsing Property

Gets or sets an application-provided ISynchronizeInvoke instance to use to invoke methods. Provides a way to invoke events on an application-provided thread, for situations in which firing an event on the internal worker thread would cause cross-thread errors.

Remarks

If InvokeEventsUsing is set to an application-provided ISynchronizeInvoke instance, then Cm11 will call ISynchronizeInvoke.Invoke to fire events, provided that ISynchronizeInvoke.InvokeRequired is true. Otherwise, Cm11 will invoke event delegates directly.

If you're writing a Windows Forms application that uses Cm11 and you get a "Cross-thread operation not valid" error when Cm11 fires an event, you can often solve this problem by setting the InvokeEventsUsing to the Form instance; for example:

C#
cm11.InvokeEventsUsing = Form1;

    [Description("Runs events on the thread provided by this object.  For Windows Forms applications, set this to the form.")]
    public ISynchronizeInvoke InvokeEventsUsing
    {
        get
        {
            return m_invokeEventsUsing;
        }
        set
        {
            m_invokeEventsUsing = value;
        }
    }

    //////////////////////////////////////////////////////////////////////////
    // Public Events
    //

    
Cm11.OnReceived Event

Fired when an "On" command is transmitted by a controller or device on the X10 network.

Remarks

This event is fired once for each device that the "On" command applies to.

    [Description("Occurs when an \"On\" command is transmitted by a controller or device on the X10 network.")]
    public event Cm11DeviceNotificationEventDelegate OnReceived;

    
Cm11.OffReceived Event

Fired when an "Off" command is transmitted by a controller or device on the X10 network.

Remarks

This event is fired once for each device that the "Off" command applies to.

    [Description("Occurs when an \"Off\" command is transmitted by a controller or device on the X10 network.")]
    public event Cm11DeviceNotificationEventDelegate OffReceived;

    
Cm11.BrightenReceived Event

Fired when a "Brighten" command is transmitted by a controller or device on the X10 network.

Remarks

This event is fired once for each device that the "Brighten" command applies to.

    [Description("Occurs when a \"Brighten\" command is transmitted by a controller or device on the X10 network.")]
    public event Cm11BrightenOrDimNotificationEventDelegate BrightenReceived;

    
Cm11.DimReceived Event

Fired when a "Dim" command is transmitted by a controller or device on the X10 network.

Remarks

This event is fired once for each device that the "Dim" command applies to.

    [Description("Occurs when a \"Dim\" command is transmitted by a controller or device on the X10 network.")]
    public event Cm11BrightenOrDimNotificationEventDelegate DimReceived;

    
Cm11.AllLightsOnReceived Event

Fired when an "AllLightsOn" command is transmitted by a controller or device on the X10 network.

    [Description("Occurs when an \"AllLightsOn\" command is transmitted by a controller or device on the X10 network.")]
    public event Cm11HouseNotificationEventDelegate AllLightsOnReceived;

    
Cm11.AllLightsOffReceived Event

Fired when an "AllLightsOff" command is transmitted by a controller or device on the X10 network.

    [Description("Occurs when an \"AllLightsOff\" command is transmitted by a controller or device on the X10 network.")]
    public event Cm11HouseNotificationEventDelegate AllLightsOffReceived;

    
Cm11.AllOffReceived Event

Fired when an "AllOff" command is transmitted by a controller or device on the X10 network.

    [Description("Occurs when an \"AllOff\" command is transmitted by a controller or device on the X10 network.")]
    public event Cm11HouseNotificationEventDelegate AllOffReceived;

    
Cm11.Notification Event

Fired when a notification of an event on the X10 network is received from the CM11 hardware.

Remarks

This is a lower-level event than events such as OnReceived and OffReceived. For example, if the "Unit 1 On" button is pressed on an X10 controller set to house code "A", three events are fired: (1) a "Notification" event with command name "A1"; a "Notification" event with a command name "On"; (3) a "OnReceived" event with address "A1". Applications can choose to handle the low-level events, the high-level events, both, or neither; handling low-level events requires that the application keep track of which X10 devices are currently addressed.

This event is fired on a thread other than the thread which created the Cm11 object, unless InvokeEventsUsing is used.

    [Description("Occurs when a notification of an event on the X10 network is received from the CM11 hardware.")]
    public event Cm11LowLevelNotificationEventDelegate Notification;

    
Cm11.IdleStateChange Event

Fired when the Cm11 object changes from processing commands to being idle, or vice versa.

Remarks

This event is fired on a thread other than the thread which created the Cm11 object, unless InvokeEventsUsing is used.

    [Description("Occurs when the Cm11 object changes from processing commands to being idle, or vice versa.")]
    public event Cm11IdleStateChangeEventDelegate IdleStateChange;

    
Cm11.Error Event

Fired when communication with the CM11 hardware fails, or the hardware itself fails.

Remarks

This event is fired on a thread other than the thread which created the Cm11 object, unless InvokeEventsUsing is used.

    [Description("Occurs when communication with the CM11 hardware fails, or the hardware itself fails.")]
    public event Cm11ErrorEventDelegate Error;

    
Cm11.LogMessage Event

Fired when the Cm11 object has information to provide to the application that may be useful for later review by the user.

Remarks

Typically the application handles this event in order to log the messages provided by this event into a file or event log that can be reviewed later by a user. The messages sent by this event include:

  • Messages prefixed by "<--" that indicate bytes sent to the CM11 hardware, including a decoding of those bytes.
  • Messages prefixed by "-->" that indicate bytes received from the CM11 hardware, including a decoding of those bytes.
  • Other messages indication error conditions.
This information may help the user debug problems with the CM11 hardware or the X10 network.

This event is fired on a thread other than the thread which created the Cm11 object, unless InvokeEventsUsing is used.

    [Description("Occurs when the Cm11 object has information to provide to the application that may be useful for later review by the user.")]
    public event Cm11LogMessageEventDelegate LogMessage;

    //////////////////////////////////////////////////////////////////////////
    // Public Methods
    //

    
Cm11.Open Method

Begins communication with the CM11 device.

Parameters

serialPortName

The name of the serial port that the CM11 device is connected to; for example, "COM1".

Exceptions
Exception type Condition
InvalidOperationException

Thrown if Open was already called.

Remarks

This method throws an InvalidOperationException if this Cm11 instance is already open. closed.

This method cannot be called while a Cm11 event handler is executing.

    public void Open(string serialPortName)
    {
        TraceInfo("Open");

        lock (m_lock)
        {
            // make sure we're not already open
            if (m_isOpen)
                throw new InvalidOperationException(Resources.AlreadyOpen);

            // update state
            m_serialPortName = serialPortName;

            // open the serial port, if it wasn't opened yet
            OpenSerialPort();

            // start the worker thread, if it wasn't started yet
            if (m_workerThreadWaitHandle == null)
            {
                // start the thread
                VoidDelegate workerThreadDelegate = WorkerThread;
                IAsyncResult ar = workerThreadDelegate.BeginInvoke(null, null);

                // initialize <m_workerThreadHandle>
                m_workerThreadWaitHandle = ar.AsyncWaitHandle;

                // wait until the worker thread has started to ensure that
                // <m_workerThreadId> is initialized before Open() returns;
                // note that this is the opposite of the normal usage of
                // <m_wakeWorkerThread>, since in this case the application
                // thread is using it to wait on the worker thread rather than
                // vice versa
                m_wakeWorkerThread.WaitOne();
            }

            // update state
            m_isOpen = true;
        }
    }

    
Cm11.Close Method

Closes the serial port and frees resources used by this object. No further access to the CM11 device by this object is possible until Open is called again.

Remarks

This method does nothing if this Cm11 instance is already closed.

    public void Close()
    {
        TraceInfo("Close");

        // update state; don't rely on <isOpen> to tell us if the object is
        // open or closed, since if an exception occurred inside Open() then
        // <isOpen> will be false but some resources may still need to be
        // cleaned up
        m_isOpen = false;
        m_serialPortName = null;

        // do nothing further if we're already closed
        if (m_workerThreadWaitHandle == null)
            return;

        // tell the worker thread that it's quitting time
        m_quitting = true;
        m_wakeWorkerThread.Set();

        // Wait for the worker thread to quit -- unless we're currently
        // running in the worker thread.
        //
        // Consider the following two scenarios:
        //
        // Scenario 1:  The worker thread fires an event.  While the event is
        // executing on the worker thread, the user closes the application,
        // which causes Close() to be called on the application's UI thread.
        // No problem: Close() sets <m_quitting> to true and waits on
        // <m_workerThreadWaitHandle> and eventually the event handler returns
        // and the worker thread notices that <m_quitting> is true and quits.
        //
        // Scenario 2:  The worker thread fires event; in application's event
        // handler the application calls Close().  (Note that this time we're
        // executing Close() in the worker thread.)  Close() sets <m_quitting>
        // to true and waits on <m_workerThreadWaitHandle>.  The result is a
        // deadlock: the current thread will never exit because it's waiting
        // for itself to exit.
        //
        // Moral of the story: don't wait on <m_workerThreadWaitHandle> if the
        // current thread is the worker thread.
        //
        if (m_workerThreadId != Thread.CurrentThread.ManagedThreadId)
            m_workerThreadWaitHandle.WaitOne();
    }

    
Cm11.Execute Method

Sends one or more DwellNet.Cm11 commands to the CM11 device. Commands are translated to the CM11 binary protocol.

Parameters

commands

The series of DwellNet.Cm11 commands to execute, separated by spaces. See Cm11Help.htm for a list of valid DwellNet.Cm11 commands.

Remarks

This method starts sending commands to the CM11 device. This method returns immediately -- it doesn't wait for the commands to complete. You can call WaitUntilIdle after calling Execute if you'd like to wait until all queued commands have been executed.

Commands are separated by spaces; each command must not contain spaces within it. For example, if commands equals "A1 A2 Dim70", that specifies three commands, "A1", "A2", and "Dim70". Commands are case-sensitive; you can't specify "a1" or "dim70".

Open must be called before calling this method.

Example

The following code turns on lamps A1, A2, and A3, then dims them by 25%, then turns off lamp B1.

C#
cm11.Execute("A1 A2 A3 On Dim25 B1 Off");

    public void Execute(string commands)
    {
        TraceInfo("Execute: queue: {0}", commands);

        // "lock (m_lock)" is not called here because we don't want to block
        // unnecessarily -- we're just adding commands to the queue, and
        // <m_commandQueue> has its own lock

        // make sure Open() was called
        if (!m_isOpen)
            throw new InvalidOperationException(Resources.NotOpen);

        // split <commands> into an array of individual commands, one per
        // command per array element
        string[] commandArray = commands.Split(
            new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

        // perform two passes: in the first pass, check the syntax of each
        // command; in the second, add the command to the command queue;
        // the purpose of this two-pass approach is to reduce the chance of
        // having invalid commands in the queue...

        // pass one:
        AddressTracker addressTracker = new AddressTracker(true);
        foreach (string command in commandArray)
            ProcessCommand(command, addressTracker, false);

        // pass two:
        foreach (string command in commandArray)
        {
            lock (m_commandQueue)
            {
                m_commandQueue.Enqueue(command);
                m_commandQueueChangeCount++;
                SetIdleState(false);
            }
        }

        // wake up the worker thread so it can process the commands
        m_wakeWorkerThread.Set();
    }

    
Cm11.Clear Method

Removes all DwellNet.Cm11 commands from the Cm11 command queue.

Exceptions
Exception type Condition
InvalidOperationException

Thrown if Open was not called before this method was called.

Remarks

Open must be called before calling this method.

    public void Clear()
    {
        // make sure Open() was called
        if (!m_isOpen)
            throw new InvalidOperationException(Resources.NotOpen);

        // clear the command queue
        lock (m_commandQueue)
        {
            m_commandQueue.Clear();
            m_commandQueueChangeCount++;
            SetIdleState(true);
        }
    }

    
Cm11.TurnOnDevice Method

Turns on an X10 device.

Parameters

address

The X10 address of the device to control; for example, "A1". An address consists of a house code, "A" through "P" inclusive, followed by a device code, "1" through "16" inclusive.

Remarks

Open must be called before calling this method.

Example

The following code turns on the X10 device at address "A1", assuming a CM11 (or compatible) device is plugged into serial port "COM1". cm11 is a variable of type Cm11 or, in the case of a Windows Forms application, a Cm11 component that was dragged onto the form.

C#
cm11.Open("COM1");
cm11.TurnOnDevice("A1");

    public void TurnOnDevice(string address)
    {
        // prevalidate <address>: make sure the string contains only a single
        // command; beyond that, Execute() will complete validaton of <address>
        if (address.IndexOf(' ') >= 0)
        {
            throw new Cm11InvalidCommandException(Resources.InvalidAddress,
                    address);
        }

        // execute the necessary commands
        Execute(String.Format("{0} On", address));
    }

    
Cm11.TurnOffDevice Method

Turns off an X10 device.

Parameters

address

The X10 address of the device to control; for example, "A1". An address consists of a house code, "A" through "P" inclusive, followed by a device code, "1" through "16" inclusive.

Remarks

Open must be called before calling this method.

Example

The following code turns off the X10 device at address "A1", assuming a CM11 (or compatible) device is plugged into serial port "COM1". cm11 is a variable of type Cm11 or, in the case of a Windows Forms application, a Cm11 component that was dragged onto the form.

C#
cm11.Open("COM1");
cm11.TurnOffDevice("A1");

    public void TurnOffDevice(string address)
    {
        // prevalidate <address>: make sure the string contains only a single
        // command; beyond that, Execute() will complete validaton of <address>
        if (address.IndexOf(' ') >= 0)
        {
            throw new Cm11InvalidCommandException(Resources.InvalidAddress,
                    address);
        }

        // execute the necessary commands
        Execute(String.Format("{0} Off", address));
    }

    
Cm11.BrightenLamp Method

Brightens an X10 lamp device.

Parameters

address

The X10 address of the device to control; for example, "A1". An address consists of a house code, "A" through "P" inclusive, followed by a device code, "1" through "16" inclusive.

percent

The amount to brighten the lamp by, measured as a percentage value from 0 to 100.

Remarks

Open must be called before calling this method.

Example

Assuming the X10 device at address "A1" is a lamp module, and assuming a CM11 (or compatible) device is plugged into serial port "COM1", the following code brightens the lamp by 25%. cm11 is a variable of type Cm11 or, in the case of a Windows Forms application, a Cm11 component that was dragged onto the form.

C#
cm11.Open("COM1");
cm11.BrightenLamp("A1", 33);

    public void BrightenLamp(string address, int percent)
    {
        // prevalidate <address>: make sure the string contains only a single
        // command; beyond that, Execute() will complete validaton of <address>
        if (address.IndexOf(' ') >= 0)
        {
            throw new Cm11InvalidCommandException(Resources.InvalidAddress,
                    address);
        }

        // execute the necessary commands
        Execute(String.Format("{0} Brighten{1}", address, percent));
    }

    
Cm11.DimLamp Method

Dims an X10 lamp device.

Parameters

address

The X10 address of the device to control; for example, "A1". An address consists of a house code, "A" through "P" inclusive, followed by a device code, "1" through "16" inclusive.

percent

The amount to dim the lamp by, measured as a percentage value from 0 to 100.

Remarks

Open must be called before calling this method.

Example

Assuming the X10 device at address "A1" is a lamp module, and assuming a CM11 (or compatible) device is plugged into serial port "COM1", the following code dims the lamp by 25%. cm11 is a variable of type Cm11 or, in the case of a Windows Forms application, a Cm11 component that was dragged onto the form.

C#
cm11.Open("COM1");
cm11.DimLamp("A1", 33);

    public void DimLamp(string address, int percent)
    {
        // prevalidate <address>: make sure the string contains only a single
        // command; beyond that, Execute() will complete validaton of <address>
        if (address.IndexOf(' ') >= 0)
        {
            throw new Cm11InvalidCommandException(Resources.InvalidAddress,
                    address);
        }

        // execute the necessary commands
        Execute(String.Format("{0} Dim{1}", address, percent));
    }

    
Cm11.WaitUntilIdle Method

Returns after all Cm11 commands queued by Execute have completed.

Exceptions
Exception type Condition
InvalidOperationException

Thrown if Open was not called before this method was called.

Remarks

This method blocks the caller until the internal command queue is empty, either because the commands were successfully executed or because there was an error that caused the commands to be discarded.

This method cannot be called while a Cm11 event handler is executing.

Open must be called before calling this method.

    public void WaitUntilIdle()
    {
        // make sure Open() was called
        if (!m_isOpen)
            throw new InvalidOperationException(Resources.NotOpen);

        // "lock (m_lock)" is not needed here
        m_idleEvent.WaitOne();
    }

    //////////////////////////////////////////////////////////////////////////
    // Event Handlers for <m_serialPort>
    //

    void m_serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        // if we received bytes, notify the worker thread
        TraceInfo("Serial port: DataReceived ({0})", e.EventType);
        if (e.EventType == SerialData.Chars)
            m_wakeWorkerThread.Set();
    }

    void m_serialPort_ErrorReceived(object sender,
        SerialErrorReceivedEventArgs e)
    {
        // a serial port communication error occurred -- log informatin to the
        // application and resync
        FireLogMessage(Resources.SerialPortError, e.EventType);
        HardResync();
    }

    //////////////////////////////////////////////////////////////////////////
    // IDisposable Implemention
    //

    
Cm11.IDisposable.Dispose Method

Disposes of resources used by this object.

    void IDisposable.Dispose()
    {
        Close();
    }

    //////////////////////////////////////////////////////////////////////////
    // Private Methods
    //
    // NOTE: These private properties and methods are generally NOT
    // thread-safe -- call these methods within "lock (m_lock)" or an
    // equivalent.
    //

    
Cm11.OpenSerialPort Method

Opens the serial port, if it's not open yet.

    void OpenSerialPort()
    {
        // do nothing if OpenSerialPort() was already called
        if (m_serialPort != null)
            return;

        // create and initialize <m_serialPort>
        m_serialPort = new DnSerialPort(new DnSerialPortStringResources(),
            m_serialPortName, 4800, Parity.None, 8, StopBits.One);
        m_serialPort.DataReceived += new SerialDataReceivedEventHandler(
            m_serialPort_DataReceived);
        m_serialPort.ErrorReceived += new SerialErrorReceivedEventHandler(
            m_serialPort_ErrorReceived);
        m_serialPort.Open();
    }

    
Cm11.CloseSerialPort Method

Closes the serial port, if it's open.

    void CloseSerialPort()
    {
        if (m_serialPort != null)
        {
            m_serialPort.Purge();
            m_serialPort.Close();
            m_serialPort = null;
        }
    }

    
Cm11.WorkerThread Method

The code of the worker thread which services the message queue.

    void WorkerThread()
    {
        TraceInfo("worker thread started");
        // initialize <m_workerThreadId>
        m_workerThreadId = Thread.CurrentThread.ManagedThreadId;

        // allow Open() to complete, now that <m_workerThreadId> has been
        // initialized; note that this is the opposite of the normal usage of
        // <m_wakeWorkerThread>, since in this case the application thread is
        // using it to wait on the worker thread rather than vice versa
        m_wakeWorkerThread.Set();

        TraceInfo("worker thread initialized");

        // process commands in the command queue until we're told to quit;
        // NOTE: we don't "lock (m_lock)" until absolutely necessary, since
        // doing so restricts concurrent access
        try
        {
            while (true)
            {
                try
                {
                    // wait for one of the following events:
                    //   (a) serial port data the CM11;
                    //   (b) a command from the application (from Execute());
                    //   (c) Close() called -- this causes a QuittingException
                    //       to be thrown
                    WaitResult wr =
                        WaitForSerialInputOrCommand(DateTime.MaxValue, true);
                    if (wr == WaitResult.SerialInput)
                        ProcessNotification();
                    else
                    if (wr == WaitResult.CommandQueued)
                        ProcessQueuedCommands();
                }
                catch (TimeoutException ex)
                {
                    // the CM11 took too long to reply during an exchange with
                    // the PC -- pause to allow the CM11 to return to a
                    // "normal" state (waiting for a command or sending a
                    // notification)
                    TraceException(ex);
                    FireLogMessage(Resources.Cm11ReplyTimeout);
                    SoftResync();
                }
                catch (HardResyncException ex)
                {
                    // a serious serial port error occurred -- close and reopen
                    // the serial port, and discard any queued commands
                    TraceException(ex, "HardResyncException");
                    HardResync();
                }
                catch (DnSerialPortException ex)
                {
                    // a serious serial port error occurred -- close and reopen
                    // the serial port, and discard any queued commands
                    TraceException(ex);
                    FireLogMessage(Resources.CommunicationError,
                        GetExceptionFullMessage(null, ex));
                    HardResync();
                }
            }
        }
        catch (QuittingException ex)
        {
            // stop worker thread by exiting this method
            TraceException(ex);
        }

        TraceInfo("worker thread exiting");

        // clear state
        m_workerThreadId = 0;
        m_workerThreadWaitHandle = null;

        // close the serial port, if it's open
        lock (m_lock)
            CloseSerialPort();

        TraceInfo("worker thread exited");
    }

    
Cm11.ProcessNotification Method

Called when we receive one or more bytes from the CM11. This method reads the bytes and processes them as a notification.

    void ProcessNotification()
    {
        // set <b1> to the first byte of the notification; return if none
        byte? b1 = m_serialPort.ReadByte();
        if (!b1.HasValue)
            return;

        // process the notification
        if (b1 == 0xA5)
        {
            // this is a *clock set request*, i.e. the CM11 is requesting that
            // we set its internal clock...

            // feedback to application
            FireLogMessage(Resources.Cm11Notification, b1,
                Resources.Cm11TimeRequestNotification);

            // set <reply> to a *clock set reply* to send to the CM11...
            DateTime now = DateTime.Now;
            int minutes = now.Hour * 60 + now.Minute;
            DateTime startOfYear = new DateTime(now.Year, 1, 1);
            int dayOfYear = (int) (now - startOfYear).TotalDays;
            byte[] reply = new byte[]
            {
                // set-time header:
                0x9B,
                // seconds component of current time
                (byte) now.Second,
                // (minute-of-day component of current time) % 120
                (byte) (minutes % 120), 
                // (minute-of-day component of current time) / 120
                (byte) (minutes / 120),
                // low order 8 bits of day of the year
                (byte) (dayOfYear & 0xFF),
                // high order bit of day of the year in MSB,
                // day of week specified by setting bit 0 (Sunday)
                // through 7 (Saturday)
                (byte) (((dayOfYear & 0x100) >> 1) |
                        (1 << (int) now.DayOfWeek)),
                // monitored house code in upper 4 bits; bit 3 is
                // reserved; bit 2 is battery timer clear flag;
                // bit 1 is monitored status clear flag; bit 0 is
                // timer purge flag
                (byte) (s_codesToNibbles[0] << 4)
            };

            // write <reply> to the CM11
            WriteToSerialPort(reply, Resources.SettingCm11Clock);

            // the CM11 will send one 0xA5 request every second after power-up,
            // so it's not unusual for a series of these to be present in the
            // serial input buffer; for efficiency, delete all remaining 0xA5
            // bytes in the input buffer
            while (m_serialPort.PeekByte() == 0xA5)
                m_serialPort.ReadByte();
        }
        else
        if (b1 == 0x5A)
        {
            // this is a *device notification*...

            // feedback to application
            FireLogMessage(Resources.Cm11Notification, b1,
                Resources.Cm11X10DeviceNotification);

            // tell the CM11 we received the first byte of the notification
            WriteToSerialPort(new byte[] { 0xC3 }, Resources.AckNotification);

            // wait for the CM11 to send a count of data bytes; ignore all
            // 0x5A bytes we receive, because those are likely just extra
            // 0x5A's that got queued before we got around to responding
            // (since the CM11 sends one 0x5A per second until we respond)
            int dataByteCount;
            while (true)
            {
                if ((dataByteCount = ReadSerialPortByte(SERIAL_TIMEOUT))
                    != 0x5A)
                    break;
            }
            FireLogMessage(Resources.Cm11NotificationDataLength, dataByteCount);
            if ((dataByteCount < 2/*note below*/) || (dataByteCount > 9))
            {
                // invalid data byte count -- ignore this notification;
                // note that there must be at least 2 data bytes: one for the
                // data byte specifier and at least one following data byte
                FireLogMessage(Resources.InvalidReply);
                SoftResync();
                return;
            }

            // read the specified number of bytes from the CM11
            byte[] dataBytes = ReadSerialPortBytes(dataByteCount,
                SERIAL_TIMEOUT);
            FireLogMessage("CM11 --> {0} ({1})", FormatBytes(dataBytes),
                Resources.DeviceNotificationData);

            // parse and process the notification bytes
            NotificationDataParser parser =
                new NotificationDataParser(dataBytes);
            while (!parser.AtEndOfDataBytes)
            {
                bool isFunctionCode;
                byte dataByte = parser.GetNextDataByte(out isFunctionCode);
                if (!isFunctionCode)
                {
                    // this is an *address code*
                    ProcessDeviceNotification(false, dataByte, 0);
                }
                else
                {
                    // this is a *function code* -- check to see if there is
                    // a following *function parameter* byte and set
                    // <parameterByte> to it if so, otherwise set
                    // <parameterByte> to zero
                    byte parameterByte = 0;
                    if (((dataByte & 0xF) == (int) X10Function.Dim) ||
                        ((dataByte & 0xF) == (int) X10Function.Brighten))
                    {
                        // this is a *function code* that requires a parameter
                        if (!parser.AtEndOfDataBytes)
                        {
                            bool isFunctionCode2;
                            byte b = parser.GetNextDataByte(
                                out isFunctionCode2);
                            if (!isFunctionCode2)
                                parameterByte = b;
                            else
                                parser.UngetDataByte();
                        }
                    }
                    ProcessDeviceNotification(true, dataByte, parameterByte);
                }
            }
        }
        else
        {
            // unknown notification (ignored)
            FireLogMessage(Resources.Cm11Notification, b1,
                Resources.Cm11UnknownNotification);
        }
    }

    
Cm11.ProcessDeviceNotification Method

Called when we receive a device notification from the CM11. This method is called once for each address code or function code contained within each device notification.

Parameters

isFunctionCode

true if this is a function code, false if it's an address code.

dataByte

The address code or function code byte.

parameterByte

If this is a function code, and the function code nibble is is X10Function.Dim or X10Function.Brighten, parameterByte contains the additional parameter byte specifying the relative brighten-by or dim-by amount, from 0 (meaning 0%) to 210 (meaning 100%).

    void ProcessDeviceNotification(bool isFunctionCode, byte dataByte,
        byte parameterByte)
    {
        // set <commandName> to the string version of the notification,
        // e.g. "On" or "Dim"; set <commandParameter> to the parameter value,
        // i.e. a brighten-by or dim-by value in the range 0 to 100 inclusive
        // in the case of "Dim" and "Brighten" commands, -1 otherwise
        string commandName;
        int commandParameter;
        if (isFunctionCode)
        {
            // this is a *function code*
            X10Function x10Function = (X10Function) (dataByte & 0xF);
            byte highNibble = (byte) ((dataByte >> 4) & 0xF);
            if ((x10Function == X10Function.Dim) ||
                (x10Function == X10Function.Brighten))
            {
                // this is a "Dim" or "Brighten" notification
                commandName = String.Format("{0}.{1}",
                    HouseCodeNibbleToChar(highNibble), x10Function);
                commandParameter = (parameterByte * 100 + 105) / 210;

                // keep track of which devices are *currently addressed*
                m_addressTracker.RegisterFunctionCommand(highNibble);
            }
            else
            if ((x10Function == X10Function.PresetDim1) ||
                (x10Function == X10Function.PresetDim2))
            {
                // this is a *device level notification* -- see Cm11.doc
                byte appended5Bits = (byte) ((highNibble << 1) |
                    ((x10Function == X10Function.PresetDim2) ? 1 : 0));
                byte reversed5Bits = (byte)
                    (((appended5Bits & 0x10) >> 4) |
                     ((appended5Bits & 0x08) >> 2) |
                     (appended5Bits & 0x04) |
                     ((appended5Bits & 0x02) << 2) |
                     ((appended5Bits & 0x01) << 4));
                commandName = "Level";
                commandParameter = ((reversed5Bits + 1) * 100 + 16) / 32;
            }
            else
            {
                // this is some other *function code* notification
                commandName = String.Format("{0}.{1}",
                    HouseCodeNibbleToChar(highNibble), x10Function);
                commandParameter = -1;

                // keep track of which devices are *currently addressed*
                m_addressTracker.RegisterFunctionCommand(highNibble);
            }
        }
        else
        {
            // this is an *address code* notification
            commandName = AddressByteToString(dataByte);
            commandParameter = -1;

            // keep track of which devices are *currently addressed*
            m_addressTracker.RegisterAddressCommand(dataByte);
        }

        // fire a low-level notification event
        FireNotification(commandName, commandParameter);

        // fire a high-level notification event, if appropriate
        int percent;
        if (isFunctionCode)
        {
            X10Function x10Function = (X10Function) (dataByte & 0xF);
            byte houseCodeNibble = (byte) ((dataByte >> 4) & 0xF);
            switch (x10Function)
            {

            case X10Function.On:

                foreach (string address in
                        m_addressTracker.GetAddressedDevices(houseCodeNibble))
                    FireOnReceived(address);
                break;

            case X10Function.Off:

                foreach (string address in
                        m_addressTracker.GetAddressedDevices(houseCodeNibble))
                    FireOffReceived(address);
                break;

            case X10Function.Brighten:

                percent = (parameterByte * 100 + 105) / 210;
                foreach (string address in
                        m_addressTracker.GetAddressedDevices(houseCodeNibble))
                    FireBrightenReceived(address, percent);
                break;

            case X10Function.Dim:

                percent = (parameterByte * 100 + 105) / 210;
                foreach (string address in
                        m_addressTracker.GetAddressedDevices(houseCodeNibble))
                    FireDimReceived(address, percent);
                break;

            case X10Function.AllLightsOn:

                FireAllLightsOnReceived(
                    HouseCodeNibbleToChar(houseCodeNibble));
                break;

            case X10Function.AllLightsOff:

                FireAllLightsOffReceived(
                    HouseCodeNibbleToChar(houseCodeNibble));
                break;

            case X10Function.AllOff:

                FireAllOffReceived(HouseCodeNibbleToChar(houseCodeNibble));
                break;
            }
        }
    }

    
Cm11.ProcessQueuedCommands Method

Called when we one or more commands were queued in m_commandQueue. This method reads and executes queued commands until Close is called, serial input is received from the CM11, or there are no more queued commands to execute, whichever happens first.

    void ProcessQueuedCommands()
    {
        // process
        while (true)
        {
            // return if Close() was called or serial input is received from
            // the CM11; in the latter case is due to the fact that we need to
            // process C11 notifications before executing commands, because the
            // CM11 ignores commands while it's in notification mode
            WaitResult wr = WaitForSerialInputOrCommand(null, true);
            if (wr != WaitResult.CommandQueued)
                return;

            // set <command> to the next command in the queue, but don't remove
            // it from the queue yet
            string command;
            lock (m_commandQueue)
            {
                // check the command queue again in case it was emptied by
                // another thread
                if (m_commandQueue.Count == 0)
                    return;
                command = m_commandQueue.Peek();
            }

            // execute <command> -- don't "lock <m_commandQueue>" here because
            // ProcessCommand() could take quite a while and we don't want to
            // lock the command queue for the entire time
            if (ProcessCommand(command, m_addressTracker, true))
            {
                // the command was successfully executed -- remove it from the
                // queue
                lock (m_commandQueue)
                {
                    // check the command queue again in case it was emptied by
                    // another thread (with possibly another command queued --
                    // we don't want to delete the wrong command)
                    if ((m_commandQueue.Count > 0) &&
                        (command == m_commandQueue.Peek()))
                    {
                        m_commandQueue.Dequeue();
                        m_commandQueueChangeCount++;
                    }
                    if (m_commandQueue.Count == 0)
                        SetIdleState(true);
                }
            }
            else
            {
                // the command could not be executed, presumably because a
                // notification arrived from the CM11 -- leave the command in
                // the queue for now, and return so the notification can be
                // processed
                return;
            }
        }
    }

    
Cm11.SoftResync Method

Performs a "soft resynchronization" of serial port communications. This process attempts to correct a serial port communication problem without losing queued commands.

Remarks

This method informs the application that the CM11 is behaving unexpectedly (for example, an invalid reply), then pauses for a short time to allow the CM11 to reset its internal state and thereby (hopefully) resync communications with the PC.

During most back-and-forth communication exchanges between the CM11 and the PC, if the CM11 doesn't hear back from the PC within a second or so it appears to cancel the exchange. This method exploits that characteristic as a way to get the CM11 back to a normal state (waiting for a command for the PC or sending a new notification to the PC).

    void SoftResync()
    {
        try
        {
            FireLogMessage(Resources.Pausing, SERIAL_TIMEOUT);
            Wait(SERIAL_TIMEOUT);
        }
        catch (HardResyncException ex)
        {
            // a serious serial port error occurred -- close and reopen
            // the serial port, and discard any queued commands
            TraceException(ex, "HardResyncException during SoftResync");
            HardResync();
        }
        catch (DnSerialPortException ex)
        {
            // a serious serial port error occurred -- close and reopen
            // the serial port, and discard any queued commands
            TraceException(ex, "DnSerialPortException during SoftResync");
            HardResync();
        }
    }

    
Cm11.HardResync Method

Performs a "hard resynchronization" of serial port communications. This process attempts to correct a serious serial port communication problem by disconnecting and reconnecting the serial port. Any queued commands are discarded, to prevent commands from building up indefinitely during a period of poor communication with the CM11.

    void HardResync()
    {
        // loop until the serial port is successfully reopened
        for (int attemptNumber = 1; ; attemptNumber++)
        {
            TraceInfo("HardResync: attempt #{0}", attemptNumber);
            try
            {
                // purge the serial port input and output buffers, then close
                // the serial port
                CloseSerialPort();

                // discard any queued commands; set <discardedMessageCount> to
                // the number of discarded messages
                int discardedMessagesCount;
                lock (m_commandQueue)
                {
                    discardedMessagesCount = m_commandQueue.Count;
                    Clear();
                }

                // notify the application of the situation
                FireError(Resources.HardResyncOccurred,
                        discardedMessagesCount);

                // give the CM11 time to reset its state; this also prevents
                // rapid close/open/close/open/etc. of the serial port if
                // reopening the serial port (below) fails continually
                Wait(SERIAL_TIMEOUT);

                // reopen the serial port and purge any collected serial port
                // input
                OpenSerialPort();
                m_serialPort.Purge();

                // done with no exceptions
                FireLogMessage(Resources.HardResyncComplete);
                break;
            }
            catch (HardResyncException ex)
            {
                TraceException(ex,
                    "HardResyncException during HardResync");
            }
            catch (DnSerialPortException ex)
            {
                TraceException(ex, "DnSerialPortException during HardResync");
            }
        }
    }

    
Cm11.Wait Method

Waits for a specified number of milliseconds.

Parameters

timeoutMsec

The number of milliseconds to wait.

Exceptions
Exception type Condition
QuittingException

Thrown if Close was called before or during this method call.

    void Wait(int timeoutMsec)
    {
        DateTime timeoutTime = DateTime.Now.AddMilliseconds(timeoutMsec);
        bool timeout = false;
        while (true)
        {
            // if Close() was called, it's time to quit
            if (m_quitting)
                throw new QuittingException();

            // see if it's time to return
            if (timeout)
                return;

            // sleep until m_wakeWorkerThread.Set() is called or we reach
            // <timeoutTime> (if not null), whichever happens first; set
            // <timeout> to true if the latter happens first, false if the
            // former happens first
            TimeSpan sleepTime = timeoutTime - DateTime.Now;
            if (sleepTime <= TimeSpan.Zero)
                timeout = true;
            else
                timeout = !m_wakeWorkerThread.WaitOne(sleepTime, false);
        }
    }

    
Cm11.WaitForSerialInputOrCommand Method

Waits until one or more bytes are received from the CM11, or a command is queued in m_commandQueue, or Close is called.

Parameters

timeoutTime

The time at which to give up waiting and throw a TimeoutException. If timeoutTime is null, then this method performs a poll returns immediately; in this case, WaitResult.Idle is returned if no bytes are in the serial input buffer and no commands are queued in m_commandQueue and Close has not been called. Use DateTime.MaxValue to wait indefinitely, i.e. never time out.

waitForCommand

If true, wait for either serial input or a queued command. If false, wait only for serial input.

Return Value

One of the following values:

Return Value Description
WaitResult.Idle No bytes are in the serial input buffer and no commands are queued in m_commandQueue. In this situation, WaitResult.Idle is only returned if timeoutTime; otherwise, this method waits until the specified timeout time and then, if no serial input or queued commands are available yet and Close has not been called, TimeoutException is thrown.
WaitResult.SerialInput One or more bytes were received from the CM11 device and are waiting to be read in m_serialPort.
WaitResult.CommandQueued One or more commands were queued by the application into m_commandQueue and are waiting to be executed.

Exceptions
Exception type Condition
TimeoutException

Thrown if timeoutTime was reached before serial input was received or a command was queued.

QuittingException

Thrown if Close was called before or during this method call.

    WaitResult WaitForSerialInputOrCommand(DateTime? timeoutTime,
        bool waitForCommand)
    {
        bool timeout = false; // true once a timeout occurs
        while (true)
        {
            // if Close() was called, it's time to quit
            if (m_quitting)
                throw new QuittingException();

            // check to see if one or more bytes are now present at the serial
            // port
            if (m_serialPort.PeekByte() != null)
                return WaitResult.SerialInput;

            // check to see if a command is already available in the queue,
            // unless <waitForCommand> is false
            if (waitForCommand)
            {
                lock (m_commandQueue)
                {
                    if (m_commandQueue.Count > 0)
                        return WaitResult.CommandQueued;
                }
            }

            // if the caller is polling, return immediately
            if (timeoutTime == null)
                return WaitResult.Idle;

            // if a timeout occurred in a previous iteration of this loop,
            // throw TimeoutException
            if (timeout)
            {
                TraceInfo("WaitForSerialInputOrCommand: Timeout: {0} msec late",
                    (DateTime.Now - timeoutTime.Value).TotalMilliseconds);
                throw new TimeoutException();
            }

            // sleep until m_wakeWorkerThread.Set() is called or we reach
            // <timeoutTime>, whichever happens first; set <timeout> to true
            // if the latter happens first, false if the former happens first
            if (timeoutTime.Value != DateTime.MaxValue)
            {
                // wait for the specified time
                TimeSpan sleepTime = timeoutTime.Value - DateTime.Now;
                if (sleepTime <= TimeSpan.Zero)
                    timeout = true;
                else
                    timeout = !m_wakeWorkerThread.WaitOne(sleepTime, false);
            }
            else
            {
                // wait indefinitely
                m_wakeWorkerThread.WaitOne();
            }
        }
    }

    
Cm11.WaitForSerialInput Method

Waits until one or more bytes are received from the CM11, or Close is called.

Parameters

timeoutTime

The time at which to give up waiting and throw a TimeoutException. Use DateTime.MaxValue to wait indefinitely, i.e. never time out.

Exceptions
Exception type Condition
TimeoutException

Thrown if timeoutTime was reached before serial input was received or a command was queued.

QuittingException

Thrown if Close was called before or during this method call.

    void WaitForSerialInput(DateTime timeoutTime)
    {
        WaitResult wr = WaitForSerialInputOrCommand(timeoutTime, false);
        Debug.Assert(wr == WaitResult.SerialInput);
    }

    
Cm11.SetIdleState Method

Updates the idle state, i.e. specifies if no commands are currently being processed by the Cm11 class.

Parameters

idle

    void SetIdleState(bool idle)
    {
        // do nothing if the idle state hasn't changed
        if (m_idle == idle)
            return;
        m_idle = idle;

        // update <m_idleEvent> (used by WaitUntilIdle)
        if (m_idle)
            m_idleEvent.Set();
        else
            m_idleEvent.Reset();

        // notify the application of the idle state change
        FireIdleStateChange(m_idle);
    }

    
Cm11.ReadSerialPortByte Method

Reads a byte from the serial port.

Parameters

timeoutMsec

The maximum number of milliseconds to wait for the requested data before throwing a TimeoutException

Exceptions
Exception type Condition
TimeoutException

Thrown if timeoutMsec milliseconds passed before the requested data was read from the serial port.

QuittingException

Thrown if Close was called before or during this method call.

    byte ReadSerialPortByte(int timeoutMsec)
    {
        DateTime timeoutTime = DateTime.Now.AddMilliseconds(timeoutMsec);
        while (true)
        {
            WaitForSerialInput(timeoutTime);
            byte? b = m_serialPort.ReadByte();
            if (b.HasValue)
                return b.Value;
        }
    }

    
Cm11.PeekSerialPortByte Method

Reads a byte from the serial port and returns it, but "pushes" the byte back to the serial port so it will be read again on the next call to ReadSerialPortByte or PeekSerialPortByte.

Parameters

timeoutMsec

The maximum number of milliseconds to wait for the requested data before throwing a TimeoutException

Exceptions
Exception type Condition
TimeoutException

Thrown if timeoutMsec milliseconds passed before the requested data was read from the serial port.

QuittingException

Thrown if Close was called before or during this method call.

    byte PeekSerialPortByte(int timeoutMsec)
    {
        DateTime timeoutTime = DateTime.Now.AddMilliseconds(timeoutMsec);
        while (true)
        {
            WaitForSerialInput(timeoutTime);
            byte? b = m_serialPort.PeekByte();
            if (b.HasValue)
                return b.Value;
        }
    }

    
Cm11.ReadSerialPortBytes Method

Reads a series of bytes from the serial port.

Parameters

byteCount

The number of bytes to read.

timeoutMsec

The maximum number of milliseconds to wait for the requested data before throwing a TimeoutException

Return Value

The byteCount bytes read from the serial port.

Exceptions
Exception type Condition
TimeoutException

Thrown if timeoutMsec milliseconds passed before the requested data was read from the serial port.

QuittingException

Thrown if Close was called before or during this method call.

    byte[] ReadSerialPortBytes(int byteCount, int timeoutMsec)
    {
        DateTime timeoutTime = DateTime.Now.AddMilliseconds(timeoutMsec);
        byte[] result = new byte[byteCount];
        int offset = 0; // where in <result> to read next
        while (true)
        {
            int bytesDesired = byteCount - offset;
            if (bytesDesired <= 0)
                return result;
            WaitForSerialInput(timeoutTime);
            int bytesRead = m_serialPort.Read(result, offset, bytesDesired);
            offset += bytesRead;
        }
    }

    
Cm11.ExpectingEmptySerialInputBuffer Method

Throws an exception if the serial input buffer is not empty.

    void ExpectingEmptySerialInputBuffer()
    {
        // read all bytes in the serial input buffer; set <bytesList> to
        // a string containing the bytes in hex format (e.g. "8A B2 1C")
        StringBuilder bytesList = null;
        while (m_serialPort.PeekByte() != null)
        {
            if (bytesList == null)
                bytesList = new StringBuilder(100);
            else
                bytesList.Append(' ');
            bytesList.AppendFormat("{0:X2}", m_serialPort.ReadByte());
        }

        // if any bytes were read, throw an exception
        if (bytesList != null)
        {
            FireLogMessage(Resources.Cm11UnexpectedBytes, bytesList);
        }
    }

    
Cm11.ProcessCommand Method

Executes a command, or simply checks its syntax and usage.

Parameters

command

The DwellNet.Cm11 command to process. Commands are case-sensitive; for example, "Dim70" is a valid command, but "dim70" is not. See Cm11Help.htm for a list of valid DwellNet.Cm11 commands.

addressTracker

Tracks which X10 devices are currently addressed.

execute

true to execute the command, false to check its syntax and usage. If execute is true, this method should only be called from the worker thread.

Return Value

true if the command was executed, false if not. false is returned if execute is false.

Exceptions
Exception type Condition
Cm11InvalidCommandException

Thrown when the command is incorrect (e.g. incorrect syntax, inconsistent house codes, etc.) See Remarks for more information.

Remarks

The CM11 doesn't allow device address for the same function (e.g. Dim) to have different house codes. For example, the command sequence "A1 A2 Dim70" is valid, but "A1 B2 Dim70" is not. ProcessCommand checks for this error condition by using , which the caller should ensure is created in single-house mode. If a house code mismatch occurs, a Cm11InvalidCommandException is thrown.

    bool ProcessCommand(string command, AddressTracker addressTracker,
        bool execute)
    {
        // parse <command>; set <commandBytes> to the byte sequence for the
        // X10 address or function corresponding to <command>; set
        // <commandLabel> to a label for the command to use in a LogMessage
        // event; exception: if <command> is just a house code ("A" through
        // "P", inclusive), just set <m_currentHouseCodeNibble> and return,
        // since this command simply sets internal state in this class -- no
        // communication with the CM11 hardware is required
        byte[] commandBytes;
        string commandLabel;
        byte? houseCodeNibble;
        byte? addressByte;
        bool hexCommand;
        if ((addressByte = ParseAddress(command)) != null)
        {
            // <command> is an X10 address, e.g. "A1"...

            // this isn't a "Hex<hex-digits>" command
            hexCommand = false;

            // set <commandLabel> to a string to use in the LogMessage event
            commandLabel = String.Format(Resources.CommandType, command);

            // set <houseCodeNibble> to the nibble value of the house
            // code of this address
            houseCodeNibble = (byte) (addressByte.Value >> 4);

            // set <commandBytes> to the byte sequence for an X10 address
            commandBytes = new byte[] { (byte) 0x04, addressByte.Value };

            // update the state of <addressTracker>
            addressTracker.RegisterAddressCommand(addressByte.Value);
        }
        else
        if ((commandBytes = ParseHexCommand(command)) != null)
        {
            // <command> is a "Hex<hex-digits>" command
            hexCommand = true;

            // set <commandLabel> to a string to use in the LogMessage event
            commandLabel = String.Format(Resources.CommandType, command);
        }
        else
        if ((commandBytes = ParseFunction(command,
                addressTracker.LastCommandHouseCodeNibble)) != null)
        {
            // <command> is an X10 function, e.g. "Dim70"...

            // this isn't a "Hex<hex-digits>" command
            hexCommand = false;

            // set <commandLabel> to a string to use in the LogMessage event
            houseCodeNibble = (byte) ((commandBytes[1] >> 4) & 0xF);
            X10Function x10Function = (X10Function) (commandBytes[1] & 0xF);
            int amount22 = (commandBytes[0] >> 3) & 0x1F;
            commandLabel = String.Format(Resources.CommandType,
                FormatFunctionCommand(houseCodeNibble.Value, x10Function,
                    amount22));

            // update the state of <addressTracker>
            addressTracker.RegisterFunctionCommand(houseCodeNibble.Value);
        }
        else
        {
            // <command> is invalid
            throw new Cm11InvalidCommandException(Resources.InvalidCommand,
                command);
        }

        // if we're only performing a syntax & usage check, we're done
        if (!execute)
            return false; // command not executed

        // send the command to the CM11, and wait for a correct checksum reply;
        // retry sending the command a few times if necessary
        for (int retry = 0; ; retry++)
        {
            // if we ran out of retries, reset communications
            int maxRetries = 5;
            if (retry > maxRetries)
            {
                FireLogMessage(Resources.CommandRetryCountExceeded, maxRetries);
                throw new HardResyncException();
            }

            // if this is a retry, tell the application
            if (retry > 0)
                FireLogMessage(Resources.RetryingCommand);

            // just before writing to the serial port we need to check to see
            // if the serial input buffer is empty; if it's not, we should
            // abort sending this command to the CM11 because the CM11 will
            // ignore any command sent while it is in *device notification
            // mode*
            if (m_serialPort.PeekByte() != null)
                return false; // command not executed

            // send a *normal command* or a raw hex command to the CM11 device
            WriteToSerialPort(commandBytes, "{0}", commandLabel);

            // if we sent a raw hex command, we're done -- for example we can't
            // assume that a checksum is to be expected
            if (hexCommand)
                return true;

            // wait for a checksum from the CM11
            byte receivedChecksum;
            try
            {
                receivedChecksum = PeekSerialPortByte(SERIAL_TIMEOUT);
            }
            catch (TimeoutException ex)
            {
                // timeout -- try again
                TraceException(ex);
                continue;
            }
            FireLogMessage(Resources.Cm11Checksum, receivedChecksum);

            // if the checksum is correct move on to the next step of the
            // protocol
            byte correctChecksum = CalculateChecksum(commandBytes);
            if (receivedChecksum == correctChecksum)
            {
                // we "peeked" the checksum above, so read it now
                ReadSerialPortByte(SERIAL_TIMEOUT);
                break;
            }

            // incorrect checksum
            FireLogMessage(Resources.IncorrectChecksum, correctChecksum);
            if (receivedChecksum == 0x5A)
            {
                // Our command probably** got interrupted by a notification;
                // leave the 0x5A in the serial input buffer and return so
                // the notification can be processed before the command is
                // retried (since the CM11 ignores commands while it's in
                // *device notification mode*.
                //
                // ** Why "probably"? because it's possible that 0x5A is simply
                // the wrong checksum.  (It's also possible that the correct
                // checksum is 0x5A and we incorrectly assumed success above.)
                // If that happens, we'll let the normal resync logic of Cm11
                // take care of the (hopefully rare) situation.
                //
                FireLogMessage(Resources.PushedByteBack, receivedChecksum);
                return false;
            }
            else
            {
                // plain old wrong checksum -- read it (we "peeked" it above),
                // wait for a short period (in case the CM11 is in the middle
                // of sending a bunch of other bytes), then purge the remaining
                // serial port input and output buffer contents and try again
                ReadSerialPortByte(SERIAL_TIMEOUT);
                Wait(250);
                m_serialPort.Purge();
            }
        }

        // the CM11 sent a correct checksum; acknowledge it with a 0x00
        ExpectingEmptySerialInputBuffer();
        WriteToSerialPort(new byte[] { 0x00 }, Resources.AckChecksum);

        // wait for a 0x55 or 0x5A reply from the CM11
        byte ackReply = PeekSerialPortByte(SERIAL_TIMEOUT);
        FireLogMessage(Resources.Cm11AckReply, ackReply);
        if (ackReply == 0x55)
        {
            // the command was successfully executed -- we "peeked" <ackReply>
            // above above, so read it now
            ReadSerialPortByte(SERIAL_TIMEOUT);
            return true;
        }
        else
        if (ackReply == 0x5A)
        {
            // the command was successfully executed; during the command,
            // the CM11 received a powerline notification which it is now
            // sending to the PC -- leave the 0x5A in the serial port "peek"
            // buffer so the notification will be processed next
            FireLogMessage(Resources.PushedByteBack, ackReply);
            return true;
        }
        else
        {
            // reply incorrect -- reset communications; we "peeked" <ackReply>
            // above above, so read it now
            ReadSerialPortByte(SERIAL_TIMEOUT);
            FireLogMessage(Resources.IncorrectChecksumAckReply);
            throw new HardResyncException();
        }
    }

    
Cm11.ParseHouseCode Method

Parses a DwellNet.Cm11 house code; for example, "A".

Parameters

houseCode

The house code to parse. House codes are case-sensitive; "A" is a house code, but "a" is not.

Return Value

A CM11 house code nibble value, between 0 and 15 inclusive, corresponding to houseCode. Returns null if houseCode isn't a house code.

    byte? ParseHouseCode(string houseCode)
    {
        if (houseCode.Length != 1)
            return null;
        return HouseCodeToNibble(houseCode[0], false);
    }

    
Cm11.ParseAddress Method

Parses a DwellNet.Cm11 device address; for example, "A1".

Parameters

address

The address to parse. Addresses are case-sensitive; "A10" is a valid address, but "a10" is not.

Return Value

A CM11 address code byte value corresponding to the address. The house code nibble value (from s_codesToNibbles) is in the high-order 4 bits, and the device code nibble value (also from s_codesToNibbles) is in the low-order 4 bits. Returns null if address doesn't appear to be an address.

Exceptions
Exception type Condition
Cm11InvalidCommandException

Thrown when the syntax of address is incorrect. See Remarks for more information.

Remarks

Valid addreses include a house code between "A" and "P" inclusive and a device code between "1" and "16" inclusive; examples: "A1", "C12", "P16".

If isn't an address (for example, if is "Abc"), null is returned. However, if looks like an address address but the device code is out of range (for example, "A99"), a Cm11InvalidCommandException is thrown.

    byte? ParseAddress(string address)
    {
        // parse <address>; set <houseCodeNibble> and <deviceCodeNibble> to
        // the corresponding house code and device code nibbles; throw a
        // Cm11InvalidCommandException if the device code is out of range
        Match match = s_addressRegex.Match(address);
        if (!match.Success)
            return null;
        byte houseCodeNibble = HouseCodeToNibble(
            match.Groups[1].Value[0], true).Value;
        byte deviceCodeNibble = DeviceCodeToNibble(
            int.Parse(match.Groups[2].Value));

        // return the byte code corresponding to this address
        return (byte) ((houseCodeNibble << 4) | deviceCodeNibble);
    }

    
Cm11.AddressByteToString Method

Converts a CM11 address code byte (e.g. 0x6E) to its string representation (e.g. "A2").

Parameters

addressCode

    static string AddressByteToString(byte addressCode)
    {
        return HouseCodeNibbleToChar((byte) ((addressCode >> 4) & 0xF)) +
            DeviceCodeNibbleToString((byte) (addressCode & 0xF));
    }

    
Cm11.ParseFunction Method

Parses a DwellNet.Cm11 function command; for example, "A.On", "On", "B.Dim70", etc.

Parameters

command

The DwellNet.Cm11 command to parse. Commands are case-sensitive; for example, "Dim70" is a valid command, but "dim70" is not. See Cm11Help.htm for a list of valid DwellNet.Cm11 commands.

defaultHouseCodeNibble

The X10 house code nibble value to assume for the command (i.e. that the function will apply to) if command doesn't contain a house code prefix (e.g. "A."). -1 indicates that there is no default to use.

Return Value

The CM11 2-byte sequence corresponding to command, or null if command doesn't appear to be a function command.

Exceptions
Exception type Condition
Cm11InvalidCommandException

Thrown when the syntax or usage of command is incorrect. See Remarks for more information.

Remarks

If isn't a DwellNet.Cm11 command, null is returned. However, if looks like a valid Execute command but a parameter is out of range (for example, "Dim999"), a Cm11InvalidCommandException is thrown.

    byte[] ParseFunction(string command, int defaultHouseCodeNibble)
    {
        // see if <command> is a "Dim<percent>" command; if so, return its
        // 2-byte sequence
        byte[] bytes = ParseBrightenOrDimCommand(command,
            defaultHouseCodeNibble);
        if (bytes != null)
            return bytes;

        // see if <command> is follows the general syntax of any other
        // *function command* (than the ones checked for above); if so, set
        // <houseCode> and <commandName> to its house code prefix (e.g. "A", or
        // "" if none) and command name (e.g. "AllLightsOn")
        Match match = s_functionRegex.Match(command);
        if (!match.Success)
            return null;
        string houseCode = match.Groups[1].Value;
        string commandName = match.Groups[2].Value;

        // see if <commandName> is an X10Function member; if so, set
        // <x10Function> to it; if not, return null since at that point we know
        // <command> is not a valid DwellNet.Cm11 command
        X10Function x10Function;
        try
        {
            x10Function = (X10Function) Enum.Parse(typeof(X10Function),
                commandName, false);
        }
        catch (ArgumentException)
        {
            return null;
        }

        // set <houseCodeNibble> to the *house code nibble* of the command,
        // using <defaultHouseCodeNibble> as a default (if provided)
        byte? houseCodeNibble = ParseHouseCode(houseCode);
        if (houseCodeNibble == null)
        {
            if (defaultHouseCodeNibble < 0)
            {
                throw new Cm11InvalidCommandException(Resources.NoHouseCode,
                    command);
            }
            else
                houseCodeNibble = (byte) defaultHouseCodeNibble;
        }

        // return the 2-byte sequence for this command
        return new byte[]
        {
            (byte) 0x06,
            (byte) ((byte) x10Function | (houseCodeNibble << 4))
        };
    }

    
Cm11.ParseBrightenOrDimCommand Method

Parses a "Dim<percent>" or "Brighten<percent>" command; for example "Dim70".

Parameters

command

The command to parse. Commands are case-sensitive; "Dim70" is a valid command, but "dim70" is not.

defaultHouseCodeNibble

The X10 house code nibble value to assume for the command (i.e. that the function will apply to) if command doesn't contain a house code prefix (e.g. "A."). -1 indicates that there is no default to use.

Return Value

The CM11 2-byte sequence corresponding to command, or null if command doesn't appear to be a "Dim" or "Brighten" command.

Exceptions
Exception type Condition
Cm11InvalidCommandException

Thrown when the syntax or usage of command is incorrect. See Remarks for more information.

Remarks

Valid "Dim" commands are "Dim0", "Dim1", etc. up to and including "Dim100". Similarly, valid "Brighten" commands are "Brighten0", "Brighten1", etc. up to and including "Brighten100".

If isn't a "Dim<percent>" command (for example, if is "Abc"), null is returned. However, if looks like a "Dim" command but the dim amount is out of range (for example, "Dim999"), a Cm11InvalidCommandException is thrown.

    byte[] ParseBrightenOrDimCommand(string command,
        int defaultHouseCodeNibble)
    {
        // parse <command>; set <houseCodeNibble> to the *house code nibble*
        // (using <defaultHouseCodeNibble> as the default, if provided); set
        // <x10Function> to the X10Function value (X10Function.Dim or
        // X10Function.Brighten); set <amount> to the brighten-by or dim-by
        // percentage amount (0 to 100)
        X10Function x10Function;
        Match match = s_dimRegex.Match(command);
        if (match.Success)
            x10Function = X10Function.Dim;
        else
        {
            match = s_brightenRegex.Match(command);
            if (match.Success)
                x10Function = X10Function.Brighten;
            else
                return null; // not a "Dim" or "Brighten" command
        }
        byte? houseCodeNibble = ParseHouseCode(match.Groups[1].Value);
        if (houseCodeNibble == null)
        {
            if (defaultHouseCodeNibble < 0)
            {
                throw new Cm11InvalidCommandException(Resources.NoHouseCode,
                    command);
            }
            else
                houseCodeNibble = (byte) defaultHouseCodeNibble;
        }
        string amountString = match.Groups[2].Value;
        if (amountString.Length == 0)
        {
            // missing dim amount percent
            throw new Cm11InvalidCommandException(Resources.InvalidCommand,
                command);
        }
        int amount = int.Parse(match.Groups[2].Value);
        if (amount > 100)
        {
            throw new Cm11InvalidCommandException(Resources.BrightenOrDimAmountOutOfRange,
                amount);
        }

        // return the 2-byte sequence for this command
        return new byte[]
        {
            (byte) ((((amount * 22 + 50) / 100) << 3) | 0x06),
            (byte) ((byte) x10Function | (houseCodeNibble << 4))
        };
    }

    
Cm11.ParseHexCommand Method

Parses a "Hex<hex-digits>" command; for example "Hex046E". This command results in arbitrary bytes being sent to the CM11. This command is primarily for debugging purposes.

Parameters

command

The command to parse. Commands are case-sensitive; "Hex1A" is a valid command, but "hex1A" is not. However, the hexadecimal digits are not case-sensitive, so "Hex1a" is valid.

Return Value

The CM11 byte sequence corresponding to command, or null if command doesn't appear to be a "Hex" command.

Exceptions
Exception type Condition
Cm11InvalidCommandException

Thrown when the syntax of command is incorrect. See Remarks for more information.

Remarks

Valid "Hex" commands are "Hex<hex-digits>", where <hex-digits> consist of an even number of hexadecimal digits two or more. "Hex100".

If isn't a "Hex<hex-digits>" command (for example, if is "Abc"), null is returned. However, if looks like a "Hex" command but <hex-digits> doesn't contain an even number of valid hexadecimal digits, a Cm11InvalidCommandException is thrown.

    byte[] ParseHexCommand(string command)
    {
        // parse <command>; set <hexDigits> to the string of hexadecimal digits
        Match match = s_hexRegex.Match(command);
        if (!match.Success)
            return null; // not a "Hex<hex-digits>" command
        string hexDigits = match.Groups[1].Value;
        if ((hexDigits.Length == 0) || ((hexDigits.Length & 1) == 1))
        {
            // missing or odd number of hex-digits
            throw new Cm11InvalidCommandException(Resources.InvalidHexDigits,
                command);
        }

        // parse <hexDigits> into <bytes>
        byte[] bytes = new byte[hexDigits.Length / 2];
        for (int iByte = 0; iByte < bytes.Length; iByte++)
        {
            bytes[iByte] = (byte) int.Parse(hexDigits.Substring(iByte * 2, 2),
                System.Globalization.NumberStyles.AllowHexSpecifier);
        }

        return bytes;
    }

    
Cm11.FormatFunctionCommand Method

Formats a function command as a string, given the binary components of the function command.

Parameters

houseCodeNibble

The house code nibble of the house code of the function command.

x10Function

The function code nibble of the function code of the function command.

amount22

The brighten-by or dim-by amount of the function command -- a value between 0 and 22 inclusive. This parameter is ignored if the function command isn't a "Dim" or "Brighten" command.

    string FormatFunctionCommand(byte houseCodeNibble, X10Function x10Function,
        int amount22)
    {
        if ((x10Function == X10Function.Dim) ||
            (x10Function == X10Function.Brighten))
        {
            return String.Format("{0}.{1}{2}",
                HouseCodeNibbleToChar(houseCodeNibble), x10Function,
                (amount22 * 100 + 11) / 22);
        }
        else
        {
            return String.Format("{0}.{1}",
                HouseCodeNibbleToChar(houseCodeNibble), x10Function);
        }
    }

    
Cm11.WriteToSerialPort Method

Writes bytes to the serial port.

Parameters

bytes

The bytes to write to the serial port.

logMessageFormat

The format string of a log message to send to the application. The formatted string is provided to the application within a LogMessage event.

logMessageArgs

Formatting arguments for logMessageFormat.

Exceptions
Exception type Condition
Cm11CommunicationException

Thrown when an error occurs while attempting to communicate with the CM11 device.

Remarks

If a serial communication occurs, Close is called automatically and a Cm11CommunicationException is thrown.

    void WriteToSerialPort(byte[] bytes, string logMessageFormat,
        params object[] logMessageArgs)
    {
        FireLogMessage("CM11 <-- {0} ({1})", FormatBytes(bytes),
            String.Format(logMessageFormat, logMessageArgs));
        m_serialPort.Write(bytes, 0, bytes.Length);
    }

    
Cm11.FireOnReceived Method

Fires an OnReceived event.

Parameters

address

    void FireOnReceived(string address)
    {
        if (OnReceived != null)
        {
            if ((m_invokeEventsUsing == null) ||
                !m_invokeEventsUsing.InvokeRequired)
            {
                // fire the event on the current thread
                OnReceived(address);
            }
            else
            {
                // fire the event on an application thread
                m_invokeEventsUsing.Invoke(OnReceived,
                    new object[] { address });
            }
        }
    }

    
Cm11.FireOffReceived Method

Fires an OffReceived event.

Parameters

address

    void FireOffReceived(string address)
    {
        if (OffReceived != null)
        {
            if ((m_invokeEventsUsing == null) ||
                !m_invokeEventsUsing.InvokeRequired)
            {
                // fire the event on the current thread
                OffReceived(address);
            }
            else
            {
                // fire the event on an application thread
                m_invokeEventsUsing.Invoke(OffReceived,
                    new object[] { address });
            }
        }
    }

    
Cm11.FireBrightenReceived Method

Fires a BrightenReceived event.

Parameters

address

percent

    void FireBrightenReceived(string address, int percent)
    {
        if (BrightenReceived != null)
        {
            if ((m_invokeEventsUsing == null) ||
                !m_invokeEventsUsing.InvokeRequired)
            {
                // fire the event on the current thread
                BrightenReceived(address, percent);
            }
            else
            {
                // fire the event on an application thread
                m_invokeEventsUsing.Invoke(BrightenReceived,
                    new object[] { address, percent });
            }
        }
    }

    
Cm11.FireDimReceived Method

Fires a DimReceived event.

Parameters

address

percent

    void FireDimReceived(string address, int percent)
    {
        if (DimReceived != null)
        {
            if ((m_invokeEventsUsing == null) ||
                !m_invokeEventsUsing.InvokeRequired)
            {
                // fire the event on the current thread
                DimReceived(address, percent);
            }
            else
            {
                // fire the event on an application thread
                m_invokeEventsUsing.Invoke(DimReceived,
                    new object[] { address, percent });
            }
        }
    }

    
Cm11.FireAllLightsOnReceived Method

Fires an AllLightsOnReceived event.

Parameters

houseCode

    void FireAllLightsOnReceived(char houseCode)
    {
        if (AllLightsOnReceived != null)
        {
            if ((m_invokeEventsUsing == null) ||
                !m_invokeEventsUsing.InvokeRequired)
            {
                // fire the event on the current thread
                AllLightsOnReceived(houseCode);
            }
            else
            {
                // fire the event on an application thread
                m_invokeEventsUsing.Invoke(AllLightsOnReceived,
                    new object[] { houseCode });
            }
        }
    }

    
Cm11.FireAllLightsOffReceived Method

Fires an AllLightsOffReceived event.

Parameters

houseCode

    void FireAllLightsOffReceived(char houseCode)
    {
        if (AllLightsOffReceived != null)
        {
            if ((m_invokeEventsUsing == null) ||
                !m_invokeEventsUsing.InvokeRequired)
            {
                // fire the event on the current thread
                AllLightsOffReceived(houseCode);
            }
            else
            {
                // fire the event on an application thread
                m_invokeEventsUsing.Invoke(AllLightsOffReceived,
                    new object[] { houseCode });
            }
        }
    }

    
Cm11.FireAllOffReceived Method

Fires an AllOffReceived event.

Parameters

houseCode

    void FireAllOffReceived(char houseCode)
    {
        if (AllOffReceived != null)
        {
            if ((m_invokeEventsUsing == null) ||
                !m_invokeEventsUsing.InvokeRequired)
            {
                // fire the event on the current thread
                AllOffReceived(houseCode);
            }
            else
            {
                // fire the event on an application thread
                m_invokeEventsUsing.Invoke(AllOffReceived,
                    new object[] { houseCode });
            }
        }
    }

    
Cm11.FireNotification Method

Fires a Notification event.

Parameters

commandName

commandParameter

    void FireNotification(string commandName, int commandParameter)
    {
        if (Notification != null)
        {
            if ((m_invokeEventsUsing == null) ||
                !m_invokeEventsUsing.InvokeRequired)
            {
                // fire the event on the current thread
                Notification(commandName, commandParameter);
            }
            else
            {
                // fire the event on an application thread
                m_invokeEventsUsing.Invoke(Notification,
                    new object[] { commandName, commandParameter });
            }
        }
    }

    
Cm11.FireIdleStateChange Method

Fires a IdleStateChange event.

Parameters

idle

    void FireIdleStateChange(bool idle)
    {
        if (IdleStateChange != null)
        {
            if ((m_invokeEventsUsing == null) ||
                !m_invokeEventsUsing.InvokeRequired)
            {
                // fire the event on the current thread
                IdleStateChange(idle);
            }
            else
            {
                // fire the event on an application thread
                m_invokeEventsUsing.Invoke(IdleStateChange,
                    new object[] { idle });
            }
        }
    }

    
Cm11.FireError Method

Formats an error message and fires an Error event containing that message.

Parameters

format

args

    void FireError(string format, params object[] args)
    {
        string message = String.Format(format, args);
        TraceInfo("ERROR: " + message);
        if (Error != null)
        {

            if ((m_invokeEventsUsing == null) ||
                !m_invokeEventsUsing.InvokeRequired)
            {
                // fire the event on the current thread
                Error(message);
            }
            else
            {
                // fire the event on an application thread
                m_invokeEventsUsing.Invoke(Error, new object[] { message });
            }
        }
    }

    
Cm11.FireLogMessage Method

Formats a log message and fires a LogMessage event containing that message.

Parameters

format

args

    void FireLogMessage(string format, params object[] args)
    {
        string message = String.Format(format, args);
        TraceInfo(message);
        if (LogMessage != null)
        {

            if ((m_invokeEventsUsing == null) ||
                !m_invokeEventsUsing.InvokeRequired)
            {
                // fire the event on the current thread
                LogMessage(message);
            }
            else
            {
                // fire the event on an application thread
                m_invokeEventsUsing.Invoke(LogMessage,
                    new object[] { message });
            }
        }
    }

    
Cm11.HouseCodeToNibble Method

Converts a house code ('A' to 'P') to a house code nibble value.

Parameters

houseCode

The house code, 'A' through 'P' inclusive. This corresponds to the house code value set on the X10 device. This value is case-sensitive.

exceptionOnError

If true, then an exception is thrown if houseCode isn't a valid house code. If false, then null is returned if houseCode isn't a valid house code.

Return Value

The house code nibble value corresponding to houseCode. For example, house code "A" corresponds to house code nibble value 0x6. Returns null if houseCode isn't a valid house code and exceptionOnError is false.

Exceptions
Exception type Condition
Cm11InvalidCommandException

Thrown if houseCode isn't a valid house code and exceptionOnError is true.

    byte? HouseCodeToNibble(char houseCode, bool exceptionOnError)
    {
        if ((houseCode >= 'A') && (houseCode <= 'P'))
            return s_codesToNibbles[houseCode - 'A'];
        else
        if ((houseCode > ' ') && (houseCode < 0x7F))
        {
            if (exceptionOnError)
            {
                throw new Cm11InvalidCommandException(Resources.InvalidHouseCode,
                    houseCode);
            }
            else
                return null;
        }
        else
        {
            if (exceptionOnError)
            {
                throw new Cm11InvalidCommandException(Resources.InvalidHouseCode,
                    String.Format("0x{0:X2}", houseCode));
            }
            else
                return null;
        }
    }

    
Cm11.HouseCodeNibbleToChar Method

Converts a CM11 house code nibble value (e.g. 0x6) to a character representation (e.g. "A").

Parameters

houseCodeNibble

    static char HouseCodeNibbleToChar(byte houseCodeNibble)
    {
        return (char) ('A' + s_nibblesToCodes[houseCodeNibble]);
    }

    
Cm11.DeviceCodeToNibble Method

Converts a device code (1 to 16) to a nibble value used in the X10 protocol.

Parameters

deviceCode

The device code, 1 through 16 inclusive. This corresponds to the device code value set on the X10 device.

    byte DeviceCodeToNibble(int deviceCode)
    {
        if ((deviceCode >= 1) && (deviceCode <= 16))
            return s_codesToNibbles[deviceCode - 1];
        else
        {
            throw new Cm11InvalidCommandException(Resources.InvalidDeviceCode,
                deviceCode);
        }
    }

    
Cm11.DeviceCodeNibbleToInt Method

Converts a CM11 device code nibble value (e.g. 0xE) to the integer form of its string representation (e.g. 2).

Parameters

deviceCodeNibble

    static int DeviceCodeNibbleToInt(byte deviceCodeNibble)
    {
        return s_nibblesToCodes[deviceCodeNibble] + 1;
    }

    
Cm11.DeviceCodeNibbleToString Method

Converts a CM11 device code nibble value (e.g. 0xE) to a string representation (e.g. "2").

Parameters

deviceCodeNibble

    static string DeviceCodeNibbleToString(byte deviceCodeNibble)
    {
        return String.Format("{0}", DeviceCodeNibbleToInt(deviceCodeNibble));
    }

    
Cm11.TraceInfo Method

Formats and writes a line of informational message text to trace listeners, in debug builds only. The line is prefixed with a string that identifies the message as coming from this class.

Parameters

format

The format string.

args

Formatting arguments

    [Conditional("DEBUG")]
    protected void TraceInfo(string format, params object[] args)
    {
#if DEBUG
        // no "lock (m_lock)" -- this operation is thread-safe
        Trace.WriteLine(String.Format("Cm11 {0:n3}: {1}",
            ((double)m_traceStopwatch.ElapsedMilliseconds) / 1000,
            String.Format(format, args)));
#endif
    }

    
Cm11.TraceException Method (Exception)

Writes a line of exception message text to trace listeners. The line is prefixed with a string that identifies the message as coming from this class.

Parameters

ex

An exception to display the message text of.

    protected void TraceException(Exception ex)
    {
        TraceException(ex, ex.GetType().Name);
    }

    
Cm11.TraceException Method (Exception, string, params object[])

Formats and writes a line of error message text to trace listeners, followed by message text from a provided exception. The line is prefixed with a string that identifies the message as coming from this class.

Parameters

ex

An exception to display the message text of.

format

The format string.

args

Formatting arguments

    protected void TraceException(Exception ex, string format,
        params object[] args)
    {
        // no "lock (m_lock)" -- this operation is thread-safe...
        Trace.WriteLine(String.Format("Cm11: {0}{1}",
            String.Format(format, args), GetExceptionFullMessage(": ", ex)));
    }

    
Cm11.GetExceptionFullMessage Method

Returns the concatenation of the exception message of a given exception and its inner exceptions.

Parameters

prefix

A string to prefix the returned exception message text with; for example, ": ". This prefix is not used if the exception message text is an empty string.

ex

The exception to retrieve the message of.

    static string GetExceptionFullMessage(string prefix, Exception ex)
    {
        StringBuilder exceptionMessage = new StringBuilder(1000);
        while (ex != null)
        {
            if (exceptionMessage.Length == 0)
            {
                if (prefix != null)
                    exceptionMessage.Append(prefix);
            }
            else
                exceptionMessage.Append(": ");
            exceptionMessage.Append(ex.Message);
            ex = ex.InnerException;
        }
        return exceptionMessage.ToString();
    }

    
Cm11.FormatBytes Method

Formats a byte array as a string in hexadecimal form.

Parameters

bytes

    static string FormatBytes(byte[] bytes)
    {
        StringBuilder result = new StringBuilder(3 * bytes.Length);
        foreach (byte b in bytes)
        {
            if (result.Length > 0)
                result.Append(' ');
            result.AppendFormat("{0:X2}", b);
        }
        return result.ToString();
    }

    
Cm11.CalculateChecksum Method

Calculates a byte checksum of a byte array.

Parameters

bytes

    static byte CalculateChecksum(byte[] bytes)
    {
        int checksum = 0;
        foreach (byte b in bytes)
            checksum += b;
        return (byte) (checksum & 0xFF);
    }

    //////////////////////////////////////////////////////////////////////////
    // Private Helper Classes and Enumerations
    //

    
Cm11.VoidDelegate Delegate

A method with no parameters and no return value.

    delegate void VoidDelegate();

    
Cm11.HardResyncException Class

Indicates that a serious problem occurred with the serial port (or the device connected to it) requiring the serial port to be reset and m_commandQueue to be cleared (to prevent commands from queuing up indefinitely with nowhere to go).

    class HardResyncException : Exception
    {
    }

    
Cm11.QuittingException Class

Indicates that m_quitting was set to true and so the executing code decided it was time to stop executing.

    class QuittingException : Exception
    {
    }

    
Cm11.WaitResult Enumeration

Specifies what situation caused a Wait* method to return.

Members
Name Description
CommandQueued

One or more commands were queued by the application into m_commandQueue and are waiting to be executed.

Idle

No bytes are in the serial input buffer, and m_commandQueue is empty.

SerialInput

One or more bytes were received from the CM11 device and are waiting to be read in m_serialPort.

    enum WaitResult
    {
        
Cm11.WaitResult.Idle Enumeration Value

No bytes are in the serial input buffer, and m_commandQueue is empty.

        Idle,

        
Cm11.WaitResult.SerialInput Enumeration Value

One or more bytes were received from the CM11 device and are waiting to be read in m_serialPort.

        SerialInput,

        
Cm11.WaitResult.CommandQueued Enumeration Value

One or more commands were queued by the application into m_commandQueue and are waiting to be executed.

        CommandQueued,
    }

    
Cm11.NotificationDataParser Class

Parses the data bytes sent within a device notification.

    class NotificationDataParser
    {
        // private fields
        byte[] m_dataBytes;
        int m_iDataByte; // index within <m_dataBytes>
        int m_dataByteSpecifier; // see Cm11.doc

        
Cm11.NotificationDataParser.AtEndOfDataBytes Property

Returns true if we've reached the end of the data bytes, in which case calling GetNextDataByte will throw an InvalidOperationException.

        public bool AtEndOfDataBytes
        {
            get
            {
                return m_iDataByte >= m_dataBytes.Length - 1;
            }
        }

        
NotificationDataParser Constructor

Initializes an instance of the NotificationDataParser class.

Parameters

dataBytes

Between 2 and 9 (inclusive) data bytes sent from the CM11.

        public NotificationDataParser(byte[] dataBytes)
        {
            // initialize state 
            m_dataBytes = dataBytes;
            m_dataByteSpecifier = dataBytes[0];

            // there must be at least 2 data bytes: one for the data byte
            // specifier and at least one following data byte
            if ((dataBytes.Length < 2) || (dataBytes.Length > 9))
                throw new ArgumentException("dataBytes");

            // set <m_dataByteSpecifier> to the *data byte specifier* which
            // contains one bit for each following data byte indicating if
            // that data byte is a function code (bit = 1) or an address or
            // parameter (bit = 0) -- see Cm11.doc for more
            // information
            m_dataByteSpecifier = dataBytes[0];
        }

        
Cm11.NotificationDataParser.GetNextDataByte Method

Returns the next data byte.

Parameters

isFunctionCode

Set to true if the returned data byte is a function code. Set to false if the returned data byte is either an address code or a function parameter.

Exceptions
Exception type Condition
InvalidOperationException

Thrown if AtEndOfDataBytes is true

        public byte GetNextDataByte(out bool isFunctionCode)
        {
            // advance <m_iDataByte>, unless we're at the end of the data bytes
            if (m_iDataByte >= m_dataBytes.Length - 1)
                throw new InvalidOperationException();
            else
                m_iDataByte++;

            // set <isFunctionCode> based on the bit in <m_dataByteSpecifier>
            // corrsponding to the current index within <m_dataBytes>
            isFunctionCode =
                ((m_dataByteSpecifier >> (m_iDataByte - 1) & 1) == 1);

            // return the data byte
            return m_dataBytes[m_iDataByte];
        }

        
Cm11.NotificationDataParser.UngetDataByte Method

Pushes the last byte returned by GetNextDataByte back into the parser, so it will be returned on the next call to GetNextDataByte.

Exceptions
Exception type Condition
InvalidOperationException

Thrown if GetNextDataByte wasn't previously called.

        public void UngetDataByte()
        {
            m_iDataByte--;
        }
    }

    
Cm11.AddressTracker Class

Tracks which X10 devices are currently addressed.

Remarks

An instance of this class can be used to keep track of which X10 devices are currently addressed -- see Cm11.doc for a definition of that term. This class doesn't interact with the CM11 hardware at all -- it's simply a record-keeper used internally by the Cm11 class.

    class AddressTracker
    {
        
Cm11.AddressTracker.m_addressedDevices Field

Tracks which X10 devices are currently addressed.

Remarks

Each element of m_addressedDevices contains information about devices with a single house code; the index of m_addressedDevices is a house code nibble. Within each element, bit number i (0x0 to 0xF) is set to 1 if the device with device code nibble i is currently addressed, 0 if the device is currently not addressed.

        UInt16[] m_addressedDevices = new UInt16[16];

        
Cm11.AddressTracker.m_singleHouseMode Field

If true, an exception is thrown if the caller makes two consecutive calls to RegisterAddressCommand that specify different house codes.

Remarks

For example, in single-house mode, "A1 A2 B1" is invalid, but "A1 A2 A.On B1 B.On" is valid. Also, "A.On" alone is valid (it applies to whatever the currently addressed devices are on the physical X10 network with house code "A"), and "A1 A2 A.On B.On" is also valid (the last "B.On" similarly applies to the currently addressed devices on the physical X10 network with house code "B".)

        bool m_singleHouseMode;

        
Cm11.AddressTracker.m_lastCommandWasFunction Field

true if RegisterFunctionCommand was called more recently than RegisterAddressCommand for a given house code.

Remarks

The index of m_lastCommandWasFunction is a house code nibble.

        bool[] m_lastCommandWasFunction = new bool[16];

        
Cm11.AddressTracker.m_lastCommandHouseCodeNibble Field

Holds the value of the LastCommandHouseCodeNibble property.

        int m_lastCommandHouseCodeNibble = -1;

        
Cm11.AddressTracker.LastCommandHouseCodeNibble Property

The house code nibble specified in the most recent call to RegisterAddressCommand or RegisterFunctionCommand. -1 means that neither of these methods was called yet.

        public int LastCommandHouseCodeNibble
        {
            get
            {
                return m_lastCommandHouseCodeNibble;
            }
        }

        
Cm11.AddressTracker.GetAddressedDevices Method

Returns an enumeration of the addresses (e.g. "A1") of all currently addressed devices with a given house code.

Parameters

houseCodeNibble

The house code nibble value of the house code for which to enumerate currently addressed devices of.

        public IEnumerable<string> GetAddressedDevices(byte houseCodeNibble)
        {
            if (houseCodeNibble >= 16)
                throw new ArgumentException("houseCodeNibble");
            UInt16 devices = m_addressedDevices[houseCodeNibble];
            char houseCode = HouseCodeNibbleToChar(houseCodeNibble);
            UInt16 mask = 1;
            byte deviceCodeNibble = 0;
            while (devices != 0)
            {
                if ((devices & mask) != 0)
                {
                    yield return String.Format("{0}{1}", houseCode,
                        DeviceCodeNibbleToInt(deviceCodeNibble));
                }
                deviceCodeNibble++;
                devices = (UInt16) (devices & ~mask);
                mask = (UInt16) (mask << 1);
            }
        }

        
AddressTracker Constructor

Initializes a new instance of the AddressTracker class.

Parameters

singleHouseMode

If true, an exception is thrown if two consecutive address commands have different house codes.

        public AddressTracker(bool singleHouseMode)
        {
            m_singleHouseMode = singleHouseMode;
        }

        
Cm11.AddressTracker.RegisterAddressCommand Method

Updates the internal state of this object to reflect the fact that an address command has executed and, as a result, a given X10 device is now currently addressed.

Parameters

addressCode

The address code of the device to add to the set of currently addressed devices.

Exceptions
Exception type Condition
Cm11InvalidCommandException

Thrown in single-house mode (see the constructor) when two consecutive address commands have different house codes.

        public void RegisterAddressCommand(byte addressCode)
        {
            // divide <addressCode> into nibbles
            byte houseCodeNibble = (byte) ((addressCode >> 4) & 0xF);
            byte deviceCodeNibble = (byte) (addressCode & 0xF);

            // if we're in single-house mode, it's an error for two consecutive
            // *address commands* to have different house codes
            if (m_singleHouseMode)
            {
                if ((m_lastCommandHouseCodeNibble != -1) &&
                    !m_lastCommandWasFunction[m_lastCommandHouseCodeNibble] &&
                    (m_lastCommandHouseCodeNibble != houseCodeNibble))
                {
                    throw new Cm11InvalidCommandException(
                        Resources.InconsistentHouseCodes);
                }
            }

            // update <m_addressedDevices> based on the nibbles
            if (m_lastCommandWasFunction[houseCodeNibble])
            {
                // this is the first *address command* in a sequence of one or
                // more *address commands* for this house code -- so reset all
                // device codes for this house code, except this device code,
                // to *currently not addressed* state
                m_addressedDevices[houseCodeNibble] =
                    (ushort) (1 << deviceCodeNibble);
            }
            else
            {
                // this is the second or later *address command* for this house
                // code -- add it to the set of *currently addressed* state
                m_addressedDevices[houseCodeNibble] |=
                    (ushort) (1 << deviceCodeNibble);
            }

            // update other state
            m_lastCommandWasFunction[houseCodeNibble] = false;
            m_lastCommandHouseCodeNibble = houseCodeNibble;
        }

        
Cm11.AddressTracker.RegisterFunctionCommand Method

Updates the internal state of this object to reflect the fact that a function command has excuted and, as a result, all X10 devices with the same house code as the function command will become currently not addressed when the next address command for that house code starts to execute.

Parameters

houseCodeNibble

The house code nibble of the house code of the function command that executed.

        public void RegisterFunctionCommand(byte houseCodeNibble)
        {
            // update state
            Debug.Assert(houseCodeNibble <= 0xF);
            m_lastCommandWasFunction[houseCodeNibble] = true;
            m_lastCommandHouseCodeNibble = houseCodeNibble;
        }
    }

    
Cm11.DnSerialPortStringResources Class

Provides DnSerialPort with its localized strings.

    internal class DnSerialPortStringResources : DnSerialPortStrings
    {
        public override string SerialPortError
        {
            get
            {
                return Resources.DnSerialPort_SerialPortError;
            }
        }
    }
}

Cm11InvalidCommandException Class

Indicates an error in a command provided to the Cm11 class.

public class Cm11InvalidCommandException : Exception
{
    
Cm11InvalidCommandException Constructor

Initializes a new instance of the Cm11InvalidCommandException class. Formats a given error string.

Parameters

format

args

    internal Cm11InvalidCommandException(string format, params object[] args) :
        base(String.Format(format, args))
    {
    }
}

Cm11CommunicationException Class

Indicates an error in communicating with the CM11. Cm11.Close is called automatically when this exception occurs.

public class Cm11CommunicationException : Exception
{
    
Cm11CommunicationException Constructor

Initializes a new instance of the Cm11InvalidException class. Formats a given error string.

Parameters

format

args

    internal Cm11CommunicationException(string format, params object[] args) :
        base(String.Format(format, args))
    {
    }
}

Cm11DeviceNotificationEventDelegate Delegate

The type of an event handler for an event which is fired to indicate that a funtion command that applies to an X10 device was transmitted on the X10 network.

Parameters

address

The address of the device that the notification applies to; for example, "A1" or "P16".

Remarks

See the events such as Cm11.OnReceived and Cm11.OffReceived.

public delegate void Cm11DeviceNotificationEventDelegate(string address);

Cm11BrightenOrDimNotificationEventDelegate Delegate

The type of an event handler for an event which is fired to indicate that a "Brighten" or "Dim" command was transmitted on the X10 network.

Parameters

address

The address of the device that the notification applies to; for example, "A1" or "P16".

percent

The amount that the device was brighted or dimmed by, as a percentage between 0 and 100 inclusive.

Remarks

See the events such as Cm11.BrightenReceived and Cm11.DimReceived.

public delegate void Cm11BrightenOrDimNotificationEventDelegate(string address,
    int percent);

Cm11HouseNotificationEventDelegate Delegate

The type of an event handler for an event which is fired to indicate that a funtion command that applies to all devices of a given house code was transmitted on the X10 network.

Parameters

houseCode

The house code of the device that the notification applies to; for example, "A" or "P".

Remarks

See the events such as Cm11.AllLightsOnReceived and Cm11.AllOffReceived.

public delegate void Cm11HouseNotificationEventDelegate(char houseCode);

Cm11LowLevelNotificationEventDelegate Delegate

The type of an event handler for an event which is fired when the Cm11 class receives a notification of an event on the X10 network from the CM11 hardware.

Parameters

commandName

The command notification sent from the CM11 hardware; for example, "A1" or "Dim" or "AllLightsOn". In the case of "Dim" or "Brighten" commands, commandName does not include a brighten-by or dim-by amount -- that value is in commandParameter.

commandParameter

The brighten-by or dim-by amount (0 to 100) in the case of "Dim" and "Brighten" commands; -1 otherwise.

Remarks

See the Cm11.Notification event.

public delegate void Cm11LowLevelNotificationEventDelegate(string commandName,
    int commandParameter);

Cm11IdleStateChangeEventDelegate Delegate

The type of an event handler for an event which is fired when the idle state of the Cm11 object changes.

Parameters

idle

false if the Cm11 object just started processing commands, true if the Cm11 object just completed processing all commands and is now idle.

Remarks

See the Cm11.IdleStateChange event.

public delegate void Cm11IdleStateChangeEventDelegate(bool idle);

Cm11ErrorEventDelegate Delegate

The type of an event handler for an event which is fired when an error occurs related to communication with, or operation of, the CM11 hardware.

Parameters

message

Information about the error.

Remarks

See the Cm11.Error event.

public delegate void Cm11ErrorEventDelegate(string message);

Cm11LogMessageEventDelegate Delegate

The type of an event handler for an event which is fired to provide the application with information it may want to log for future review by the user.

Parameters

message

A text message to log.

Remarks

See the Cm11.LogMessage event.

public delegate void Cm11LogMessageEventDelegate(string message);

}

ResourceFinder Class

Placeholder classe used to aid in locating certain resources embedded in this DLL.

Remarks

In order for [ToolboxBitmap] attributes to work correctly, a class must be created that has no namespace, since the default namespace of this project (see project properties in Visual Studio) was changed and no longer matches the name of the assembly.

Thanks to http://www.bobpowell.net/toolboxbitmap.htm for pointing out this handy technique.

abstract internal class ResourceFinder // must not be in any namespace
{
}