protected TrackerBase(IntPtr handle, IntPtr start, int step, int countOffset)
        {
            this.step        = step;
            this.countOffset = countOffset;

            Handle    = handle;
            Start     = start;
            OpenSlots = new LinkedList <int>();
            tracker   = new Dictionary <ItemId, List <IntPtr> >();
        }
Esempio n. 2
0
        /// <summary>
        /// Executes a command, or simply checks its syntax and usage.
        /// </summary>
        ///
        /// <param name="command">The <r>DwellNet.Cm11</r> 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
        /// 	<r>DwellNet.Cm11</r> commands.</param>
        ///
        /// <param name="addressTracker">Tracks which X10 devices are <i>currently
        /// 	addressed</i>.</param>
        ///
        /// <param name="execute"><n>true</n> to execute the command, <n>false</n>
        /// 	to check its syntax and usage.  If <paramref name="execute"/> is
        /// 	<n>true</n>, this method should only be called from the worker
        /// 	thread.</param>
        ///
        /// <returns>
        /// <n>true</n> if the command was executed, <n>false</n> if not.
        /// <n>false</n> is returned if <paramref name="execute"/> is <n>false</n>.
        /// </returns>
        ///
        /// <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.
        /// <r>ProcessCommand</r> checks for this error condition by using
        /// <paramef name="addressTracker"/>, which the caller should ensure is
        /// created in single-house mode.  If a house code mismatch occurs, a
        /// <r>Cm11InvalidCommandException</r> is thrown.
        /// </remarks>
        ///
        /// <exception cref="Cm11InvalidCommandException">
        /// Thrown when the command is incorrect (e.g. incorrect syntax,
        /// inconsistent house codes, etc.) See Remarks for more information.
        /// </exception>
        ///
        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(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(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(CommandType,
                FormatFunctionCommand(houseCodeNibble.Value, x10Function,
              amount22));

            // update the state of <addressTracker>
            addressTracker.RegisterFunctionCommand(houseCodeNibble.Value);
              }
              else
              {
            // <command> is invalid
            throw new Cm11InvalidCommandException(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(CommandRetryCountExceeded, maxRetries);
              throw new HardResyncException();
            }

            // if this is a retry, tell the application
            if (retry > 0)
              FireLogMessage(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(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(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(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 }, AckChecksum);

              // wait for a 0x55 or 0x5A reply from the CM11
              byte ackReply = PeekSerialPortByte(SERIAL_TIMEOUT);
              FireLogMessage(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(PushedByteBack, ackReply);
              return true;
            }
            else
            {
              // reply incorrect -- reset communications; we "peeked" <ackReply>
              // above above, so read it now
              ReadSerialPortByte(SERIAL_TIMEOUT);
              FireLogMessage(IncorrectChecksumAckReply);
              throw new HardResyncException();
            }
        }
Esempio n. 3
0
        /// <summary>
        /// Sends one or more <r>DwellNet.Cm11</r> commands to the CM11 device.
        /// Commands are translated to the CM11 binary protocol.
        /// </summary>
        ///
        /// <param name="commands">The series of <r>DwellNet.Cm11</r> commands to
        /// 	execute, separated by spaces.  See Cm11Help.htm for a list of valid
        /// 	<r>DwellNet.Cm11</r> commands.</param>
        ///
        /// <remarks>
        /// <para>
        /// 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 <r>WaitUntilIdle</r> after calling <r>Execute</r> if you'd
        /// like to wait until all queued commands have been executed.
        /// </para>
        /// <para>
        /// Commands are separated by spaces; each command must not contain
        /// spaces within it.  For example, if <paramref name="commands"/> equals
        /// "A1 A2 Dim70", that specifies three commands, "A1", "A2", and "Dim70".
        /// Commands are case-sensitive; you can't specify "a1" or "dim70".
        /// </para>
        /// <para>
        /// <r>Open</r> must be called before calling this method.
        /// </para>
        /// </remarks>
        ///
        /// <example>
        /// The following code turns on lamps A1, A2, and A3, then dims them by
        /// 25%, then turns off lamp B1.
        /// <code>
        /// cm11.Execute("A1 A2 A3 On Dim25 B1 Off");
        /// </code>
        /// </example>
        ///
        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(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();
        }