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> >(); }
/// <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(); } }
/// <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(); }