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;
}
}
}
}