File: ManchesterDecoder

Details

File: ManchesterDecoder.cs
Dir: http://www.ofitselfso.com/IRSky/IRSky.php
Date: Mon, Dec 15, 2014
using System;
using System.Collections;
using BBBCSIO;

/// +------------------------------------------------------------------------------------------------------------------------------+
/// |                                                   TERMS OF USE: MIT License                                                  |
/// +------------------------------------------------------------------------------------------------------------------------------|
/// |Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation    |
/// |files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,    |
/// |modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software|
/// |is furnished to do so, subject to the following conditions:                                                                   |
/// |                                                                                                                              |
/// |The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.|
/// |                                                                                                                              |
/// |THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE          |
/// |WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR         |
/// |COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,   |
/// |ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                         |
/// +------------------------------------------------------------------------------------------------------------------------------+

namespace IRSkyBBB
{
    /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
    /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
    /// <summary>
    /// A class to provide decoding functionality for Manchester encoded signals
    /// This code is specifically optimized for RC5 and RC6 IR Remote Control protocols
    /// and handles both simultaneously. However, it should be readily adaptable to any 
    /// Manchester encoded signal. 
    /// 
    /// RC5 and RC6 signals are Manchester encoded. This means that each bit (1 or 0)
    /// is represented by the direction of the rising or falling pulse transition.
    /// 
    /// http://en.wikipedia.org/wiki/Manchester_code
    /// 
    /// In general, RC6 signals are based off a 1T pulse width of 444uS and can also 
    /// contain 2T 4T and 6T pulse durations in them. RC5 is based off a 888uS (2T) 
    /// pulse width and will only ever contain 2T and 4T pulse durations. It is the 
    /// fact that RC5 pulses are always a 2T or 4T multiple of the RC6 1T pulse which 
    /// enables the same code to handle both.
    /// 
    /// The code is heavily optimised for speed here. It does not create and destroy 
    /// objects and there is miminal passing of variables. Variables are mostly
    /// Int32s as this is a native size and easily manipulated in an atomic manner 
    /// on 32 bit processors.
    /// 
    /// This code is written in C#, for a Beaglebone Black and uses the BBBCSIO Library
    /// 
    /// </summary>
    /// <history>
    ///   20 Jan 14  Cynic - originally written
    /// </history>
    public class ManchesterDecoder
    {
        // these constants determine the width of the IR pulses we detect. RC5 
        // and RC6 use pulse timings based off of a time T of 444uS which 
        // works out to 4440 .NET MF Ticks
        private const int STD_PULSE_IN_TICKS = 4440;

        // NOTE on the Netduino Plus2 a 10% window appears to be sufficient
        //  to identify most pulses. However on the BBB, which is not a 
        //  realtime board, a 25% variation works better. The kernel 
        //  is always swapping various processes in and out and the timing
        //  gets a bit choppy.
        //    private const int PEMITTED_PULSE_VARIATION = 444; // 10%
        private const int PEMITTED_PULSE_VARIATION = 1111; // 25%

        // permitted ranges of our pulses all derived from the STD_PULSE_IN_TICKS
        private const int MAX_PULSE_1T = STD_PULSE_IN_TICKS + PEMITTED_PULSE_VARIATION;
        private const int MIN_PULSE_1T = STD_PULSE_IN_TICKS - PEMITTED_PULSE_VARIATION;
        private const int MAX_PULSE_2T = 2 * MAX_PULSE_1T;
        private const int MIN_PULSE_2T = 2 * MIN_PULSE_1T;
        private const int MAX_PULSE_4T = 2 * MAX_PULSE_2T;
        private const int MIN_PULSE_4T = 2 * MIN_PULSE_2T;
        private const int MAX_PULSE_6T = MAX_PULSE_4T + MAX_PULSE_2T;
        private const int MIN_PULSE_6T = MIN_PULSE_4T + MIN_PULSE_2T;

        // this is the biggest we ever expect to see, beyond this  
        // means corruption in the signal
        private const int MAX_EXPECTED_PULSE_TIME = MIN_PULSE_6T;

        // The processing methodology is quite simple. We have a routine which is called 
        // on the rising and falling edge of each pulse. This call should be interrupt
        // driven by some external agent and in the call we get information on both the 
        // direction of the pulse (rising or falling) and the time at which it occurred.

        // To process these pulses, we do not store the time or the time difference. What we
        // do is note the length of the time difference from the last pulse we saw and 
        // figure out if it is a 1T, 2T, 4T or 6T pulse taking into account a permitted +/- 
        // variation in the pulse width (controlled by the PEMITTED_PULSE_VARIATION) constant
 
        // If a pulse is of a 1T width and we are on a RISING pulse edge we place a -1 in the
        // current pulse array slot. If we are on a FALLING edge we place a +1 in the slot.
        // Thus the SIGN of the value in the pulseArray indicates whether the pulse was low (-ve)
        // or high +ve during that 1T period.

        // If the pulse is of a 2T width, we place TWO entries in the pulse array and they will both
        // be negative or positive. As additional information, we put a "2" in both slots rather
        // than a "1" as for the 1T pulse. For example, a +2, +2 for a 2T falling edge pulse.

        // Each slot in the pulse array ALWAYS represents a 1T time interval. The fact that
        // are two +2 entries in succession just means that each of those 1T intervals came in
        // as part of a 2T falling pulse. This will greatly assist in interpretation of the 
        // pulse sequence by other routines. For example, they can easily see the leading 6T 
        // pulse of an RC6 signal (it will fill six slots of with values of +6,+6,+6,+6,+6,+6. 
        // Also it will not need to  look at each of the slots. If the first one is +6, the next 5 
        // will also be that value. This greatly speeds up interpretation to the point where if
        // the code which interprets the pulses into 1's and 0's is efficient then the processing
        // can be done inside the interrupt call itself.

        // Just for clarification, I'll say it again. Each slot in the pulseArray ALWAYS represents 
        // a 1T interval. The SIGN indicates whether the signal was low or high during that period
        // and the VALUE indicates the width of the pulse that filled in that 1T slot sequence.
        // So, in other words, a chain of +4,+4,+4,+4,-2,-2 means that there were two pulses
        // recorded - one of 4T where during which the signal was high and one of 2T where it was low.
        
        // the array is OVERFLOWPULSES larger than we ever actually use so that we do not have to 
        // continously check bounds - as mentioned befor we are optimizing for speed
        public const int MAXPULSES = 70;
        public const int OVERFLOWPULSES = 10;
        // each entry represents a 1T pulse - see comments above
        public int[] pulseArray = new int[MAXPULSES + OVERFLOWPULSES];
        // the next slot in the pulse array we can record a pulse in
        public int nextAvailablePulseArraySlot = 0;
        // number of times we reset the pulseArray. Any bad or invalid pulse train makes the 
        // pulse array reset. If you have fluorescent lights you'll get a lot of dodgy ir signal
        private int pulseArrayResetCount = 0;
        // RC5 IR codes only ever have 2T and 4T pulses. If we ever see a 1T or a 6T we know
        // we probably have a RC6 pulse. Now, we do not interpret the pulse train in here
        // but for reasons of speed we do not want to force the object that does the interpretation
        // to scan the pulse arry for this information itself. 
        private bool pulsesAssociatedWithRC6Detected = false;
        // this is the ticks when the last recorded pulse happened. It is the tick value of the current
        // pulse minus the lastPulseTicks value which gives us the pulse duration in ticks
        private long lastPulseTicks = 0;
        // this is the direction of the last pulse. NOTE: by definition of the Manchester encoding
        // protocol if we ever see two pulses of the same type in succession then the pulse train
        // is corrupt and we reset.
        private PulseTypeEnum lastPulseType = PulseTypeEnum.PULSE_UNKNOWN;

        // this is for diagnostics
        private int lastResetReason = 0;

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Constructor
        /// </summary>
        /// <history>
        ///   20 Jan 14  Cynic - originally written
        /// </history>
        public ManchesterDecoder()
        {
            // set up
            ResetForNewInput(0);
        }
 
        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Resets  for new input
        /// </summary>
        /// <param name="resetReason">the reset reason - in case we wish to know this</param>
        /// <history>
        ///   20 Jan 14  Cynic - originally written
        /// </history>
        public void ResetForNewInput(int resetReason)
        {            
            /* we do not need to reset the pulseArray
            for (int i = 0; i < MAXPULSES; i++)
            {
                pulseArray[i] = 0;
            }
             */
            // reset some vars
            nextAvailablePulseArraySlot = 0;
            lastPulseType = PulseTypeEnum.PULSE_UNKNOWN;
            pulseArrayResetCount++;
            lastResetReason = resetReason;
            pulsesAssociatedWithRC6Detected = false;
         }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Returns the number of stored pulses. There is no set accessor. The pulse count
        /// is set and reset by the incoming stream
        /// </summary>
        /// <history>
        ///   20 Jan 14  Cynic - originally written
        /// </history>
        public int PulsesReceived
        {
            get
            {
                // this is just the value of the nextAvailablePulseArraySlot
                return nextAvailablePulseArraySlot;
            }
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Returns the number of times we have reset the pulse array. Is indicative of
        /// the number of IR Codes received plus the number of corrupted ones we could
        /// not process. Essentially gets incremented everytime we give up and start
        /// recording a new stream. There is no set accessor. 
        /// </summary>
        /// <history>
        ///   20 Jan 14  Cynic - originally written
        /// </history>
        public int PulseArrayResetCount
        {
            get
            {
                // this how many times we have reset
                return pulseArrayResetCount;
            }
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Returns a marker code indicating the reason for the last reset
        /// </summary>
        /// <history>
        ///   20 Jan 14  Cynic - originally written
        /// </history>
        public int LastResetReason
        {
            get
            {
                // this the marker indicating the reason for the last reset
                return lastResetReason;
            }
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Returns a flag indicating if the current pulse stream has any pulses 
        /// usually associated with RC6 codes (essentially 1T and 6T widths). This
        /// is useful for differentiating RC5 and RC6 IR codes
        /// </summary>
        /// <history>
        ///   20 Jan 14  Cynic - originally written
        /// </history>
        public bool RC6Detected
        {
            get
            {
                return pulsesAssociatedWithRC6Detected;
            }
        }

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Records a pulse event. This common code handles both a falling and rising event
        /// </summary>
        /// <param name="pulseType">the type of pulse (falling, rising)</param>
        /// <param name="timeOfEvent">the time of the event</param>
        /// <history>
        ///   20 Jan 14  Cynic - originally written
        /// </history>
        public void RecordPulseEvent(PulseTypeEnum pulseType, DateTime timeOfEvent)
        {
            int pulseDirection;

            try
            {
                // rising pulse means was-low-going-high 
                if (pulseType == PulseTypeEnum.PULSE_RISING)
                {
                    // as a convention we mark them as negative
                    pulseDirection = -1;
                    // but was the last pulse we saw also in this state?
                    if (lastPulseType == PulseTypeEnum.PULSE_RISING)
                    {
                        // yes, it was, this is not possible, we have a corrupted stream
                        ResetForNewInput(310);
                        return;
                    }
                }
                else if (pulseType == PulseTypeEnum.PULSE_FALLING)
                {
                    // but was the last pulse we saw also in this state?
                    if (lastPulseType == PulseTypeEnum.PULSE_FALLING)
                    {
                        // yes, it was, this is not possible, we have a corrupted stream
                        ResetForNewInput(320);
                        return;
                    }
                    // as a convention we mark these as positive
                    pulseDirection = 1;
                }
                else
                {
                    // pulse type unknown - should never happen
                    ResetForNewInput(300);
                    return;
                }

                // calculate the time difference, we compare this a lot
                // so we keep it as an int for speed purposes
                int tickDifference = (int)(timeOfEvent.Ticks - lastPulseTicks);

                // find out where our tick difference is placed in the sequence
                // note how successive comparisons either reset or take action on
                // the pulse without making any comparision twice
                if (tickDifference < MIN_PULSE_1T)
                {
                    //    Console.WriteLine("tickDifference < MIN_PULSE_1T "+tickDifference.ToString() + "," + MIN_PULSE_1T.ToString());
                    // invalid, so reset
                    ResetForNewInput(311);
                    // and leave
                    return;
                }
                else if (tickDifference < MAX_PULSE_1T)
                {
                    // valid, record it
                    pulseArray[nextAvailablePulseArraySlot] = 1 * pulseDirection;
                    // set this now
                    nextAvailablePulseArraySlot += 1;
                    // note this
                    pulsesAssociatedWithRC6Detected = true;
                    // and leave
                    return;
                }
                else if (tickDifference < MIN_PULSE_2T)
                {
                    //Console.WriteLine("tickDifference < MIN_PULSE_2T "+tickDifference.ToString() + "," + MIN_PULSE_2T.ToString());
                    // invalid, so reset
                    ResetForNewInput(322);
                    // and leave
                    return;
                }
                else if (tickDifference < MAX_PULSE_2T)
                {
                    // valid, record it
                    pulseArray[nextAvailablePulseArraySlot] = 2 * pulseDirection;
                    pulseArray[nextAvailablePulseArraySlot + 1] = 2 * pulseDirection;
                    // set this now
                    nextAvailablePulseArraySlot += 2;
                    // and leave
                    return;
                }
                else if (tickDifference < MIN_PULSE_4T)
                {
                    //Console.WriteLine("tickDifference < MIN_PULSE_4T "+tickDifference.ToString() + "," + MIN_PULSE_4T.ToString());
                    // invalid, so reset
                    ResetForNewInput(344);
                    // and leave
                    return;
                }
                else if (tickDifference < MAX_PULSE_4T)
                {
                    // valid, record it
                    pulseArray[nextAvailablePulseArraySlot] = 4 * pulseDirection;
                    pulseArray[nextAvailablePulseArraySlot + 1] = 4 * pulseDirection;
                    pulseArray[nextAvailablePulseArraySlot + 2] = 4 * pulseDirection;
                    pulseArray[nextAvailablePulseArraySlot + 3] = 4 * pulseDirection;
                    // set this now
                    nextAvailablePulseArraySlot += 4;
                    // and leave
                    return;
                }
                else if (tickDifference < MIN_PULSE_6T)
                {
                    // invalid, so reset
                    ResetForNewInput(366);
                    // and leave
                    return;
                }
                else if (tickDifference < MAX_PULSE_6T)
                {
                    // valid, record it
                    pulseArray[nextAvailablePulseArraySlot] = 6 * pulseDirection;
                    pulseArray[nextAvailablePulseArraySlot + 1] = 6 * pulseDirection;
                    pulseArray[nextAvailablePulseArraySlot + 2] = 6 * pulseDirection;
                    pulseArray[nextAvailablePulseArraySlot + 3] = 6 * pulseDirection;
                    pulseArray[nextAvailablePulseArraySlot + 4] = 6 * pulseDirection;
                    pulseArray[nextAvailablePulseArraySlot + 5] = 6 * pulseDirection;
                    // set this now
                    nextAvailablePulseArraySlot += 6;
                    // note this
                    pulsesAssociatedWithRC6Detected = true;
                    // and leave
                    return;
                }
                else
                {
                    //Console.WriteLine("tickDifference not acceptable "+tickDifference.ToString());
                    // time interval, not acceptable, something wrong, we must reset
                    ResetForNewInput(303);
                    // and leave
                    return;
                }

            }
            finally
            {
                // record this
                lastPulseTicks = timeOfEvent.Ticks;
            }
         }
    }
}
HTML Code Generated by CoDocker v00.90 Beta