Mirror with some correction
Dependencies: mbed FastIO FastPWM USBDevice
IRRemote/IRProtocols.h@116:7a67265d7c19, 2021-10-01 (annotated)
- Committer:
- arnoz
- Date:
- Fri Oct 01 08:19:46 2021 +0000
- Revision:
- 116:7a67265d7c19
- Parent:
- 97:fc7727303038
- Correct information regarding your last merge
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
mjr | 77:0b96f6867312 | 1 | // IR Remote Protocols |
mjr | 77:0b96f6867312 | 2 | // |
mjr | 77:0b96f6867312 | 3 | // This file implements a handlers for specific IR protocols. A protocol |
mjr | 77:0b96f6867312 | 4 | // is the set of rules that a particular IR remote uses to convert binary |
mjr | 77:0b96f6867312 | 5 | // data to and from a series of timed infrared pulses. A protocol generally |
mjr | 77:0b96f6867312 | 6 | // encompasses a bit-level encoding scheme (how each data bit is represented |
mjr | 77:0b96f6867312 | 7 | // as one or more pulses of IR light) and a message or packet format (how a |
mjr | 77:0b96f6867312 | 8 | // series of bits is assembled to form a higher level datatype, which in the |
mjr | 77:0b96f6867312 | 9 | // case of an IR remote usually amounts to a representation of a key press |
mjr | 77:0b96f6867312 | 10 | // on a remote control). |
mjr | 77:0b96f6867312 | 11 | // |
mjr | 77:0b96f6867312 | 12 | // Lots of companies make CE products with remotes, and many of them use |
mjr | 77:0b96f6867312 | 13 | // their own custom protocols. There's some commonality; a few proprietary |
mjr | 77:0b96f6867312 | 14 | // protocols, such as those from NEC and Philips, are quasi industry |
mjr | 77:0b96f6867312 | 15 | // standards that multiple companies adopted, more or less intact. On |
mjr | 77:0b96f6867312 | 16 | // the other hand, other companies not only have their own systems but |
mjr | 77:0b96f6867312 | 17 | // use multiple proprietary systems across different products. So it'll |
mjr | 77:0b96f6867312 | 18 | // never be possible for us to recognize every remote code out there. |
mjr | 77:0b96f6867312 | 19 | // So we'll cover the widely used ones, and then add the rarer ones as |
mjr | 77:0b96f6867312 | 20 | // needed. |
mjr | 77:0b96f6867312 | 21 | // |
mjr | 77:0b96f6867312 | 22 | // For each protocol we recognize, we define a subclass of IRReceiver |
mjr | 77:0b96f6867312 | 23 | // that implements the code to encode and decode signals for the protocol. |
mjr | 77:0b96f6867312 | 24 | // To send a command, we call the sender function for the protocol we want |
mjr | 77:0b96f6867312 | 25 | // to use for the transmission. When we receive a signal, we run it through |
mjr | 77:0b96f6867312 | 26 | // each protocol class's decode routine, to determine which protocol the |
mjr | 77:0b96f6867312 | 27 | // signal was encoded with and what the signal means. The decoders can |
mjr | 77:0b96f6867312 | 28 | // usually tell if a signal uses their protocol, since there's enough |
mjr | 77:0b96f6867312 | 29 | // structure in most of the protocols that you can distinguish signals that |
mjr | 77:0b96f6867312 | 30 | // use the protocol from those that don't. This allows the decoders to |
mjr | 77:0b96f6867312 | 31 | // serve the dual purposes of decoding signals and also classifying them |
mjr | 77:0b96f6867312 | 32 | // by protocol. |
mjr | 77:0b96f6867312 | 33 | // |
mjr | 77:0b96f6867312 | 34 | // To add support for a new protocol, we (a) define a class for it here, |
mjr | 77:0b96f6867312 | 35 | // and (b) add an entry for the class to IRProtocolList.h. The list entry |
mjr | 77:0b96f6867312 | 36 | // automatically adds the new protocol to the tables we use to look up the |
mjr | 77:0b96f6867312 | 37 | // desired protocol class when sending, and to check each supported protocol |
mjr | 77:0b96f6867312 | 38 | // when receiving. |
mjr | 77:0b96f6867312 | 39 | // |
mjr | 77:0b96f6867312 | 40 | // The protocol decoders operate in parallel: as a transmission is received, |
mjr | 77:0b96f6867312 | 41 | // we run the signal through all of the decoders at the same time. This |
mjr | 77:0b96f6867312 | 42 | // allows each decoder to keep track of the incoming pulse stream and |
mjr | 77:0b96f6867312 | 43 | // recognize messages that conform to its protocol. The parallel operation |
mjr | 77:0b96f6867312 | 44 | // means that each protocol object needs its own complete, independent |
mjr | 77:0b96f6867312 | 45 | // receiver state. |
mjr | 77:0b96f6867312 | 46 | // |
mjr | 77:0b96f6867312 | 47 | // In contrast, there can only be one transmission in progress at a time, |
mjr | 77:0b96f6867312 | 48 | // since a transmission obviously requires exclusive access to the IR LED. |
mjr | 77:0b96f6867312 | 49 | // (You can't eve interleave two transmissions in theory, since the coding |
mjr | 77:0b96f6867312 | 50 | // is all about pulse timing and order.) That means that we *don't* need |
mjr | 77:0b96f6867312 | 51 | // separate independent transmitter state per object. That lets us save |
mjr | 77:0b96f6867312 | 52 | // a little memory by using a single, shared transmitter state object, |
mjr | 77:0b96f6867312 | 53 | // managed by the global transmitter class and passed to the current |
mjr | 77:0b96f6867312 | 54 | // transmitter on each send. The exception would be any state that has |
mjr | 77:0b96f6867312 | 55 | // to be maintained across, which would have to be tracked per protocol. |
mjr | 77:0b96f6867312 | 56 | // The only common example is "toggle bits". |
mjr | 77:0b96f6867312 | 57 | // |
mjr | 77:0b96f6867312 | 58 | |
mjr | 77:0b96f6867312 | 59 | #ifndef _IRPROTOCOLS_H_ |
mjr | 77:0b96f6867312 | 60 | #define _IRPROTOCOLS_H_ |
mjr | 77:0b96f6867312 | 61 | |
mjr | 77:0b96f6867312 | 62 | #include <ctype.h> |
mjr | 77:0b96f6867312 | 63 | #include "NewPwm.h" |
mjr | 77:0b96f6867312 | 64 | #include "IRRemote.h" |
mjr | 77:0b96f6867312 | 65 | #include "IRReceiver.h" |
mjr | 77:0b96f6867312 | 66 | #include "IRProtocolID.h" |
mjr | 77:0b96f6867312 | 67 | #include "IRCommand.h" |
mjr | 77:0b96f6867312 | 68 | |
mjr | 77:0b96f6867312 | 69 | using namespace IRRemote; |
mjr | 77:0b96f6867312 | 70 | |
mjr | 77:0b96f6867312 | 71 | struct DebugItem { char c; int t; DebugItem(char c, int t) : c(c),t(t) {} |
mjr | 77:0b96f6867312 | 72 | DebugItem(char c) : c(c), t(0) { } DebugItem() : c(0), t(0) { } |
mjr | 77:0b96f6867312 | 73 | }; |
mjr | 77:0b96f6867312 | 74 | extern CircBuf<DebugItem,256> debug; |
mjr | 77:0b96f6867312 | 75 | |
mjr | 77:0b96f6867312 | 76 | |
mjr | 77:0b96f6867312 | 77 | // IR transmitter state object. |
mjr | 77:0b96f6867312 | 78 | // |
mjr | 77:0b96f6867312 | 79 | // We can only transmit one signal at a time, so we only need to keep |
mjr | 77:0b96f6867312 | 80 | // track of the state of one transmission at a time. This lets us |
mjr | 77:0b96f6867312 | 81 | // save a little memory by using a single combined state object that's |
mjr | 77:0b96f6867312 | 82 | // shared by all transmitters. The transmitter currently operating |
mjr | 77:0b96f6867312 | 83 | // uses the shared object for as long as the transmission takes. |
mjr | 77:0b96f6867312 | 84 | struct IRTXState |
mjr | 77:0b96f6867312 | 85 | { |
mjr | 77:0b96f6867312 | 86 | // Time since the transmission started |
mjr | 77:0b96f6867312 | 87 | Timer txTime; |
mjr | 77:0b96f6867312 | 88 | |
mjr | 77:0b96f6867312 | 89 | // The command code we're transmitting |
mjr | 77:0b96f6867312 | 90 | uint64_t cmdCode; |
mjr | 77:0b96f6867312 | 91 | |
mjr | 77:0b96f6867312 | 92 | // Bit stream to transmit. Many of the protocols use a universal |
mjr | 77:0b96f6867312 | 93 | // command representation (in IRCommand.code) that rearranges the |
mjr | 77:0b96f6867312 | 94 | // bits from the transmission order. This is a scratch pad where the |
mjr | 77:0b96f6867312 | 95 | // protocol handler can translate a command code back into transmission |
mjr | 77:0b96f6867312 | 96 | // order once at the start of the transmission, then just shift bits out |
mjr | 77:0b96f6867312 | 97 | // of here to transmit them in sequence. |
mjr | 77:0b96f6867312 | 98 | uint64_t bitstream; |
mjr | 77:0b96f6867312 | 99 | |
mjr | 77:0b96f6867312 | 100 | // The IR LED control pin |
mjr | 77:0b96f6867312 | 101 | NewPwmOut *pin; |
mjr | 77:0b96f6867312 | 102 | |
mjr | 77:0b96f6867312 | 103 | // Protocol ID |
mjr | 77:0b96f6867312 | 104 | uint8_t protocolId; |
mjr | 77:0b96f6867312 | 105 | |
mjr | 77:0b96f6867312 | 106 | // Transmission step. The meaning is up to the individual protocols, |
mjr | 77:0b96f6867312 | 107 | // but generally this is used to keep track of the current structural |
mjr | 77:0b96f6867312 | 108 | // part of the pulse stream we're generating. E.g., a protocol might |
mjr | 77:0b96f6867312 | 109 | // use 0 for the header mark, 1 for the header space, etc. |
mjr | 77:0b96f6867312 | 110 | uint8_t step; |
mjr | 77:0b96f6867312 | 111 | |
mjr | 77:0b96f6867312 | 112 | // number of bits to transmit |
mjr | 77:0b96f6867312 | 113 | uint8_t nbits; |
mjr | 77:0b96f6867312 | 114 | |
mjr | 77:0b96f6867312 | 115 | // Current bit position within the data |
mjr | 77:0b96f6867312 | 116 | uint8_t bit; |
mjr | 77:0b96f6867312 | 117 | |
mjr | 77:0b96f6867312 | 118 | // Substep within the current bit. Many of the protocols represent each |
mjr | 77:0b96f6867312 | 119 | // bit as multiple IR symbols, such as mark+space. This keeps track of |
mjr | 77:0b96f6867312 | 120 | // the step within the current bit. |
mjr | 77:0b96f6867312 | 121 | uint8_t bitstep; |
mjr | 77:0b96f6867312 | 122 | |
mjr | 77:0b96f6867312 | 123 | // Repeat phase. Some protocols have rules about minimum repeat counts, |
mjr | 77:0b96f6867312 | 124 | // or use different coding for auto-repeats. This lets the sender keep |
mjr | 77:0b96f6867312 | 125 | // track of the current step. For example, the Sony protocol requires |
mjr | 77:0b96f6867312 | 126 | // each message to be sent a minimum of 3 times. The NEC protocol uses |
mjr | 77:0b96f6867312 | 127 | // a "ditto" code for each repeat of a code after the initial command. |
mjr | 77:0b96f6867312 | 128 | // The OrtekMCE protocol requires a minimum of 2 sends per code, and has |
mjr | 77:0b96f6867312 | 129 | // a position counter within the code that indicates which copy we're on. |
mjr | 77:0b96f6867312 | 130 | uint8_t rep; |
mjr | 77:0b96f6867312 | 131 | |
mjr | 77:0b96f6867312 | 132 | // Is the virtual button that initiated this transmission still pressed? |
mjr | 77:0b96f6867312 | 133 | // The global transmitter sets this before each call to txStep() to let |
mjr | 77:0b96f6867312 | 134 | // the protocol know if it should auto-repeat at the end of the code. |
mjr | 77:0b96f6867312 | 135 | uint8_t pressed : 1; |
mjr | 77:0b96f6867312 | 136 | |
mjr | 77:0b96f6867312 | 137 | // Use "ditto" codes when sending repeats? Some protocols use special |
mjr | 77:0b96f6867312 | 138 | // coding for auto-repeats, so that receivers can tell whether a key |
mjr | 77:0b96f6867312 | 139 | // is being held down or pressed repeatedly. But in some cases, the |
mjr | 77:0b96f6867312 | 140 | // same protocol may be used with dittos on some devices but without |
mjr | 77:0b96f6867312 | 141 | // dittos on other devices. It's therefore not always enough to know |
mjr | 77:0b96f6867312 | 142 | // that the protocol supports dittos; we have to know separately whether |
mjr | 77:0b96f6867312 | 143 | // the device we're sending to wants them. This flag lets the caller |
mjr | 77:0b96f6867312 | 144 | // tell us which format to use. This is ignored if the protocol either |
mjr | 77:0b96f6867312 | 145 | // never uses dittos or always does: in that case we'll do whatever the |
mjr | 77:0b96f6867312 | 146 | // protocol specifies. To implement a "learning remote", you should |
mjr | 77:0b96f6867312 | 147 | // make sure that the user holds down each key long enough for several |
mjr | 77:0b96f6867312 | 148 | // repeats when learning codes, so that the learning remote can determine |
mjr | 77:0b96f6867312 | 149 | // when dittos are used by observing how the repeats are sent from the |
mjr | 77:0b96f6867312 | 150 | // reference remote. Then you can set this bit if you saw any ditto |
mjr | 77:0b96f6867312 | 151 | // codes during training for a given key. |
mjr | 77:0b96f6867312 | 152 | uint8_t dittos : 1; |
mjr | 77:0b96f6867312 | 153 | |
mjr | 77:0b96f6867312 | 154 | // TX toggle bit. We provide this for protocols that need it. Note |
mjr | 77:0b96f6867312 | 155 | // that this is a global toggle bit, so if we switch from transmitting |
mjr | 77:0b96f6867312 | 156 | // one protocol to another and then return to the first, we'll lose |
mjr | 77:0b96f6867312 | 157 | // continuity with the toggle sequence in the original protocol. But |
mjr | 77:0b96f6867312 | 158 | // that shouldn't be a problem: the protocols with toggles only use it |
mjr | 77:0b96f6867312 | 159 | // to distinguish two rapid presses of the same key in succession from |
mjr | 77:0b96f6867312 | 160 | // auto-repeat while the key is held down. If we had an intervening |
mjr | 77:0b96f6867312 | 161 | // transmission to another protocol, the original receiver will see a |
mjr | 77:0b96f6867312 | 162 | // long gap between the earlier and later messages; the toggle bit isn't |
mjr | 77:0b96f6867312 | 163 | // necessary in this case to tell that these were two key presses. |
mjr | 77:0b96f6867312 | 164 | uint8_t toggle : 1; |
mjr | 77:0b96f6867312 | 165 | }; |
mjr | 77:0b96f6867312 | 166 | |
mjr | 77:0b96f6867312 | 167 | |
mjr | 77:0b96f6867312 | 168 | // Base class for all protocols |
mjr | 77:0b96f6867312 | 169 | // |
mjr | 77:0b96f6867312 | 170 | // Note that most of the data parameters are set through virtuals rather |
mjr | 77:0b96f6867312 | 171 | // than member variables. This helps minimize the RAM footprint. |
mjr | 77:0b96f6867312 | 172 | // |
mjr | 77:0b96f6867312 | 173 | class IRProtocol |
mjr | 77:0b96f6867312 | 174 | { |
mjr | 77:0b96f6867312 | 175 | public: |
mjr | 77:0b96f6867312 | 176 | IRProtocol() { } |
mjr | 77:0b96f6867312 | 177 | virtual ~IRProtocol() { } |
mjr | 77:0b96f6867312 | 178 | |
mjr | 77:0b96f6867312 | 179 | // look up a protocol by ID |
mjr | 77:0b96f6867312 | 180 | static IRProtocol *senderForId(int id); |
mjr | 77:0b96f6867312 | 181 | |
mjr | 77:0b96f6867312 | 182 | // name and ID |
mjr | 77:0b96f6867312 | 183 | virtual const char *name() const = 0; |
mjr | 77:0b96f6867312 | 184 | virtual int id() const = 0; |
mjr | 77:0b96f6867312 | 185 | |
mjr | 77:0b96f6867312 | 186 | // Are we a transmitter for the given protocol? Some protocol |
mjr | 77:0b96f6867312 | 187 | // handlers send and receive variations on a protocol that have |
mjr | 77:0b96f6867312 | 188 | // distinct protocol IDs, such as the various Sony codes at |
mjr | 77:0b96f6867312 | 189 | // different bit sizes. By default, we assume we handle only |
mjr | 77:0b96f6867312 | 190 | // our nominal protocol as returned by id(). |
mjr | 77:0b96f6867312 | 191 | virtual bool isSenderFor(int protocolId) const { return protocolId == id(); } |
mjr | 77:0b96f6867312 | 192 | |
mjr | 77:0b96f6867312 | 193 | // parse a pulse on receive |
mjr | 77:0b96f6867312 | 194 | virtual void rxPulse(IRRecvProIfc *receiver, uint32_t t, bool mark) = 0; |
mjr | 77:0b96f6867312 | 195 | |
mjr | 77:0b96f6867312 | 196 | // PWM carrier frequency used for the IR signal, expressed as a PWM |
mjr | 97:fc7727303038 | 197 | // cycle period in seconds. We use this to set the appropriate PWM |
mjr | 77:0b96f6867312 | 198 | // frequency for transmissions. The most common carrier is 38kHz. |
mjr | 77:0b96f6867312 | 199 | // |
mjr | 77:0b96f6867312 | 200 | // We can't use adjust the carrier frequency for receiving signals, since |
mjr | 77:0b96f6867312 | 201 | // the TSOP sensor we use does the demodulation at a fixed frequency. |
mjr | 77:0b96f6867312 | 202 | // You can choose a frequency by choosing your sensor, since TSOP sensors |
mjr | 77:0b96f6867312 | 203 | // are available in a range of carrier frequencies, but once you choose a |
mjr | 97:fc7727303038 | 204 | // sensor we can't change its frequency in software. Fortunately, the |
mjr | 77:0b96f6867312 | 205 | // TSOP384xx seems tolerant in practice of a fairly wide range around |
mjr | 97:fc7727303038 | 206 | // its nominal 38kHz carrier. I've successfully tested it with remotes |
mjr | 97:fc7727303038 | 207 | // documented to use frequencies from 36 to 40 kHz. Most CE remotes |
mjr | 97:fc7727303038 | 208 | // fall within this range, so the 38kHz sensor makes a good universal |
mjr | 97:fc7727303038 | 209 | // receiver. |
mjr | 77:0b96f6867312 | 210 | virtual float pwmPeriod(IRTXState *state) const { return 26.31578947e-6f; } // 38kHz |
mjr | 77:0b96f6867312 | 211 | |
mjr | 97:fc7727303038 | 212 | // PWM duty cycle when transmitting. This is the proportion of On to |
mjr | 97:fc7727303038 | 213 | // Off time for each PWM pulse. A few of the IR protocols that have |
mjr | 97:fc7727303038 | 214 | // official documentation do specify a duty cycle, so when this is laid |
mjr | 97:fc7727303038 | 215 | // out in the spec, it's probably a good idea to use the same value in |
mjr | 97:fc7727303038 | 216 | // our protocol implementation. In practice, though, it doesn't seem |
mjr | 97:fc7727303038 | 217 | // to be an important parameter as far as the receivers are concerned, |
mjr | 97:fc7727303038 | 218 | // and I'm not sure it actually matters for any of the protocols. To |
mjr | 97:fc7727303038 | 219 | // the extent it's specified at all, they might just be documenting the |
mjr | 97:fc7727303038 | 220 | // original manufacturer's implementation rather than specifying a |
mjr | 97:fc7727303038 | 221 | // requirement. |
mjr | 77:0b96f6867312 | 222 | virtual float pwmDutyCycle() const { return .3f; } |
mjr | 77:0b96f6867312 | 223 | |
mjr | 77:0b96f6867312 | 224 | // Begin transmitting the given command. Before calling, the caller |
mjr | 77:0b96f6867312 | 225 | // turns off the IR LED and sets its PWM period to the one given by |
mjr | 77:0b96f6867312 | 226 | // our pwmPeriod() method. The caller also clears 'state', and then |
mjr | 77:0b96f6867312 | 227 | // sets the 'cmd', 'pin', and 'pressed' fields. The rest of the struct |
mjr | 77:0b96f6867312 | 228 | // is for the protocol handler's private use during the transmission. |
mjr | 77:0b96f6867312 | 229 | // handler is free to store its interim state here to pass information |
mjr | 77:0b96f6867312 | 230 | // from txStart() to txStep() and from one txStep() to the next. |
mjr | 77:0b96f6867312 | 231 | // |
mjr | 77:0b96f6867312 | 232 | // Subclass implementations should start by setting up 'state' with any |
mjr | 77:0b96f6867312 | 233 | // data they need during the transmission. E.g., convert the code word |
mjr | 77:0b96f6867312 | 234 | // value into a series of bits to transmit, and store this in the |
mjr | 77:0b96f6867312 | 235 | // 'bitstream' field. Once that's set up, determine how long the initial |
mjr | 77:0b96f6867312 | 236 | // gap should be (the IR off time between transmissions in the protocol), |
mjr | 77:0b96f6867312 | 237 | // and return this time in microseconds. The caller will return to |
mjr | 77:0b96f6867312 | 238 | // other work while the gap time is elapsing, then it will call txStep() |
mjr | 77:0b96f6867312 | 239 | // to advance to the next step of the transmission. |
mjr | 77:0b96f6867312 | 240 | // |
mjr | 77:0b96f6867312 | 241 | // DON'T do a timer pause or spin wait here. This routine is called in |
mjr | 77:0b96f6867312 | 242 | // interrupt context, so it must return as quickly as possible. All |
mjr | 77:0b96f6867312 | 243 | // waits should be done simply by returning the desired wait time. |
mjr | 77:0b96f6867312 | 244 | // |
mjr | 77:0b96f6867312 | 245 | // By convention, implementations should start each transmission with a |
mjr | 77:0b96f6867312 | 246 | // sufficient gap time (IR off) to allow senders to recognize the new |
mjr | 77:0b96f6867312 | 247 | // transmission. It's best to put the gap time at the *start* of each |
mjr | 77:0b96f6867312 | 248 | // new transmission rather than at the end because two consecutive |
mjr | 77:0b96f6867312 | 249 | // transmissions might use different protocols with different timing |
mjr | 77:0b96f6867312 | 250 | // requirements. If the first protocol has a short gap time and the |
mjr | 77:0b96f6867312 | 251 | // second has a long gap time, putting the gap at the end of the first |
mjr | 77:0b96f6867312 | 252 | // transmission would only use the shorter time, which might not be |
mjr | 77:0b96f6867312 | 253 | // sufficient for the second protocol's receiver. |
mjr | 77:0b96f6867312 | 254 | virtual int txStart(IRTXState *state) = 0; |
mjr | 77:0b96f6867312 | 255 | |
mjr | 77:0b96f6867312 | 256 | // Continue a transmission with the next pulse step. This carries |
mjr | 77:0b96f6867312 | 257 | // out the next step of the transmission, then returns a time value |
mjr | 77:0b96f6867312 | 258 | // with the number of microseconds until the next event. For example, |
mjr | 77:0b96f6867312 | 259 | // if the current step in the transmission is a 600us mark, turn on the |
mjr | 77:0b96f6867312 | 260 | // IR transmitter, update 'state' to indicate that we're in the mark, |
mjr | 77:0b96f6867312 | 261 | // and return 600 to tell the caller to go do other work while the |
mjr | 77:0b96f6867312 | 262 | // mark is being sent. Don't wait or spin here, since this is called |
mjr | 77:0b96f6867312 | 263 | // in interrupt context and should thus return as quickly as possible. |
mjr | 77:0b96f6867312 | 264 | // |
mjr | 77:0b96f6867312 | 265 | // Before calling this, the caller will update the 'pressed' field in |
mjr | 77:0b96f6867312 | 266 | // 'state' to let us know if the virtual button is still being pressed. |
mjr | 77:0b96f6867312 | 267 | // The protocol handler can auto-repeat if the button is still pressed |
mjr | 77:0b96f6867312 | 268 | // at the end of the current transmission, if that's appropriate for |
mjr | 77:0b96f6867312 | 269 | // the protocol. We let the handlers choose how to handle auto-repeat |
mjr | 77:0b96f6867312 | 270 | // rather than trying to manage it globally, since there's a lot of |
mjr | 77:0b96f6867312 | 271 | // variation in how it needs to be handled. Some protocols, for example, |
mjr | 77:0b96f6867312 | 272 | // use "ditto" codes (distinct codes that mean "same as last time" |
mjr | 77:0b96f6867312 | 273 | // rather than actually re-transmitting a full command), while others |
mjr | 77:0b96f6867312 | 274 | // have internal position markers to indicate a series of repeats |
mjr | 77:0b96f6867312 | 275 | // for one key press. |
mjr | 77:0b96f6867312 | 276 | virtual int txStep(IRTXState *state) = 0; |
mjr | 77:0b96f6867312 | 277 | |
mjr | 77:0b96f6867312 | 278 | // protocol singletons |
mjr | 77:0b96f6867312 | 279 | static class IRProtocols *protocols; |
mjr | 77:0b96f6867312 | 280 | |
mjr | 77:0b96f6867312 | 281 | // allocate the protocol singletons, if we haven't already |
mjr | 77:0b96f6867312 | 282 | static void allocProtocols(); |
mjr | 77:0b96f6867312 | 283 | |
mjr | 77:0b96f6867312 | 284 | protected: |
mjr | 77:0b96f6867312 | 285 | // report code with a specific protocol and sub-protocol |
mjr | 77:0b96f6867312 | 286 | void reportCode(IRRecvProIfc *receiver, int pro, uint64_t code, bool3 toggle, bool3 ditto); |
mjr | 77:0b96f6867312 | 287 | }; |
mjr | 77:0b96f6867312 | 288 | |
mjr | 77:0b96f6867312 | 289 | // ----------------------------------------------------------------------- |
mjr | 77:0b96f6867312 | 290 | // |
mjr | 77:0b96f6867312 | 291 | // Protocol containing a decoded command value |
mjr | 77:0b96f6867312 | 292 | // |
mjr | 77:0b96f6867312 | 293 | template<class CodeType> class IRPWithCode: public IRProtocol |
mjr | 77:0b96f6867312 | 294 | { |
mjr | 77:0b96f6867312 | 295 | public: |
mjr | 77:0b96f6867312 | 296 | IRPWithCode() |
mjr | 77:0b96f6867312 | 297 | { |
mjr | 77:0b96f6867312 | 298 | rxState = 0; |
mjr | 77:0b96f6867312 | 299 | bit = 0; |
mjr | 77:0b96f6867312 | 300 | code = 0; |
mjr | 77:0b96f6867312 | 301 | } |
mjr | 77:0b96f6867312 | 302 | |
mjr | 77:0b96f6867312 | 303 | // Minimum gap on receive (space before header) |
mjr | 77:0b96f6867312 | 304 | virtual uint32_t minRxGap() const = 0; |
mjr | 77:0b96f6867312 | 305 | |
mjr | 77:0b96f6867312 | 306 | // Gap time to send on transmit between before the first code, and |
mjr | 77:0b96f6867312 | 307 | // between repeats. By default, we use the the generic txGap() in |
mjr | 77:0b96f6867312 | 308 | // both cases. |
mjr | 77:0b96f6867312 | 309 | virtual uint32_t txGap(IRTXState *state) const = 0; |
mjr | 77:0b96f6867312 | 310 | virtual uint32_t txPreGap(IRTXState *state) const { return txGap(state); } |
mjr | 77:0b96f6867312 | 311 | virtual uint32_t txPostGap(IRTXState *state) const { return txGap(state); } |
mjr | 77:0b96f6867312 | 312 | |
mjr | 77:0b96f6867312 | 313 | // Minimum number of repetitions when transmitting. Some codes |
mjr | 77:0b96f6867312 | 314 | // (e.g., Sony) require a minimum repeat count, no matter how |
mjr | 77:0b96f6867312 | 315 | // quickly the button was released. 1 means that only a single |
mjr | 77:0b96f6867312 | 316 | // transmission is required, which is true of most codes. |
mjr | 77:0b96f6867312 | 317 | virtual int txMinReps(IRTXState *state) const { return 1; } |
mjr | 77:0b96f6867312 | 318 | |
mjr | 77:0b96f6867312 | 319 | // Header mark and space length. Most IR protocols have an initial |
mjr | 77:0b96f6867312 | 320 | // mark and space of fixed length as a lead-in or header. Being of |
mjr | 77:0b96f6867312 | 321 | // fixed length, they carry no data themselves, but they serve three |
mjr | 77:0b96f6867312 | 322 | // useful functions: the initial mark can be used to set an AGC level |
mjr | 77:0b96f6867312 | 323 | // if the receiver needs it (the TSOP sensors don't, but some older |
mjr | 77:0b96f6867312 | 324 | // devices did); they can help distinguish one protocol from another |
mjr | 77:0b96f6867312 | 325 | // by observing the distinctive timing of the header; and they serve |
mjr | 77:0b96f6867312 | 326 | // as synchronization markers, by using timing that can't possibly |
mjr | 77:0b96f6867312 | 327 | // occur in the middle of a code word to tell us unambiguously that |
mjr | 77:0b96f6867312 | 328 | // we're at the start of a code word. |
mjr | 77:0b96f6867312 | 329 | // |
mjr | 77:0b96f6867312 | 330 | // Not all protocols have the lead-in mark and/or space. Some |
mjr | 77:0b96f6867312 | 331 | // protocols simply open with the initial data bit, and others use |
mjr | 77:0b96f6867312 | 332 | // an AGC mark but follow it immediately with a data bit, with no |
mjr | 77:0b96f6867312 | 333 | // intervening fixed space. |
mjr | 77:0b96f6867312 | 334 | // |
mjr | 77:0b96f6867312 | 335 | // * If the protocol doesn't use a header mark at all but opens with |
mjr | 77:0b96f6867312 | 336 | // a mark that's part of a data bit, set tHeaderMark() to 0. |
mjr | 77:0b96f6867312 | 337 | // |
mjr | 77:0b96f6867312 | 338 | // * If the protocol has a fixed AGC mark, but follows it with a |
mjr | 77:0b96f6867312 | 339 | // varying-length space that's part of the first data bit, set |
mjr | 77:0b96f6867312 | 340 | // tHeaderMark() to the fixed mark length and set tHeaderSpace() to 0. |
mjr | 77:0b96f6867312 | 341 | // |
mjr | 77:0b96f6867312 | 342 | virtual uint32_t tHeaderMark() const = 0; |
mjr | 77:0b96f6867312 | 343 | virtual uint32_t tHeaderSpace() const = 0; |
mjr | 77:0b96f6867312 | 344 | |
mjr | 77:0b96f6867312 | 345 | // Can the header space be adjacent to a data space? In most protocols, |
mjr | 77:0b96f6867312 | 346 | // the header space is of constant length because it's always followed |
mjr | 77:0b96f6867312 | 347 | // by a mark. This is accomplished in some protocols with explicit |
mjr | 77:0b96f6867312 | 348 | // structural marks; in some, it happens naturally because all bits start |
mjr | 77:0b96f6867312 | 349 | // with marks (e.g., NEC, Sony); and in others, the protocol requires a |
mjr | 77:0b96f6867312 | 350 | // a "start bit" with a fixed 0 or 1 value whose representation starts |
mjr | 77:0b96f6867312 | 351 | // with a mark (e.g., RC6). But in a few protocols (e.g., OrtekMCE), |
mjr | 77:0b96f6867312 | 352 | // there's no such care taken, so the header space can flow into a space |
mjr | 77:0b96f6867312 | 353 | // at the start of the first data bit. Set this to true for such |
mjr | 77:0b96f6867312 | 354 | // protocols, and we'll divvy up the space after the header mark into |
mjr | 77:0b96f6867312 | 355 | // a fixed part and a data portion for the first bit. |
mjr | 77:0b96f6867312 | 356 | virtual bool headerSpaceToData() const { return false; } |
mjr | 77:0b96f6867312 | 357 | |
mjr | 77:0b96f6867312 | 358 | // Ditto header. For codes with dittos that use a distinct header |
mjr | 77:0b96f6867312 | 359 | // format, this gives the header timing that identifies a ditto. |
mjr | 77:0b96f6867312 | 360 | // Return zero for a code that doesn't use dittos at all or encodes |
mjr | 77:0b96f6867312 | 361 | // them in some other way than a distinctive header (e.g., in the |
mjr | 77:0b96f6867312 | 362 | // payload data). |
mjr | 77:0b96f6867312 | 363 | virtual uint32_t tDittoMark() const { return 0; } |
mjr | 77:0b96f6867312 | 364 | virtual uint32_t tDittoSpace() const { return 0; } |
mjr | 77:0b96f6867312 | 365 | |
mjr | 77:0b96f6867312 | 366 | // Stop mark length. Many codes have a fixed-length mark following |
mjr | 77:0b96f6867312 | 367 | // the data bits. Return 0 if there's no final mark. |
mjr | 77:0b96f6867312 | 368 | virtual uint32_t tStopMark() const { return 0; } |
mjr | 77:0b96f6867312 | 369 | |
mjr | 77:0b96f6867312 | 370 | // Number of bits in the code. For protocols with multiple bit |
mjr | 77:0b96f6867312 | 371 | // lengths, use the longest here. |
mjr | 77:0b96f6867312 | 372 | virtual int nbits() const = 0; |
mjr | 77:0b96f6867312 | 373 | |
mjr | 77:0b96f6867312 | 374 | // true -> bits arrive LSB-first, false -> MSB first |
mjr | 77:0b96f6867312 | 375 | virtual bool lsbFirst() const { return true; } |
mjr | 77:0b96f6867312 | 376 | |
mjr | 77:0b96f6867312 | 377 | // Pulse processing state machine. |
mjr | 77:0b96f6867312 | 378 | // This state machine handles the basic protocol structure used by |
mjr | 77:0b96f6867312 | 379 | // most IR remotes: |
mjr | 77:0b96f6867312 | 380 | // |
mjr | 77:0b96f6867312 | 381 | // Header mark of fixed duration |
mjr | 77:0b96f6867312 | 382 | // Header space (possibly followed directly by the first bit's space) |
mjr | 77:0b96f6867312 | 383 | // Data bits |
mjr | 77:0b96f6867312 | 384 | // Stop mark |
mjr | 77:0b96f6867312 | 385 | // Gap between codes |
mjr | 77:0b96f6867312 | 386 | // Ditto header mark } a pattern that's distinguishable from |
mjr | 77:0b96f6867312 | 387 | // Ditto header space } the standard header mark |
mjr | 77:0b96f6867312 | 388 | // Ditto data bits |
mjr | 77:0b96f6867312 | 389 | // Gap betwee codes |
mjr | 77:0b96f6867312 | 390 | // |
mjr | 77:0b96f6867312 | 391 | // The state machine can handle protocols that use all of these sections, |
mjr | 77:0b96f6867312 | 392 | // or only a subset of them. For example, a few protocols (Denon, RC5) |
mjr | 77:0b96f6867312 | 393 | // have no header at all and start directly wtih the data bits. Most |
mjr | 77:0b96f6867312 | 394 | // protocols have no "ditto" coding and just repeat the main code to |
mjr | 77:0b96f6867312 | 395 | // signal auto-repeat. |
mjr | 77:0b96f6867312 | 396 | // |
mjr | 77:0b96f6867312 | 397 | // The monolithic state machine switch looks kind of ugly, but it seems |
mjr | 77:0b96f6867312 | 398 | // to be the cleanest way to handle this. My initial design was more OO, |
mjr | 77:0b96f6867312 | 399 | // using virtual subroutines to handle each step. But that turned out to |
mjr | 77:0b96f6867312 | 400 | // be fragile, because there was too much interaction between the handlers |
mjr | 77:0b96f6867312 | 401 | // and the overall state machine sequencing. The monolithic switch actually |
mjr | 77:0b96f6867312 | 402 | // seems much cleaner in practice. The variations are all handled through |
mjr | 77:0b96f6867312 | 403 | // simple data parameters. The resulting design isn't as flexible in |
mjr | 77:0b96f6867312 | 404 | // principle as something more virtualized at each step, but nearly all |
mjr | 77:0b96f6867312 | 405 | // of the IR protocols I've seen so far are so close to the same basic |
mjr | 77:0b96f6867312 | 406 | // structure that this routine works for practically everything. Any |
mjr | 77:0b96f6867312 | 407 | // protocols that don't fit the same structure will be best served by |
mjr | 77:0b96f6867312 | 408 | // replacing the whole state machine for the individual protocols. |
mjr | 77:0b96f6867312 | 409 | virtual void rxPulse(IRRecvProIfc *receiver, uint32_t t, bool mark) |
mjr | 77:0b96f6867312 | 410 | { |
mjr | 77:0b96f6867312 | 411 | uint32_t tRef; |
mjr | 77:0b96f6867312 | 412 | switch (rxState) |
mjr | 77:0b96f6867312 | 413 | { |
mjr | 77:0b96f6867312 | 414 | case 0: |
mjr | 77:0b96f6867312 | 415 | s0: |
mjr | 77:0b96f6867312 | 416 | // Initial gap or inter-code gap. When we see a space of |
mjr | 77:0b96f6867312 | 417 | // sufficient length, switch to Header Mark mode. |
mjr | 77:0b96f6867312 | 418 | rxState = (!mark && t > minRxGap() ? 1 : 0); |
mjr | 77:0b96f6867312 | 419 | break; |
mjr | 77:0b96f6867312 | 420 | |
mjr | 77:0b96f6867312 | 421 | case 1: |
mjr | 77:0b96f6867312 | 422 | s1: |
mjr | 77:0b96f6867312 | 423 | // Header mark. If the protocol has no header mark, go |
mjr | 77:0b96f6867312 | 424 | // straight to the data section. Otherwise, if we have |
mjr | 77:0b96f6867312 | 425 | // a mark that matches the header mark we're expecting, |
mjr | 77:0b96f6867312 | 426 | // go to Header Space mode. Otherwise, we're probably in |
mjr | 77:0b96f6867312 | 427 | // the middle of a code that we missed the beginning of, or |
mjr | 77:0b96f6867312 | 428 | // we're just receiving a code using another protocol. Go |
mjr | 77:0b96f6867312 | 429 | // back to Gap mode - that will ignore everything until we |
mjr | 77:0b96f6867312 | 430 | // get radio silence for long enough to be sure we're |
mjr | 77:0b96f6867312 | 431 | // starting a brand new code word. |
mjr | 77:0b96f6867312 | 432 | if ((tRef = tHeaderMark()) == 0) |
mjr | 77:0b96f6867312 | 433 | goto s3; |
mjr | 77:0b96f6867312 | 434 | rxState = (mark && inRange(t, tRef) ? 2 : 0); |
mjr | 77:0b96f6867312 | 435 | break; |
mjr | 77:0b96f6867312 | 436 | |
mjr | 77:0b96f6867312 | 437 | case 2: |
mjr | 77:0b96f6867312 | 438 | s2: |
mjr | 77:0b96f6867312 | 439 | // Header space. If the protocol doesn't have a header |
mjr | 77:0b96f6867312 | 440 | // space, go straight to the data. |
mjr | 77:0b96f6867312 | 441 | if ((tRef = tHeaderSpace()) == 0) |
mjr | 77:0b96f6867312 | 442 | goto s3; |
mjr | 77:0b96f6867312 | 443 | |
mjr | 77:0b96f6867312 | 444 | // If this protocol has an undelimited header space, make |
mjr | 77:0b96f6867312 | 445 | // sure this space is long enough to qualify, but allow it |
mjr | 77:0b96f6867312 | 446 | // to be longer. If it qualifies, deduct the header space |
mjr | 77:0b96f6867312 | 447 | // span from it and apply the balance as the first data bit. |
mjr | 77:0b96f6867312 | 448 | if (headerSpaceToData() && aboveRange(t, tRef, tRef)) |
mjr | 77:0b96f6867312 | 449 | { |
mjr | 77:0b96f6867312 | 450 | t -= tRef; |
mjr | 77:0b96f6867312 | 451 | goto s3; |
mjr | 77:0b96f6867312 | 452 | } |
mjr | 77:0b96f6867312 | 453 | |
mjr | 77:0b96f6867312 | 454 | // If we have a space matching the header space, enter the |
mjr | 77:0b96f6867312 | 455 | // data section. |
mjr | 77:0b96f6867312 | 456 | if (!mark && inRange(t, tRef)) |
mjr | 77:0b96f6867312 | 457 | { |
mjr | 77:0b96f6867312 | 458 | rxState = 3; |
mjr | 77:0b96f6867312 | 459 | break; |
mjr | 77:0b96f6867312 | 460 | } |
mjr | 77:0b96f6867312 | 461 | |
mjr | 77:0b96f6867312 | 462 | // Not a match - go back to gap mode |
mjr | 77:0b96f6867312 | 463 | goto s0; |
mjr | 77:0b96f6867312 | 464 | |
mjr | 77:0b96f6867312 | 465 | case 3: |
mjr | 77:0b96f6867312 | 466 | s3: |
mjr | 77:0b96f6867312 | 467 | // enter data section |
mjr | 77:0b96f6867312 | 468 | rxReset(); |
mjr | 77:0b96f6867312 | 469 | if (mark) |
mjr | 77:0b96f6867312 | 470 | goto s4; |
mjr | 77:0b96f6867312 | 471 | else |
mjr | 77:0b96f6867312 | 472 | goto s5; |
mjr | 77:0b96f6867312 | 473 | |
mjr | 77:0b96f6867312 | 474 | case 4: |
mjr | 77:0b96f6867312 | 475 | s4: |
mjr | 77:0b96f6867312 | 476 | // data mark |
mjr | 77:0b96f6867312 | 477 | if (mark && rxMark(receiver, t)) |
mjr | 77:0b96f6867312 | 478 | { |
mjr | 77:0b96f6867312 | 479 | rxState = bit < nbits() ? 5 : 7; |
mjr | 77:0b96f6867312 | 480 | break; |
mjr | 77:0b96f6867312 | 481 | } |
mjr | 77:0b96f6867312 | 482 | goto s0; |
mjr | 77:0b96f6867312 | 483 | |
mjr | 77:0b96f6867312 | 484 | case 5: |
mjr | 77:0b96f6867312 | 485 | s5: |
mjr | 77:0b96f6867312 | 486 | // data space |
mjr | 77:0b96f6867312 | 487 | if (!mark && rxSpace(receiver, t)) |
mjr | 77:0b96f6867312 | 488 | { |
mjr | 77:0b96f6867312 | 489 | rxState = bit < nbits() ? 4 : 6; |
mjr | 77:0b96f6867312 | 490 | break; |
mjr | 77:0b96f6867312 | 491 | } |
mjr | 77:0b96f6867312 | 492 | else if (!mark && t > minRxGap()) |
mjr | 77:0b96f6867312 | 493 | goto s7; |
mjr | 77:0b96f6867312 | 494 | goto s0; |
mjr | 77:0b96f6867312 | 495 | |
mjr | 77:0b96f6867312 | 496 | case 6: |
mjr | 77:0b96f6867312 | 497 | // stop mark - if we don't have a mark, go to the gap instead |
mjr | 77:0b96f6867312 | 498 | if (!mark) |
mjr | 77:0b96f6867312 | 499 | goto s7; |
mjr | 77:0b96f6867312 | 500 | |
mjr | 77:0b96f6867312 | 501 | // check to see if the protocol even has a stop mark |
mjr | 77:0b96f6867312 | 502 | if ((tRef = tStopMark()) == 0) |
mjr | 77:0b96f6867312 | 503 | { |
mjr | 77:0b96f6867312 | 504 | // The protocol has no stop mark, and we've processed |
mjr | 77:0b96f6867312 | 505 | // the last data bit. Close the data section and go |
mjr | 77:0b96f6867312 | 506 | // straight to the next header. |
mjr | 77:0b96f6867312 | 507 | rxClose(receiver, false); |
mjr | 77:0b96f6867312 | 508 | goto s8; |
mjr | 77:0b96f6867312 | 509 | } |
mjr | 77:0b96f6867312 | 510 | |
mjr | 77:0b96f6867312 | 511 | // there is a mark - make sure it's in range |
mjr | 77:0b96f6867312 | 512 | if (inRange(t, tRef)) |
mjr | 77:0b96f6867312 | 513 | { |
mjr | 77:0b96f6867312 | 514 | rxState = 7; |
mjr | 77:0b96f6867312 | 515 | break; |
mjr | 77:0b96f6867312 | 516 | } |
mjr | 77:0b96f6867312 | 517 | goto s0; |
mjr | 77:0b96f6867312 | 518 | |
mjr | 77:0b96f6867312 | 519 | case 7: |
mjr | 77:0b96f6867312 | 520 | s7: |
mjr | 77:0b96f6867312 | 521 | // end of data - require minimum gap |
mjr | 77:0b96f6867312 | 522 | if (!mark && t > minRxGap()) |
mjr | 77:0b96f6867312 | 523 | { |
mjr | 77:0b96f6867312 | 524 | // close the data section |
mjr | 77:0b96f6867312 | 525 | rxClose(receiver, false); |
mjr | 77:0b96f6867312 | 526 | rxState = 8; |
mjr | 77:0b96f6867312 | 527 | break; |
mjr | 77:0b96f6867312 | 528 | } |
mjr | 77:0b96f6867312 | 529 | goto s0; |
mjr | 77:0b96f6867312 | 530 | |
mjr | 77:0b96f6867312 | 531 | case 8: |
mjr | 77:0b96f6867312 | 532 | s8: |
mjr | 77:0b96f6867312 | 533 | // Ditto header. If the protocol has a ditto header at all, |
mjr | 77:0b96f6867312 | 534 | // and this mark matches, proceed to the ditto space. Otherwise |
mjr | 77:0b96f6867312 | 535 | // try interepreting this as a new regular header instead. |
mjr | 77:0b96f6867312 | 536 | if (mark && (tRef = tDittoMark()) != 0 && inRange(t, tRef)) |
mjr | 77:0b96f6867312 | 537 | { |
mjr | 77:0b96f6867312 | 538 | rxState = 9; |
mjr | 77:0b96f6867312 | 539 | break; |
mjr | 77:0b96f6867312 | 540 | } |
mjr | 77:0b96f6867312 | 541 | goto s1; |
mjr | 77:0b96f6867312 | 542 | |
mjr | 77:0b96f6867312 | 543 | case 9: |
mjr | 77:0b96f6867312 | 544 | // Ditto space. If this doesn't match the ditto space, and |
mjr | 77:0b96f6867312 | 545 | // the ditto header and regular header are the same, try |
mjr | 77:0b96f6867312 | 546 | // re-interpreting the space as a new regular header space. |
mjr | 77:0b96f6867312 | 547 | if (!mark && (tRef = tDittoSpace()) != 0 && inRange(t, tRef)) |
mjr | 77:0b96f6867312 | 548 | { |
mjr | 77:0b96f6867312 | 549 | rxState = 10; |
mjr | 77:0b96f6867312 | 550 | break; |
mjr | 77:0b96f6867312 | 551 | } |
mjr | 77:0b96f6867312 | 552 | else if (!mark && tDittoMark() == tHeaderMark()) |
mjr | 77:0b96f6867312 | 553 | goto s2; |
mjr | 77:0b96f6867312 | 554 | goto s0; |
mjr | 77:0b96f6867312 | 555 | |
mjr | 77:0b96f6867312 | 556 | case 10: |
mjr | 77:0b96f6867312 | 557 | // Enter ditto data |
mjr | 77:0b96f6867312 | 558 | rxDittoReset(); |
mjr | 77:0b96f6867312 | 559 | goto s11; |
mjr | 77:0b96f6867312 | 560 | |
mjr | 77:0b96f6867312 | 561 | case 11: |
mjr | 77:0b96f6867312 | 562 | s11: |
mjr | 77:0b96f6867312 | 563 | // Ditto data - mark |
mjr | 77:0b96f6867312 | 564 | if (mark && rxMark(receiver, t)) |
mjr | 77:0b96f6867312 | 565 | { |
mjr | 77:0b96f6867312 | 566 | rxState = bit < nbits() ? 12 : 13; |
mjr | 77:0b96f6867312 | 567 | break; |
mjr | 77:0b96f6867312 | 568 | } |
mjr | 77:0b96f6867312 | 569 | goto s0; |
mjr | 77:0b96f6867312 | 570 | |
mjr | 77:0b96f6867312 | 571 | case 12: |
mjr | 77:0b96f6867312 | 572 | // data space |
mjr | 77:0b96f6867312 | 573 | if (!mark && rxSpace(receiver, t)) |
mjr | 77:0b96f6867312 | 574 | { |
mjr | 77:0b96f6867312 | 575 | rxState = bit < nbits() ? 11 : 13; |
mjr | 77:0b96f6867312 | 576 | break; |
mjr | 77:0b96f6867312 | 577 | } |
mjr | 77:0b96f6867312 | 578 | else if (!mark && t > minRxGap()) |
mjr | 77:0b96f6867312 | 579 | goto s13; |
mjr | 77:0b96f6867312 | 580 | goto s0; |
mjr | 77:0b96f6867312 | 581 | |
mjr | 77:0b96f6867312 | 582 | case 13: |
mjr | 77:0b96f6867312 | 583 | s13: |
mjr | 77:0b96f6867312 | 584 | // end ditto data |
mjr | 77:0b96f6867312 | 585 | if (!mark && t > minRxGap()) |
mjr | 77:0b96f6867312 | 586 | { |
mjr | 77:0b96f6867312 | 587 | // close the ditto data section |
mjr | 77:0b96f6867312 | 588 | rxClose(receiver, true); |
mjr | 77:0b96f6867312 | 589 | rxState = 8; |
mjr | 77:0b96f6867312 | 590 | break; |
mjr | 77:0b96f6867312 | 591 | } |
mjr | 77:0b96f6867312 | 592 | goto s0; |
mjr | 77:0b96f6867312 | 593 | } |
mjr | 77:0b96f6867312 | 594 | |
mjr | 77:0b96f6867312 | 595 | // if this is a space longer than the timeout, go into idle mode |
mjr | 77:0b96f6867312 | 596 | if (!mark && t >= IRReceiver::MAX_PULSE) |
mjr | 77:0b96f6867312 | 597 | rxIdle(receiver); |
mjr | 77:0b96f6867312 | 598 | } |
mjr | 77:0b96f6867312 | 599 | |
mjr | 77:0b96f6867312 | 600 | // Start transmission. By convention, we start each transmission with |
mjr | 77:0b96f6867312 | 601 | // a gap of sufficient length to allow receivers to recognize a new |
mjr | 77:0b96f6867312 | 602 | // transmission. The only protocol-specific work we usually have to |
mjr | 77:0b96f6867312 | 603 | // do here is to prepare a bit string to send. |
mjr | 77:0b96f6867312 | 604 | virtual int txStart(IRTXState *state) |
mjr | 77:0b96f6867312 | 605 | { |
mjr | 77:0b96f6867312 | 606 | // convert the code into a bitstream to send |
mjr | 77:0b96f6867312 | 607 | codeToBitstream(state); |
mjr | 77:0b96f6867312 | 608 | |
mjr | 77:0b96f6867312 | 609 | // transmit the initial gap to make sure we've been silent long enough |
mjr | 77:0b96f6867312 | 610 | return txPreGap(state); |
mjr | 77:0b96f6867312 | 611 | } |
mjr | 77:0b96f6867312 | 612 | |
mjr | 77:0b96f6867312 | 613 | // Continue transmission. Most protocols have a similar structure, |
mjr | 77:0b96f6867312 | 614 | // with a header mark, header gap, data section, and trailing gap. |
mjr | 77:0b96f6867312 | 615 | // We implement the framework for this common structure with a |
mjr | 77:0b96f6867312 | 616 | // simple state machine: |
mjr | 77:0b96f6867312 | 617 | // |
mjr | 77:0b96f6867312 | 618 | // state 0 = done with gap, transmitting header mark |
mjr | 77:0b96f6867312 | 619 | // state 1 = done with header, transmitting header space |
mjr | 77:0b96f6867312 | 620 | // state 2 = transmitting data bits, via txDataStep() in subclasses |
mjr | 77:0b96f6867312 | 621 | // state 3 = done with data, sending post-code gap |
mjr | 77:0b96f6867312 | 622 | // |
mjr | 77:0b96f6867312 | 623 | // Subclasses for protocols that don't follow the usual structure can |
mjr | 77:0b96f6867312 | 624 | // override this entire routine as needed and redefine these internal |
mjr | 77:0b96f6867312 | 625 | // states. Protocols that match the common structure will only have |
mjr | 77:0b96f6867312 | 626 | // to define txDataStep(). |
mjr | 77:0b96f6867312 | 627 | // |
mjr | 77:0b96f6867312 | 628 | // Returns the time to the next step, or a negative value if we're |
mjr | 77:0b96f6867312 | 629 | // done with the transmission. |
mjr | 77:0b96f6867312 | 630 | virtual int txStep(IRTXState *state) |
mjr | 77:0b96f6867312 | 631 | { |
mjr | 77:0b96f6867312 | 632 | // The individual step handlers can return 0 to indicate |
mjr | 77:0b96f6867312 | 633 | // that we should go immediately to the next step without |
mjr | 77:0b96f6867312 | 634 | // a delay, so iterate as long as they return 0. |
mjr | 77:0b96f6867312 | 635 | for (;;) |
mjr | 77:0b96f6867312 | 636 | { |
mjr | 77:0b96f6867312 | 637 | // Use the first-code or "ditto" handling, as appropriate |
mjr | 77:0b96f6867312 | 638 | int t = state->rep > 0 && state->dittos ? |
mjr | 77:0b96f6867312 | 639 | txDittoStep(state) : |
mjr | 77:0b96f6867312 | 640 | txMainStep(state); |
mjr | 77:0b96f6867312 | 641 | |
mjr | 77:0b96f6867312 | 642 | // If it's a positive time, it's a delay; if it's a negative |
mjr | 77:0b96f6867312 | 643 | // time, it's the end of the transmission. In either case, |
mjr | 77:0b96f6867312 | 644 | // return the time to the main transmitter routine. If it's |
mjr | 77:0b96f6867312 | 645 | // zero, though, it means to proceed without delay, so we'll |
mjr | 77:0b96f6867312 | 646 | // simply continue iterating. |
mjr | 77:0b96f6867312 | 647 | if (t != 0) |
mjr | 77:0b96f6867312 | 648 | return t; |
mjr | 77:0b96f6867312 | 649 | } |
mjr | 77:0b96f6867312 | 650 | } |
mjr | 77:0b96f6867312 | 651 | |
mjr | 77:0b96f6867312 | 652 | // Main transmission handler. This handles the txStep() work for |
mjr | 77:0b96f6867312 | 653 | // the first code in a repeat group. |
mjr | 77:0b96f6867312 | 654 | virtual int txMainStep(IRTXState *state) |
mjr | 77:0b96f6867312 | 655 | { |
mjr | 77:0b96f6867312 | 656 | // One state might transition directly to the next state |
mjr | 77:0b96f6867312 | 657 | // without any time delay, so keep going until we come to |
mjr | 77:0b96f6867312 | 658 | // a wait state. |
mjr | 77:0b96f6867312 | 659 | int t; |
mjr | 77:0b96f6867312 | 660 | for (;;) |
mjr | 77:0b96f6867312 | 661 | { |
mjr | 77:0b96f6867312 | 662 | switch (state->step) |
mjr | 77:0b96f6867312 | 663 | { |
mjr | 77:0b96f6867312 | 664 | case 0: |
mjr | 77:0b96f6867312 | 665 | // Finished the pre-code gap. This is the actual start |
mjr | 77:0b96f6867312 | 666 | // of the transmission, so mark the time. |
mjr | 77:0b96f6867312 | 667 | state->txTime.reset(); |
mjr | 77:0b96f6867312 | 668 | |
mjr | 77:0b96f6867312 | 669 | // if there's a header mark, transmit it |
mjr | 77:0b96f6867312 | 670 | state->step++; |
mjr | 77:0b96f6867312 | 671 | if ((t = this->tHeaderMark()) > 0) |
mjr | 77:0b96f6867312 | 672 | { |
mjr | 77:0b96f6867312 | 673 | state->pin->write(pwmDutyCycle()); |
mjr | 77:0b96f6867312 | 674 | return t; |
mjr | 77:0b96f6867312 | 675 | } |
mjr | 77:0b96f6867312 | 676 | break; |
mjr | 77:0b96f6867312 | 677 | |
mjr | 77:0b96f6867312 | 678 | case 1: |
mjr | 77:0b96f6867312 | 679 | // finished header mark, start header space |
mjr | 77:0b96f6867312 | 680 | state->step++; |
mjr | 77:0b96f6867312 | 681 | if ((t = this->tHeaderSpace()) > 0) |
mjr | 77:0b96f6867312 | 682 | { |
mjr | 77:0b96f6867312 | 683 | state->pin->write(0); |
mjr | 77:0b96f6867312 | 684 | return this->tHeaderSpace(); |
mjr | 77:0b96f6867312 | 685 | } |
mjr | 77:0b96f6867312 | 686 | break; |
mjr | 77:0b96f6867312 | 687 | |
mjr | 77:0b96f6867312 | 688 | case 2: |
mjr | 77:0b96f6867312 | 689 | // data section - this is up to the subclass |
mjr | 77:0b96f6867312 | 690 | if ((t = txDataStep(state)) != 0) |
mjr | 77:0b96f6867312 | 691 | return t; |
mjr | 77:0b96f6867312 | 692 | break; |
mjr | 77:0b96f6867312 | 693 | |
mjr | 77:0b96f6867312 | 694 | case 3: |
mjr | 77:0b96f6867312 | 695 | // done with data; send the stop mark, if applicable |
mjr | 77:0b96f6867312 | 696 | state->step++; |
mjr | 77:0b96f6867312 | 697 | if ((t = tStopMark()) > 0) |
mjr | 77:0b96f6867312 | 698 | { |
mjr | 77:0b96f6867312 | 699 | state->pin->write(pwmDutyCycle()); |
mjr | 77:0b96f6867312 | 700 | return t; |
mjr | 77:0b96f6867312 | 701 | } |
mjr | 77:0b96f6867312 | 702 | break; |
mjr | 77:0b96f6867312 | 703 | |
mjr | 77:0b96f6867312 | 704 | case 4: |
mjr | 77:0b96f6867312 | 705 | // post-code gap |
mjr | 77:0b96f6867312 | 706 | state->step++; |
mjr | 77:0b96f6867312 | 707 | state->pin->write(0); |
mjr | 77:0b96f6867312 | 708 | if ((t = this->txPostGap(state)) > 0) |
mjr | 77:0b96f6867312 | 709 | return t; |
mjr | 77:0b96f6867312 | 710 | break; |
mjr | 77:0b96f6867312 | 711 | |
mjr | 77:0b96f6867312 | 712 | default: |
mjr | 77:0b96f6867312 | 713 | // Done with the iteration. Finalize the transmission; |
mjr | 77:0b96f6867312 | 714 | // this will figure out if we're going to repeat. |
mjr | 77:0b96f6867312 | 715 | return this->txEnd(state); |
mjr | 77:0b96f6867312 | 716 | } |
mjr | 77:0b96f6867312 | 717 | } |
mjr | 77:0b96f6867312 | 718 | } |
mjr | 77:0b96f6867312 | 719 | |
mjr | 77:0b96f6867312 | 720 | // Ditto step handler. This handles txStep() work for a repeated |
mjr | 77:0b96f6867312 | 721 | // code. Most protocols just re-transmit the same code each time, |
mjr | 77:0b96f6867312 | 722 | // so by default we use the main step handling. Subclasses for |
mjr | 77:0b96f6867312 | 723 | // protocols that transmit different codes on repeat (such as NEC) |
mjr | 77:0b96f6867312 | 724 | // can override this to send the ditto code instead. |
mjr | 77:0b96f6867312 | 725 | virtual int txDittoStep(IRTXState *state) |
mjr | 77:0b96f6867312 | 726 | { |
mjr | 77:0b96f6867312 | 727 | return txMainStep(state); |
mjr | 77:0b96f6867312 | 728 | } |
mjr | 77:0b96f6867312 | 729 | |
mjr | 77:0b96f6867312 | 730 | // Handle a txStep() iteration for a data bit. Subclasses must |
mjr | 77:0b96f6867312 | 731 | // override this to handle the particulars of sending the data bits. |
mjr | 77:0b96f6867312 | 732 | // At the end of the data transmission, the subclass should increment |
mjr | 77:0b96f6867312 | 733 | // state->step to tell us that we've reached the post-code gap. |
mjr | 77:0b96f6867312 | 734 | virtual int txDataStep(IRTXState *state) |
mjr | 77:0b96f6867312 | 735 | { |
mjr | 77:0b96f6867312 | 736 | state->step++; |
mjr | 77:0b96f6867312 | 737 | return 0; |
mjr | 77:0b96f6867312 | 738 | } |
mjr | 77:0b96f6867312 | 739 | |
mjr | 77:0b96f6867312 | 740 | // Send the stop bit, if applicable. If there's no stop bit or |
mjr | 77:0b96f6867312 | 741 | // equivalent, simply increment state->step and return 0; |
mjr | 77:0b96f6867312 | 742 | int txStopBit(IRTXState *state) |
mjr | 77:0b96f6867312 | 743 | { |
mjr | 77:0b96f6867312 | 744 | state->step++; |
mjr | 77:0b96f6867312 | 745 | return 0; |
mjr | 77:0b96f6867312 | 746 | } |
mjr | 77:0b96f6867312 | 747 | |
mjr | 77:0b96f6867312 | 748 | // Handle the end of a transmission. This figures out if we're |
mjr | 77:0b96f6867312 | 749 | // going to auto-repeat, and if so, resets for the next iteration. |
mjr | 77:0b96f6867312 | 750 | // If we're going to repeat, we return the time to the next tx step, |
mjr | 77:0b96f6867312 | 751 | // as usual. If the transmission is done, we return -1 to indicate |
mjr | 77:0b96f6867312 | 752 | // that no more steps are required. |
mjr | 77:0b96f6867312 | 753 | int txEnd(IRTXState *state) |
mjr | 77:0b96f6867312 | 754 | { |
mjr | 77:0b96f6867312 | 755 | // count the repeat |
mjr | 77:0b96f6867312 | 756 | state->rep++; |
mjr | 77:0b96f6867312 | 757 | |
mjr | 77:0b96f6867312 | 758 | // If the button is still down, or we haven't reached our minimum |
mjr | 77:0b96f6867312 | 759 | // repetition count, repeat the code. |
mjr | 77:0b96f6867312 | 760 | if (state->pressed || state->rep < txMinReps(state)) |
mjr | 77:0b96f6867312 | 761 | { |
mjr | 77:0b96f6867312 | 762 | // return to the first transmission step |
mjr | 77:0b96f6867312 | 763 | state->step = 0; |
mjr | 77:0b96f6867312 | 764 | state->bit = 0; |
mjr | 77:0b96f6867312 | 765 | state->bitstep = 0; |
mjr | 77:0b96f6867312 | 766 | |
mjr | 77:0b96f6867312 | 767 | // re-generate the bitstream, in case we need to encode positional |
mjr | 77:0b96f6867312 | 768 | // information such as a toggle bit or position counter |
mjr | 77:0b96f6867312 | 769 | codeToBitstream(state); |
mjr | 77:0b96f6867312 | 770 | |
mjr | 77:0b96f6867312 | 771 | // restart the transmission timer |
mjr | 77:0b96f6867312 | 772 | state->txTime.reset(); |
mjr | 77:0b96f6867312 | 773 | |
mjr | 77:0b96f6867312 | 774 | // we can go immediately to the next step, so return a zero delay |
mjr | 77:0b96f6867312 | 775 | return 0; |
mjr | 77:0b96f6867312 | 776 | } |
mjr | 77:0b96f6867312 | 777 | |
mjr | 77:0b96f6867312 | 778 | // we're well and truly done - tell the caller not to call us |
mjr | 77:0b96f6867312 | 779 | // again by returning a negative time interval |
mjr | 77:0b96f6867312 | 780 | return -1; |
mjr | 77:0b96f6867312 | 781 | } |
mjr | 77:0b96f6867312 | 782 | |
mjr | 77:0b96f6867312 | 783 | protected: |
mjr | 77:0b96f6867312 | 784 | // Reset the receiver. This is called when the receiver enters |
mjr | 77:0b96f6867312 | 785 | // the data section of the frame, after parsing the header (or |
mjr | 77:0b96f6867312 | 786 | // after a gap, if the protocol doesn't use a header). |
mjr | 77:0b96f6867312 | 787 | virtual void rxReset() |
mjr | 77:0b96f6867312 | 788 | { |
mjr | 77:0b96f6867312 | 789 | bit = 0; |
mjr | 77:0b96f6867312 | 790 | code = 0; |
mjr | 77:0b96f6867312 | 791 | } |
mjr | 77:0b96f6867312 | 792 | |
mjr | 77:0b96f6867312 | 793 | // Reset on entering a new ditto frame. This is called after |
mjr | 77:0b96f6867312 | 794 | // parsing a "ditto" header. This is only needed for protocols |
mjr | 77:0b96f6867312 | 795 | // that use distinctive ditto framing. |
mjr | 77:0b96f6867312 | 796 | virtual void rxDittoReset() { } |
mjr | 77:0b96f6867312 | 797 | |
mjr | 77:0b96f6867312 | 798 | // Receiver is going idle. This is called any time we get a space |
mjr | 77:0b96f6867312 | 799 | // (IR OFF) that exceeds the general receiver timeout, regardless |
mjr | 77:0b96f6867312 | 800 | // of protocol state. |
mjr | 77:0b96f6867312 | 801 | virtual void rxIdle(IRRecvProIfc *receiver) { } |
mjr | 77:0b96f6867312 | 802 | |
mjr | 77:0b96f6867312 | 803 | // Parse a data mark or space. If the symbol is valid, shifts the |
mjr | 77:0b96f6867312 | 804 | // bit into 'code' (the code word under construction) and returns |
mjr | 77:0b96f6867312 | 805 | // true. If the symbol is invalid, returns false. Updates the |
mjr | 77:0b96f6867312 | 806 | // bit counter in 'bit' if this symbol finishes a bit. |
mjr | 77:0b96f6867312 | 807 | virtual bool rxMark(IRRecvProIfc *receiver, uint32_t t) { return false; } |
mjr | 77:0b96f6867312 | 808 | virtual bool rxSpace(IRRecvProIfc *receiver, uint32_t t) { return false; } |
mjr | 77:0b96f6867312 | 809 | |
mjr | 77:0b96f6867312 | 810 | // Report the decoded value in our internal register, if it's valid. |
mjr | 77:0b96f6867312 | 811 | // By default, we'll report the code value as stored in 'code', with |
mjr | 77:0b96f6867312 | 812 | // no toggle bit, if the number of bits we've decoded matches the |
mjr | 77:0b96f6867312 | 813 | // expected number of bits as given by nbits(). Subclasses can |
mjr | 77:0b96f6867312 | 814 | // override as needed to do other validation checks; e.g., protocols |
mjr | 77:0b96f6867312 | 815 | // with varying bit lengths will need to check for all of the valid |
mjr | 77:0b96f6867312 | 816 | // bit lengths, and protocols that contain error-checking information |
mjr | 77:0b96f6867312 | 817 | // can validate that. |
mjr | 77:0b96f6867312 | 818 | // |
mjr | 77:0b96f6867312 | 819 | // Unless the protocol subclass overrides the basic pulse handler |
mjr | 77:0b96f6867312 | 820 | // (rxPulse()), this is called when we end the data section of the |
mjr | 77:0b96f6867312 | 821 | // code. |
mjr | 77:0b96f6867312 | 822 | virtual void rxClose(IRRecvProIfc *receiver, bool ditto) |
mjr | 77:0b96f6867312 | 823 | { |
mjr | 77:0b96f6867312 | 824 | if (bit == nbits()) |
mjr | 77:0b96f6867312 | 825 | reportCode(receiver, id(), code, bool3::null, ditto); |
mjr | 77:0b96f6867312 | 826 | } |
mjr | 77:0b96f6867312 | 827 | |
mjr | 77:0b96f6867312 | 828 | // Encode a universal code value into a bit string in preparation |
mjr | 77:0b96f6867312 | 829 | // for transmission. This should take the code value in state->cmdCode, |
mjr | 77:0b96f6867312 | 830 | // convert it to the string of bits to serially, and store the result |
mjr | 77:0b96f6867312 | 831 | // in state->bitstream. |
mjr | 77:0b96f6867312 | 832 | virtual void codeToBitstream(IRTXState *state) |
mjr | 77:0b96f6867312 | 833 | { |
mjr | 77:0b96f6867312 | 834 | // by default, simply store the code as-is |
mjr | 77:0b96f6867312 | 835 | state->bitstream = state->cmdCode; |
mjr | 77:0b96f6867312 | 836 | state->nbits = nbits(); |
mjr | 77:0b96f6867312 | 837 | } |
mjr | 77:0b96f6867312 | 838 | |
mjr | 77:0b96f6867312 | 839 | // Get the next bit for transmission from the bitstream object |
mjr | 77:0b96f6867312 | 840 | int getBit(IRTXState *state) |
mjr | 77:0b96f6867312 | 841 | { |
mjr | 77:0b96f6867312 | 842 | // get the number of bits and the current bit position |
mjr | 77:0b96f6867312 | 843 | int nbits = state->nbits; |
mjr | 77:0b96f6867312 | 844 | int bit = state->bit; |
mjr | 77:0b96f6867312 | 845 | |
mjr | 77:0b96f6867312 | 846 | // figure the bit position according to the bit order |
mjr | 77:0b96f6867312 | 847 | int bitpos = (lsbFirst() ? bit : nbits - bit - 1); |
mjr | 77:0b96f6867312 | 848 | |
mjr | 77:0b96f6867312 | 849 | // pull out the bit |
mjr | 77:0b96f6867312 | 850 | return int((state->bitstream >> bitpos) & 1); |
mjr | 77:0b96f6867312 | 851 | } |
mjr | 77:0b96f6867312 | 852 | |
mjr | 77:0b96f6867312 | 853 | // Set the next bit to '1', optionally incrementing the bit counter |
mjr | 77:0b96f6867312 | 854 | void setBit(bool inc = true) |
mjr | 77:0b96f6867312 | 855 | { |
mjr | 77:0b96f6867312 | 856 | // ignore overflow |
mjr | 77:0b96f6867312 | 857 | int nbits = this->nbits(); |
mjr | 77:0b96f6867312 | 858 | if (bit >= nbits) |
mjr | 77:0b96f6867312 | 859 | return; |
mjr | 77:0b96f6867312 | 860 | |
mjr | 77:0b96f6867312 | 861 | // Figure the starting bit position in 'code' for the bit, |
mjr | 77:0b96f6867312 | 862 | // according to whether we're adding them LSB-first or MSB-first. |
mjr | 77:0b96f6867312 | 863 | int bitpos = (lsbFirst() ? bit : nbits - bit - 1); |
mjr | 77:0b96f6867312 | 864 | |
mjr | 77:0b96f6867312 | 865 | // mask in the bit |
mjr | 77:0b96f6867312 | 866 | code |= (CodeType(1) << bitpos); |
mjr | 77:0b96f6867312 | 867 | |
mjr | 77:0b96f6867312 | 868 | // advance the bit position |
mjr | 77:0b96f6867312 | 869 | if (inc) |
mjr | 77:0b96f6867312 | 870 | ++bit; |
mjr | 77:0b96f6867312 | 871 | } |
mjr | 77:0b96f6867312 | 872 | |
mjr | 77:0b96f6867312 | 873 | // Set the next N bits to '1' |
mjr | 77:0b96f6867312 | 874 | void setBits(int n) |
mjr | 77:0b96f6867312 | 875 | { |
mjr | 77:0b96f6867312 | 876 | // ignore overflow |
mjr | 77:0b96f6867312 | 877 | int nbits = this->nbits(); |
mjr | 77:0b96f6867312 | 878 | if (bit + n - 1 >= nbits) |
mjr | 77:0b96f6867312 | 879 | return; |
mjr | 77:0b96f6867312 | 880 | |
mjr | 77:0b96f6867312 | 881 | // Figure the starting bit position in 'code' for the bits we're |
mjr | 77:0b96f6867312 | 882 | // setting. If bits arrive LSB-first, the 'bit' counter gives us |
mjr | 77:0b96f6867312 | 883 | // the bit position directly. If bits arrive MSB-first, we need |
mjr | 77:0b96f6867312 | 884 | // to start from the high end instead, so we're starting at |
mjr | 77:0b96f6867312 | 885 | // ((nbits()-1) - bit). However, we want to set multiple bits |
mjr | 77:0b96f6867312 | 886 | // left-to-right in any case, so move our starting position to |
mjr | 77:0b96f6867312 | 887 | // the "end" of the window by moving right n-1 addition bits. |
mjr | 77:0b96f6867312 | 888 | int bitpos = (lsbFirst() ? bit : nbits - bit - n); |
mjr | 77:0b96f6867312 | 889 | |
mjr | 77:0b96f6867312 | 890 | // turn that into a bit mask for the first bit |
mjr | 77:0b96f6867312 | 891 | uint64_t mask = (CodeType(1) << bitpos); |
mjr | 77:0b96f6867312 | 892 | |
mjr | 77:0b96f6867312 | 893 | // advance our 'bit' counter past the added bits |
mjr | 77:0b96f6867312 | 894 | bit += n; |
mjr | 77:0b96f6867312 | 895 | |
mjr | 77:0b96f6867312 | 896 | // set each added bit to 1 |
mjr | 77:0b96f6867312 | 897 | for ( ; n != 0 ; --n, mask <<= 1) |
mjr | 77:0b96f6867312 | 898 | code |= mask; |
mjr | 77:0b96f6867312 | 899 | } |
mjr | 77:0b96f6867312 | 900 | |
mjr | 77:0b96f6867312 | 901 | // decoding state |
mjr | 77:0b96f6867312 | 902 | uint8_t rxState; |
mjr | 77:0b96f6867312 | 903 | |
mjr | 77:0b96f6867312 | 904 | // next bit position |
mjr | 77:0b96f6867312 | 905 | uint8_t bit; |
mjr | 77:0b96f6867312 | 906 | |
mjr | 77:0b96f6867312 | 907 | // command code under construction |
mjr | 77:0b96f6867312 | 908 | CodeType code; |
mjr | 77:0b96f6867312 | 909 | }; |
mjr | 77:0b96f6867312 | 910 | |
mjr | 77:0b96f6867312 | 911 | // ----------------------------------------------------------------------- |
mjr | 77:0b96f6867312 | 912 | // |
mjr | 77:0b96f6867312 | 913 | // Basic asynchronous encoding |
mjr | 77:0b96f6867312 | 914 | // |
mjr | 77:0b96f6867312 | 915 | // This is essentially the IR equivalent of a wired UART. The transmission |
mjr | 77:0b96f6867312 | 916 | // for a code word is divided into a fixed number of bit time periods of |
mjr | 77:0b96f6867312 | 917 | // fixed length, with each period containing one bit, signified by IR ON for |
mjr | 77:0b96f6867312 | 918 | // 1 or IR OFF for 0. |
mjr | 77:0b96f6867312 | 919 | // |
mjr | 77:0b96f6867312 | 920 | // Simple async coding doesn't have any inherent structure other than the |
mjr | 77:0b96f6867312 | 921 | // bit length. That makes it hard to distinguish from other IR remotes that |
mjr | 77:0b96f6867312 | 922 | // might share the environment, and even from random noise, which is why |
mjr | 77:0b96f6867312 | 923 | // most CE manufacturers use more structured protocols. In practice, remotes |
mjr | 77:0b96f6867312 | 924 | // using this coding are likely have to impose some sort of structure on the |
mjr | 77:0b96f6867312 | 925 | // bits, such as long bit strings, error checking bits, or distinctive prefixes |
mjr | 77:0b96f6867312 | 926 | // or suffixes. The only example of a pure async code I've seen in the wild |
mjr | 77:0b96f6867312 | 927 | // is Lutron, and they do in fact use fairly long codes (36 bits) with set |
mjr | 77:0b96f6867312 | 928 | // prefixes. Their prefixes aren't only distinctive as bit sequences, but |
mjr | 77:0b96f6867312 | 929 | // also in the raw space/mark timing, which makes them effectively serve the |
mjr | 77:0b96f6867312 | 930 | // function that header marks do in most of the structured protocols. |
mjr | 77:0b96f6867312 | 931 | // |
mjr | 77:0b96f6867312 | 932 | template<class CodeType> class IRPAsync: public IRPWithCode<CodeType> |
mjr | 77:0b96f6867312 | 933 | { |
mjr | 77:0b96f6867312 | 934 | public: |
mjr | 77:0b96f6867312 | 935 | IRPAsync() { } |
mjr | 77:0b96f6867312 | 936 | |
mjr | 77:0b96f6867312 | 937 | // duration of each bit in microseconds |
mjr | 77:0b96f6867312 | 938 | virtual int tBit() const = 0; |
mjr | 77:0b96f6867312 | 939 | |
mjr | 77:0b96f6867312 | 940 | // maximum legal mark in a data section, if applicable |
mjr | 77:0b96f6867312 | 941 | virtual uint32_t maxMark() const { return IRReceiver::MAX_PULSE; } |
mjr | 77:0b96f6867312 | 942 | |
mjr | 77:0b96f6867312 | 943 | // Async codes lack the structure of the more typical codes, so we |
mjr | 77:0b96f6867312 | 944 | // use a completely custom pulse handler |
mjr | 77:0b96f6867312 | 945 | virtual void rxPulse(IRRecvProIfc *receiver, uint32_t t, bool mark) |
mjr | 77:0b96f6867312 | 946 | { |
mjr | 77:0b96f6867312 | 947 | uint32_t tRef, tRef2; |
mjr | 77:0b96f6867312 | 948 | switch (this->rxState) |
mjr | 77:0b96f6867312 | 949 | { |
mjr | 77:0b96f6867312 | 950 | case 0: |
mjr | 77:0b96f6867312 | 951 | s0: |
mjr | 77:0b96f6867312 | 952 | // Gap - if this is a long enough space, switch to header mode. |
mjr | 77:0b96f6867312 | 953 | this->rxState = (!mark && t > this->minRxGap() ? 1 : 0); |
mjr | 77:0b96f6867312 | 954 | break; |
mjr | 77:0b96f6867312 | 955 | |
mjr | 77:0b96f6867312 | 956 | case 1: |
mjr | 77:0b96f6867312 | 957 | // Header mode. Async protocols don't necessarily have headers, |
mjr | 77:0b96f6867312 | 958 | // but some (e.g., Lutron) do start all codes with a distinctively |
mjr | 77:0b96f6867312 | 959 | // long series of bits. If the subclass defines a header space, |
mjr | 77:0b96f6867312 | 960 | // apply it to sense the start of a code. |
mjr | 77:0b96f6867312 | 961 | if ((tRef = this->tHeaderMark()) == 0) |
mjr | 77:0b96f6867312 | 962 | goto s2; |
mjr | 77:0b96f6867312 | 963 | |
mjr | 77:0b96f6867312 | 964 | // we have a defined header mark - make sure this is long enough |
mjr | 77:0b96f6867312 | 965 | tRef2 = tBit(); |
mjr | 77:0b96f6867312 | 966 | if (mark && inRangeOrAbove(t, tRef, tRef2)) |
mjr | 77:0b96f6867312 | 967 | { |
mjr | 77:0b96f6867312 | 968 | // deduct the header time |
mjr | 77:0b96f6867312 | 969 | t = t > tRef ? t - tRef : 0; |
mjr | 77:0b96f6867312 | 970 | |
mjr | 77:0b96f6867312 | 971 | // if that leaves us with a single bit time or better, |
mjr | 77:0b96f6867312 | 972 | // treat it as the first data bit |
mjr | 77:0b96f6867312 | 973 | if (inRangeOrAbove(t, tRef2, tRef2)) |
mjr | 77:0b96f6867312 | 974 | goto s2; |
mjr | 77:0b96f6867312 | 975 | |
mjr | 77:0b96f6867312 | 976 | // that consumes the whole mark, so just switch to data |
mjr | 77:0b96f6867312 | 977 | // mode starting with the next space |
mjr | 77:0b96f6867312 | 978 | this->rxState = 2; |
mjr | 77:0b96f6867312 | 979 | break; |
mjr | 77:0b96f6867312 | 980 | } |
mjr | 77:0b96f6867312 | 981 | |
mjr | 77:0b96f6867312 | 982 | // doesn't look like the start of a code; back to gap mode |
mjr | 77:0b96f6867312 | 983 | goto s0; |
mjr | 77:0b96f6867312 | 984 | |
mjr | 77:0b96f6867312 | 985 | case 2: |
mjr | 77:0b96f6867312 | 986 | s2: |
mjr | 77:0b96f6867312 | 987 | // Enter data mode |
mjr | 77:0b96f6867312 | 988 | this->rxReset(); |
mjr | 77:0b96f6867312 | 989 | goto s3; |
mjr | 77:0b96f6867312 | 990 | |
mjr | 77:0b96f6867312 | 991 | case 3: |
mjr | 77:0b96f6867312 | 992 | s3: |
mjr | 77:0b96f6867312 | 993 | // Data mode. Process the mark or space as a number of bits. |
mjr | 77:0b96f6867312 | 994 | { |
mjr | 77:0b96f6867312 | 995 | // figure how many bits this symbol represents |
mjr | 77:0b96f6867312 | 996 | int tb = tBit(); |
mjr | 77:0b96f6867312 | 997 | int n = (t + tb/2)/tb; |
mjr | 77:0b96f6867312 | 998 | |
mjr | 77:0b96f6867312 | 999 | // figure how many bits remain in the code |
mjr | 77:0b96f6867312 | 1000 | int rem = this->nbits() - this->bit; |
mjr | 77:0b96f6867312 | 1001 | |
mjr | 77:0b96f6867312 | 1002 | // check to see if this symbol overflows the bits remaining |
mjr | 77:0b96f6867312 | 1003 | if (n > rem) |
mjr | 77:0b96f6867312 | 1004 | { |
mjr | 77:0b96f6867312 | 1005 | // marks simply can't exceed the bits remaining |
mjr | 77:0b96f6867312 | 1006 | if (mark) |
mjr | 77:0b96f6867312 | 1007 | goto s0; |
mjr | 77:0b96f6867312 | 1008 | |
mjr | 77:0b96f6867312 | 1009 | // Spaces can exceed the remaining bits, since we can |
mjr | 77:0b96f6867312 | 1010 | // have a string of 0 bits followed by a gap between |
mjr | 77:0b96f6867312 | 1011 | // codes. Use up the remaining bits as 0's, and apply |
mjr | 77:0b96f6867312 | 1012 | // the balance as a gap. |
mjr | 77:0b96f6867312 | 1013 | this->bit += rem; |
mjr | 77:0b96f6867312 | 1014 | t -= rem*tb; |
mjr | 77:0b96f6867312 | 1015 | goto s4; |
mjr | 77:0b96f6867312 | 1016 | } |
mjr | 77:0b96f6867312 | 1017 | |
mjr | 77:0b96f6867312 | 1018 | // check if it exceeds the code's maximum mark length |
mjr | 77:0b96f6867312 | 1019 | if (mark && t > maxMark()) |
mjr | 77:0b96f6867312 | 1020 | goto s0; |
mjr | 77:0b96f6867312 | 1021 | |
mjr | 77:0b96f6867312 | 1022 | // Make sure that it actually looks like an integral |
mjr | 77:0b96f6867312 | 1023 | // number of bits. If it's not, take it as a bad code. |
mjr | 77:0b96f6867312 | 1024 | if (!inRange(t, n*tb, tb)) |
mjr | 77:0b96f6867312 | 1025 | goto s0; |
mjr | 77:0b96f6867312 | 1026 | |
mjr | 77:0b96f6867312 | 1027 | // Add the bits |
mjr | 77:0b96f6867312 | 1028 | if (mark) |
mjr | 77:0b96f6867312 | 1029 | this->setBits(n); |
mjr | 77:0b96f6867312 | 1030 | else |
mjr | 77:0b96f6867312 | 1031 | this->bit += n; |
mjr | 77:0b96f6867312 | 1032 | |
mjr | 77:0b96f6867312 | 1033 | // we've consumed the whole interval as bits |
mjr | 77:0b96f6867312 | 1034 | t = 0; |
mjr | 77:0b96f6867312 | 1035 | |
mjr | 77:0b96f6867312 | 1036 | // if that's enough bits, we have a decode |
mjr | 77:0b96f6867312 | 1037 | if (this->bit == this->nbits()) |
mjr | 77:0b96f6867312 | 1038 | goto s4; |
mjr | 77:0b96f6867312 | 1039 | |
mjr | 77:0b96f6867312 | 1040 | // stay in data mode |
mjr | 77:0b96f6867312 | 1041 | this->rxState = 3; |
mjr | 77:0b96f6867312 | 1042 | } |
mjr | 77:0b96f6867312 | 1043 | break; |
mjr | 77:0b96f6867312 | 1044 | |
mjr | 77:0b96f6867312 | 1045 | case 4: |
mjr | 77:0b96f6867312 | 1046 | s4: |
mjr | 77:0b96f6867312 | 1047 | // done with the code - close it out and start over |
mjr | 77:0b96f6867312 | 1048 | this->rxClose(receiver, false); |
mjr | 77:0b96f6867312 | 1049 | goto s0; |
mjr | 77:0b96f6867312 | 1050 | } |
mjr | 77:0b96f6867312 | 1051 | } |
mjr | 77:0b96f6867312 | 1052 | |
mjr | 77:0b96f6867312 | 1053 | // send data |
mjr | 77:0b96f6867312 | 1054 | virtual int txDataStep(IRTXState *state) |
mjr | 77:0b96f6867312 | 1055 | { |
mjr | 77:0b96f6867312 | 1056 | // get the next bit |
mjr | 77:0b96f6867312 | 1057 | int b = this->getBit(state); |
mjr | 77:0b96f6867312 | 1058 | state->bit++; |
mjr | 77:0b96f6867312 | 1059 | |
mjr | 77:0b96f6867312 | 1060 | // count how many consecutive matching bits follow |
mjr | 77:0b96f6867312 | 1061 | int n = 1; |
mjr | 77:0b96f6867312 | 1062 | int nbits = state->nbits; |
mjr | 77:0b96f6867312 | 1063 | for ( ; state->bit < nbits && this->getBit(state) == b ; |
mjr | 77:0b96f6867312 | 1064 | ++n, ++state->bit) ; |
mjr | 77:0b96f6867312 | 1065 | |
mjr | 77:0b96f6867312 | 1066 | // if we're out of bits, advance to the next step |
mjr | 77:0b96f6867312 | 1067 | if (state->bit >= nbits) |
mjr | 77:0b96f6867312 | 1068 | ++state->step; |
mjr | 77:0b96f6867312 | 1069 | |
mjr | 77:0b96f6867312 | 1070 | // 0 bits are IR OFF and 1 bits are IR ON |
mjr | 77:0b96f6867312 | 1071 | state->pin->write(b ? this->pwmDutyCycle() : 0); |
mjr | 77:0b96f6867312 | 1072 | |
mjr | 77:0b96f6867312 | 1073 | // stay on for the number of bits times the time per bit |
mjr | 77:0b96f6867312 | 1074 | return n * this->tBit(); |
mjr | 77:0b96f6867312 | 1075 | } |
mjr | 77:0b96f6867312 | 1076 | }; |
mjr | 77:0b96f6867312 | 1077 | |
mjr | 77:0b96f6867312 | 1078 | |
mjr | 77:0b96f6867312 | 1079 | // ----------------------------------------------------------------------- |
mjr | 77:0b96f6867312 | 1080 | // |
mjr | 77:0b96f6867312 | 1081 | // Space-length encoding |
mjr | 77:0b96f6867312 | 1082 | // |
mjr | 77:0b96f6867312 | 1083 | // This type of encoding uses the lengths of the spaces to encode the bit |
mjr | 77:0b96f6867312 | 1084 | // values. Marks are all the same length, and spaces come in two lengths, |
mjr | 77:0b96f6867312 | 1085 | // short and long, usually T and 2T for a vendor-specific time T (typically |
mjr | 77:0b96f6867312 | 1086 | // on the order of 500us). The short space encodes 0 and the long space |
mjr | 77:0b96f6867312 | 1087 | // encodes 1, or vice versa. |
mjr | 77:0b96f6867312 | 1088 | // |
mjr | 77:0b96f6867312 | 1089 | // The widely used NEC protocol is a space-length encoding, and in practice |
mjr | 77:0b96f6867312 | 1090 | // it seems that most of the ad hoc proprietary protocols are also space- |
mjr | 77:0b96f6867312 | 1091 | // length encodings, mostly with similar parameters to NEC. |
mjr | 77:0b96f6867312 | 1092 | // |
mjr | 77:0b96f6867312 | 1093 | template<class CodeType> class IRPSpaceLength: public IRPWithCode<CodeType> |
mjr | 77:0b96f6867312 | 1094 | { |
mjr | 77:0b96f6867312 | 1095 | public: |
mjr | 77:0b96f6867312 | 1096 | IRPSpaceLength() { } |
mjr | 77:0b96f6867312 | 1097 | |
mjr | 77:0b96f6867312 | 1098 | // mark length, in microseconds |
mjr | 77:0b96f6867312 | 1099 | virtual int tMark() const = 0; |
mjr | 77:0b96f6867312 | 1100 | |
mjr | 77:0b96f6867312 | 1101 | // 0 and 1 bit space lengths, in microseconds |
mjr | 77:0b96f6867312 | 1102 | virtual int tZero() const = 0; |
mjr | 77:0b96f6867312 | 1103 | virtual int tOne() const = 0; |
mjr | 77:0b96f6867312 | 1104 | |
mjr | 77:0b96f6867312 | 1105 | // Space-length codings almost always need a mark after the last |
mjr | 77:0b96f6867312 | 1106 | // bit, since otherwise the last bit's space (the significant part) |
mjr | 77:0b96f6867312 | 1107 | // would just flow into the gap that follows. |
mjr | 77:0b96f6867312 | 1108 | virtual uint32_t tStopMark() const { return tMark(); } |
mjr | 77:0b96f6867312 | 1109 | |
mjr | 77:0b96f6867312 | 1110 | // process a mark |
mjr | 77:0b96f6867312 | 1111 | virtual bool rxMark(IRRecvProIfc *receiver, uint32_t t) |
mjr | 77:0b96f6867312 | 1112 | { |
mjr | 77:0b96f6867312 | 1113 | // marks simply delimit spaces in this protocol and thus |
mjr | 77:0b96f6867312 | 1114 | // carry no bit information |
mjr | 77:0b96f6867312 | 1115 | if (inRange(t, tMark())) |
mjr | 77:0b96f6867312 | 1116 | return true; |
mjr | 77:0b96f6867312 | 1117 | else |
mjr | 77:0b96f6867312 | 1118 | return false; |
mjr | 77:0b96f6867312 | 1119 | } |
mjr | 77:0b96f6867312 | 1120 | |
mjr | 77:0b96f6867312 | 1121 | // process a space |
mjr | 77:0b96f6867312 | 1122 | virtual bool rxSpace(IRRecvProIfc *receiver, uint32_t t) |
mjr | 77:0b96f6867312 | 1123 | { |
mjr | 77:0b96f6867312 | 1124 | // a short space represents a '0' bit, a long space is a '1' |
mjr | 77:0b96f6867312 | 1125 | if (inRange(t, tZero())) |
mjr | 77:0b96f6867312 | 1126 | return this->bit++, true; |
mjr | 77:0b96f6867312 | 1127 | else if (inRange(t, tOne())) |
mjr | 77:0b96f6867312 | 1128 | return this->setBit(), true; |
mjr | 77:0b96f6867312 | 1129 | else |
mjr | 77:0b96f6867312 | 1130 | return false; |
mjr | 77:0b96f6867312 | 1131 | } |
mjr | 77:0b96f6867312 | 1132 | |
mjr | 77:0b96f6867312 | 1133 | // continue a transmission |
mjr | 77:0b96f6867312 | 1134 | virtual int txDataStep(IRTXState *state) |
mjr | 77:0b96f6867312 | 1135 | { |
mjr | 77:0b96f6867312 | 1136 | // Data section. |
mjr | 77:0b96f6867312 | 1137 | if (state->bitstep == 0) |
mjr | 77:0b96f6867312 | 1138 | { |
mjr | 77:0b96f6867312 | 1139 | // mark - these are fixed length |
mjr | 77:0b96f6867312 | 1140 | state->pin->write(this->pwmDutyCycle()); |
mjr | 77:0b96f6867312 | 1141 | state->bitstep = 1; |
mjr | 77:0b96f6867312 | 1142 | return tMark(); |
mjr | 77:0b96f6867312 | 1143 | } |
mjr | 77:0b96f6867312 | 1144 | else |
mjr | 77:0b96f6867312 | 1145 | { |
mjr | 77:0b96f6867312 | 1146 | // space - these are variable length according to the data |
mjr | 77:0b96f6867312 | 1147 | state->pin->write(0); |
mjr | 77:0b96f6867312 | 1148 | int t = this->getBit(state) ? tOne() : tZero(); |
mjr | 77:0b96f6867312 | 1149 | state->bitstep = 0; |
mjr | 77:0b96f6867312 | 1150 | |
mjr | 77:0b96f6867312 | 1151 | // advance to the next bit; stop if we're done |
mjr | 77:0b96f6867312 | 1152 | if (++state->bit >= state->nbits) |
mjr | 77:0b96f6867312 | 1153 | ++state->step; |
mjr | 77:0b96f6867312 | 1154 | |
mjr | 77:0b96f6867312 | 1155 | // return the space time |
mjr | 77:0b96f6867312 | 1156 | return t; |
mjr | 77:0b96f6867312 | 1157 | } |
mjr | 77:0b96f6867312 | 1158 | } |
mjr | 77:0b96f6867312 | 1159 | |
mjr | 77:0b96f6867312 | 1160 | }; |
mjr | 77:0b96f6867312 | 1161 | |
mjr | 77:0b96f6867312 | 1162 | |
mjr | 77:0b96f6867312 | 1163 | // ----------------------------------------------------------------------- |
mjr | 77:0b96f6867312 | 1164 | // |
mjr | 77:0b96f6867312 | 1165 | // Mark-length encoding |
mjr | 77:0b96f6867312 | 1166 | // |
mjr | 77:0b96f6867312 | 1167 | // This is the inverse of space-length coding. In this scheme, the bit |
mjr | 77:0b96f6867312 | 1168 | // values are encoded in the mark length. Spaces are fixed length, and |
mjr | 77:0b96f6867312 | 1169 | // marks come in short (0) and long (1) lengths, usually of time T and 2T |
mjr | 77:0b96f6867312 | 1170 | // for a protocol-specific time T. |
mjr | 77:0b96f6867312 | 1171 | // |
mjr | 77:0b96f6867312 | 1172 | // Sony uses this type of encoding. |
mjr | 77:0b96f6867312 | 1173 | template<class CodeType> class IRPMarkLength: public IRPWithCode<CodeType> |
mjr | 77:0b96f6867312 | 1174 | { |
mjr | 77:0b96f6867312 | 1175 | public: |
mjr | 77:0b96f6867312 | 1176 | IRPMarkLength() { } |
mjr | 77:0b96f6867312 | 1177 | |
mjr | 77:0b96f6867312 | 1178 | // space length, in microseconds |
mjr | 77:0b96f6867312 | 1179 | virtual int tSpace() const = 0; |
mjr | 77:0b96f6867312 | 1180 | |
mjr | 77:0b96f6867312 | 1181 | // 0 and 1 bit mark lengths, in microseconds |
mjr | 77:0b96f6867312 | 1182 | virtual int tZero() const = 0; |
mjr | 77:0b96f6867312 | 1183 | virtual int tOne() const = 0; |
mjr | 77:0b96f6867312 | 1184 | |
mjr | 77:0b96f6867312 | 1185 | // process a mark |
mjr | 77:0b96f6867312 | 1186 | virtual bool rxMark(IRRecvProIfc *receiver, uint32_t t) |
mjr | 77:0b96f6867312 | 1187 | { |
mjr | 77:0b96f6867312 | 1188 | // a short mark represents a '0' bit, a long space is a '1' |
mjr | 77:0b96f6867312 | 1189 | if (inRange(t, tZero())) |
mjr | 77:0b96f6867312 | 1190 | this->bit++; |
mjr | 77:0b96f6867312 | 1191 | else if (inRange(t, tOne())) |
mjr | 77:0b96f6867312 | 1192 | this->setBit(); |
mjr | 77:0b96f6867312 | 1193 | else |
mjr | 77:0b96f6867312 | 1194 | return false; |
mjr | 77:0b96f6867312 | 1195 | return true; |
mjr | 77:0b96f6867312 | 1196 | } |
mjr | 77:0b96f6867312 | 1197 | |
mjr | 77:0b96f6867312 | 1198 | // process a space |
mjr | 77:0b96f6867312 | 1199 | virtual bool rxSpace(IRRecvProIfc *receiver, uint32_t t) |
mjr | 77:0b96f6867312 | 1200 | { |
mjr | 77:0b96f6867312 | 1201 | // spaces simply delimit marks in this protocol and carry |
mjr | 77:0b96f6867312 | 1202 | // no bit information of their own |
mjr | 77:0b96f6867312 | 1203 | return inRange(t, tSpace()); |
mjr | 77:0b96f6867312 | 1204 | } |
mjr | 77:0b96f6867312 | 1205 | |
mjr | 77:0b96f6867312 | 1206 | // continue a transmission |
mjr | 77:0b96f6867312 | 1207 | virtual int txDataStep(IRTXState *state) |
mjr | 77:0b96f6867312 | 1208 | { |
mjr | 77:0b96f6867312 | 1209 | // check if we're on a mark (step 0) or space (step 1) |
mjr | 77:0b96f6867312 | 1210 | if (state->bitstep == 0) |
mjr | 77:0b96f6867312 | 1211 | { |
mjr | 77:0b96f6867312 | 1212 | // space - these are variable length according to the data |
mjr | 77:0b96f6867312 | 1213 | state->pin->write(this->pwmDutyCycle()); |
mjr | 77:0b96f6867312 | 1214 | int t = this->getBit(state) ? tOne() : tZero(); |
mjr | 77:0b96f6867312 | 1215 | state->bitstep = 1; |
mjr | 77:0b96f6867312 | 1216 | |
mjr | 77:0b96f6867312 | 1217 | // return the mark time |
mjr | 77:0b96f6867312 | 1218 | return t; |
mjr | 77:0b96f6867312 | 1219 | } |
mjr | 77:0b96f6867312 | 1220 | else |
mjr | 77:0b96f6867312 | 1221 | { |
mjr | 77:0b96f6867312 | 1222 | // Space - fixed length |
mjr | 77:0b96f6867312 | 1223 | state->pin->write(0); |
mjr | 77:0b96f6867312 | 1224 | state->bitstep = 0; |
mjr | 77:0b96f6867312 | 1225 | |
mjr | 77:0b96f6867312 | 1226 | // advance to the next bit; stop if we're done |
mjr | 77:0b96f6867312 | 1227 | if (++state->bit >= state->nbits) |
mjr | 77:0b96f6867312 | 1228 | state->step = 3; |
mjr | 77:0b96f6867312 | 1229 | return tSpace(); |
mjr | 77:0b96f6867312 | 1230 | } |
mjr | 77:0b96f6867312 | 1231 | } |
mjr | 77:0b96f6867312 | 1232 | |
mjr | 77:0b96f6867312 | 1233 | }; |
mjr | 77:0b96f6867312 | 1234 | |
mjr | 77:0b96f6867312 | 1235 | |
mjr | 77:0b96f6867312 | 1236 | // ----------------------------------------------------------------------- |
mjr | 77:0b96f6867312 | 1237 | // |
mjr | 77:0b96f6867312 | 1238 | // Manchester coding |
mjr | 77:0b96f6867312 | 1239 | // |
mjr | 77:0b96f6867312 | 1240 | // This type of coding uses a fixed time per bit, and encodes the bit |
mjr | 77:0b96f6867312 | 1241 | // value in a mark/space or space/mark transition within each bit's |
mjr | 77:0b96f6867312 | 1242 | // time window. |
mjr | 77:0b96f6867312 | 1243 | // |
mjr | 77:0b96f6867312 | 1244 | // The decoding process is a little tricky to grap when you're looking |
mjr | 77:0b96f6867312 | 1245 | // at just the raw data, because the raw data renders things in terms of |
mjr | 77:0b96f6867312 | 1246 | // monolithic marks and spaces of different lengths, whereas the coding |
mjr | 77:0b96f6867312 | 1247 | // divides the time axis into even chunks and looks at what's going on |
mjr | 77:0b96f6867312 | 1248 | // in each chunk. In terms of the raw data, we can think of it this way. |
mjr | 77:0b96f6867312 | 1249 | // Because every bit time window has a transition (mark/space or space/mark) |
mjr | 77:0b96f6867312 | 1250 | // in the middle of it, there has to be at least one transition per window. |
mjr | 77:0b96f6867312 | 1251 | // There can also be a transition between each window, or not, as needed |
mjr | 77:0b96f6867312 | 1252 | // to get the transmitter into the right initial state for the next bit. |
mjr | 77:0b96f6867312 | 1253 | // This means that each mark and each space is either T or 2T long, where |
mjr | 77:0b96f6867312 | 1254 | // T is the half the bit window time. So we can simply count these units. |
mjr | 77:0b96f6867312 | 1255 | // If we see a mark or space of approximate length T, we count one unit; |
mjr | 77:0b96f6867312 | 1256 | // if the length is around 2T, we count two units. On each ODD count, we |
mjr | 77:0b96f6867312 | 1257 | // look at the state just before the count. If we were in a space just |
mjr | 77:0b96f6867312 | 1258 | // before the count, the bit is a 1; if it was a mark, the bit is a 0. |
mjr | 77:0b96f6867312 | 1259 | // |
mjr | 77:0b96f6867312 | 1260 | // Manchester coding is used in the Philips RC5 and RC6 protocols, which |
mjr | 77:0b96f6867312 | 1261 | // are in turn used by most other European CE companies. |
mjr | 77:0b96f6867312 | 1262 | template<class CodeType> class IRPManchester: public IRPWithCode<CodeType> |
mjr | 77:0b96f6867312 | 1263 | { |
mjr | 77:0b96f6867312 | 1264 | public: |
mjr | 77:0b96f6867312 | 1265 | IRPManchester() { } |
mjr | 77:0b96f6867312 | 1266 | |
mjr | 77:0b96f6867312 | 1267 | // Half-bit time. This is half of the time interval of one bit, |
mjr | 77:0b96f6867312 | 1268 | // so it's equal to the time on each side of the mark/space or |
mjr | 77:0b96f6867312 | 1269 | // space/mark transition in the middle of each bit. |
mjr | 77:0b96f6867312 | 1270 | virtual int tHalfBit(int bit) const = 0; |
mjr | 77:0b96f6867312 | 1271 | |
mjr | 77:0b96f6867312 | 1272 | // Bit value (0 or 1) of a space-to-mark transition. A mark-to-space |
mjr | 77:0b96f6867312 | 1273 | // transition always has the opposite sense. |
mjr | 77:0b96f6867312 | 1274 | virtual int spaceToMarkBit() const { return 1; } |
mjr | 77:0b96f6867312 | 1275 | inline int markToSpaceBit() const { return !spaceToMarkBit(); } |
mjr | 77:0b96f6867312 | 1276 | |
mjr | 77:0b96f6867312 | 1277 | // reset the decoder state |
mjr | 77:0b96f6867312 | 1278 | virtual void rxReset() |
mjr | 77:0b96f6867312 | 1279 | { |
mjr | 77:0b96f6867312 | 1280 | IRPWithCode<CodeType>::rxReset(); |
mjr | 77:0b96f6867312 | 1281 | halfBitPos = 0; |
mjr | 77:0b96f6867312 | 1282 | } |
mjr | 77:0b96f6867312 | 1283 | |
mjr | 77:0b96f6867312 | 1284 | // process a mark |
mjr | 77:0b96f6867312 | 1285 | virtual bool rxMark(IRRecvProIfc *receiver, uint32_t t) |
mjr | 77:0b96f6867312 | 1286 | { |
mjr | 77:0b96f6867312 | 1287 | // transitioning from mark to space, so this is a |
mjr | 77:0b96f6867312 | 1288 | return processTransition(t, spaceToMarkBit()); |
mjr | 77:0b96f6867312 | 1289 | } |
mjr | 77:0b96f6867312 | 1290 | |
mjr | 77:0b96f6867312 | 1291 | // process a space |
mjr | 77:0b96f6867312 | 1292 | virtual bool rxSpace(IRRecvProIfc *receiver, uint32_t t) |
mjr | 77:0b96f6867312 | 1293 | { |
mjr | 77:0b96f6867312 | 1294 | return (processTransition(t, markToSpaceBit())); |
mjr | 77:0b96f6867312 | 1295 | } |
mjr | 77:0b96f6867312 | 1296 | |
mjr | 77:0b96f6867312 | 1297 | // Process a space/mark or mark/space transition. Returns true on |
mjr | 77:0b96f6867312 | 1298 | // success, false on failure. |
mjr | 77:0b96f6867312 | 1299 | bool processTransition(uint32_t t, int bitval) |
mjr | 77:0b96f6867312 | 1300 | { |
mjr | 77:0b96f6867312 | 1301 | // If this time is close to zero, ignore it. |
mjr | 77:0b96f6867312 | 1302 | int thb = tHalfBit(this->bit); |
mjr | 77:0b96f6867312 | 1303 | if (t < ((thb*toleranceShl8) >> 8)) |
mjr | 77:0b96f6867312 | 1304 | return true; |
mjr | 77:0b96f6867312 | 1305 | |
mjr | 77:0b96f6867312 | 1306 | // If the current time is the middle of a bit, the transition |
mjr | 77:0b96f6867312 | 1307 | // specifies a bit value, so set the bit. Transitions between |
mjr | 77:0b96f6867312 | 1308 | // bits are just clocking. |
mjr | 77:0b96f6867312 | 1309 | if (halfBitPos == 1) |
mjr | 77:0b96f6867312 | 1310 | { |
mjr | 77:0b96f6867312 | 1311 | if (bitval) |
mjr | 77:0b96f6867312 | 1312 | { |
mjr | 77:0b96f6867312 | 1313 | // set the bit, but keep the bit counter where it is, since |
mjr | 77:0b96f6867312 | 1314 | // we manage it ourselves |
mjr | 77:0b96f6867312 | 1315 | this->setBit(); |
mjr | 77:0b96f6867312 | 1316 | this->bit--; |
mjr | 77:0b96f6867312 | 1317 | } |
mjr | 77:0b96f6867312 | 1318 | } |
mjr | 77:0b96f6867312 | 1319 | |
mjr | 77:0b96f6867312 | 1320 | // Advance by the time interval. Check that we have at least one |
mjr | 77:0b96f6867312 | 1321 | // half-bit interval to work with. |
mjr | 77:0b96f6867312 | 1322 | if (t < ((thb * (256 - toleranceShl8)) >> 8)) |
mjr | 77:0b96f6867312 | 1323 | return false; |
mjr | 77:0b96f6867312 | 1324 | |
mjr | 77:0b96f6867312 | 1325 | // If we're at a half-bit position, start by advancing to the next |
mjr | 77:0b96f6867312 | 1326 | // bit boundary. |
mjr | 77:0b96f6867312 | 1327 | if (halfBitPos) |
mjr | 77:0b96f6867312 | 1328 | { |
mjr | 77:0b96f6867312 | 1329 | // deduct the half-bit time |
mjr | 77:0b96f6867312 | 1330 | t = (t > thb ? t - thb : 0); |
mjr | 77:0b96f6867312 | 1331 | |
mjr | 77:0b96f6867312 | 1332 | // advance our position counters to the next bit boundary |
mjr | 77:0b96f6867312 | 1333 | halfBitPos = 0; |
mjr | 77:0b96f6867312 | 1334 | this->bit++; |
mjr | 77:0b96f6867312 | 1335 | |
mjr | 77:0b96f6867312 | 1336 | // Some subprotocols (e.g., RC6) have variable bit timing, |
mjr | 77:0b96f6867312 | 1337 | // so the timing for this bit might be different than for |
mjr | 77:0b96f6867312 | 1338 | // the previous bit. Re-fetch the time. |
mjr | 77:0b96f6867312 | 1339 | thb = tHalfBit(this->bit); |
mjr | 77:0b96f6867312 | 1340 | } |
mjr | 77:0b96f6867312 | 1341 | |
mjr | 77:0b96f6867312 | 1342 | // If we have another half-interval left to process, advance |
mjr | 77:0b96f6867312 | 1343 | // to the middle of the current bit. |
mjr | 77:0b96f6867312 | 1344 | if (t < ((thb * toleranceShl8) >> 8)) |
mjr | 77:0b96f6867312 | 1345 | { |
mjr | 77:0b96f6867312 | 1346 | // we already used up the symbol time, so we're done |
mjr | 77:0b96f6867312 | 1347 | return true; |
mjr | 77:0b96f6867312 | 1348 | } |
mjr | 77:0b96f6867312 | 1349 | else if (inRange(t, thb)) |
mjr | 77:0b96f6867312 | 1350 | { |
mjr | 77:0b96f6867312 | 1351 | // we have another half-bit time to use, so advance to |
mjr | 77:0b96f6867312 | 1352 | // the middle of the bit |
mjr | 77:0b96f6867312 | 1353 | halfBitPos = true; |
mjr | 77:0b96f6867312 | 1354 | return true; |
mjr | 77:0b96f6867312 | 1355 | } |
mjr | 77:0b96f6867312 | 1356 | else |
mjr | 77:0b96f6867312 | 1357 | { |
mjr | 77:0b96f6867312 | 1358 | // The time remaining is wrong, so terminate decoding. |
mjr | 77:0b96f6867312 | 1359 | // Note that this could simply be because we've reached |
mjr | 77:0b96f6867312 | 1360 | // the gap at the end of the code word, in which case we'll |
mjr | 77:0b96f6867312 | 1361 | // already have all of the bits stored and will generate |
mjr | 77:0b96f6867312 | 1362 | // the finished code value. |
mjr | 77:0b96f6867312 | 1363 | return false; |
mjr | 77:0b96f6867312 | 1364 | } |
mjr | 77:0b96f6867312 | 1365 | } |
mjr | 77:0b96f6867312 | 1366 | |
mjr | 77:0b96f6867312 | 1367 | virtual int txDataStep(IRTXState *state) |
mjr | 77:0b96f6867312 | 1368 | { |
mjr | 77:0b96f6867312 | 1369 | // Get the current bit |
mjr | 77:0b96f6867312 | 1370 | int b = this->getBit(state); |
mjr | 77:0b96f6867312 | 1371 | |
mjr | 77:0b96f6867312 | 1372 | // Determine if this bit uses a space-to-mark or mark-to-space |
mjr | 77:0b96f6867312 | 1373 | // transition. It uses a space-to-mark transition if it matches |
mjr | 77:0b96f6867312 | 1374 | // the space-to-mark bit. |
mjr | 77:0b96f6867312 | 1375 | int stm = (b == spaceToMarkBit()); |
mjr | 77:0b96f6867312 | 1376 | |
mjr | 77:0b96f6867312 | 1377 | // Check to see if we're at the start or middle of the bit |
mjr | 77:0b96f6867312 | 1378 | if (state->bitstep == 0) |
mjr | 77:0b96f6867312 | 1379 | { |
mjr | 77:0b96f6867312 | 1380 | // Start of the current bit. Set the level for the first |
mjr | 77:0b96f6867312 | 1381 | // half of the bit. If we're doing a space-to-mark bit, |
mjr | 77:0b96f6867312 | 1382 | // the first half is a space, otherwise it's a mark. |
mjr | 77:0b96f6867312 | 1383 | state->pin->write(stm ? 0 : this->pwmDutyCycle()); |
mjr | 77:0b96f6867312 | 1384 | |
mjr | 77:0b96f6867312 | 1385 | // leave this on for a half-bit time to get to the |
mjr | 77:0b96f6867312 | 1386 | // middle of the bit |
mjr | 77:0b96f6867312 | 1387 | state->bitstep = 1; |
mjr | 77:0b96f6867312 | 1388 | return tHalfBit(state->bit); |
mjr | 77:0b96f6867312 | 1389 | } |
mjr | 77:0b96f6867312 | 1390 | else |
mjr | 77:0b96f6867312 | 1391 | { |
mjr | 77:0b96f6867312 | 1392 | // Middle of the current bit. Set the level for the second |
mjr | 77:0b96f6867312 | 1393 | // half of the bit. If we're in a space-to-mark bit, the |
mjr | 77:0b96f6867312 | 1394 | // second half is the mark, otherwise it's the space. |
mjr | 77:0b96f6867312 | 1395 | state->pin->write(stm ? this->pwmDutyCycle() : 0); |
mjr | 77:0b96f6867312 | 1396 | |
mjr | 77:0b96f6867312 | 1397 | // figure the time to the start of the next bit |
mjr | 77:0b96f6867312 | 1398 | int t = tHalfBit(state->bit); |
mjr | 77:0b96f6867312 | 1399 | |
mjr | 77:0b96f6867312 | 1400 | // advance to the start of the next bit |
mjr | 77:0b96f6867312 | 1401 | state->bit++; |
mjr | 77:0b96f6867312 | 1402 | state->bitstep = 0; |
mjr | 77:0b96f6867312 | 1403 | |
mjr | 77:0b96f6867312 | 1404 | // If the next bit is the inverse of the current bit, it will |
mjr | 77:0b96f6867312 | 1405 | // lead in with the same level we're going out with. That |
mjr | 77:0b96f6867312 | 1406 | // means we can go straight to the middle of the next bit |
mjr | 77:0b96f6867312 | 1407 | // without another interrupt. |
mjr | 77:0b96f6867312 | 1408 | if (state->bit < state->nbits && this->getBit(state) != b) |
mjr | 77:0b96f6867312 | 1409 | { |
mjr | 77:0b96f6867312 | 1410 | // proceed to the middle of the next bit |
mjr | 77:0b96f6867312 | 1411 | state->bitstep = 1; |
mjr | 77:0b96f6867312 | 1412 | |
mjr | 77:0b96f6867312 | 1413 | // add the half-bit time |
mjr | 77:0b96f6867312 | 1414 | t += tHalfBit(state->bit); |
mjr | 77:0b96f6867312 | 1415 | } |
mjr | 77:0b96f6867312 | 1416 | |
mjr | 77:0b96f6867312 | 1417 | // if this was the last bit, advance to the next state |
mjr | 77:0b96f6867312 | 1418 | if (state->bit >= state->nbits) |
mjr | 77:0b96f6867312 | 1419 | state->step++; |
mjr | 77:0b96f6867312 | 1420 | |
mjr | 77:0b96f6867312 | 1421 | // return the time to the next transition |
mjr | 77:0b96f6867312 | 1422 | return t; |
mjr | 77:0b96f6867312 | 1423 | } |
mjr | 77:0b96f6867312 | 1424 | } |
mjr | 77:0b96f6867312 | 1425 | |
mjr | 77:0b96f6867312 | 1426 | // Current half-bit position. If the last transition was on the |
mjr | 77:0b96f6867312 | 1427 | // border between two bits, this is 0. If it was in the middle |
mjr | 77:0b96f6867312 | 1428 | // of a bit, this is 1. |
mjr | 77:0b96f6867312 | 1429 | uint8_t halfBitPos : 1; |
mjr | 77:0b96f6867312 | 1430 | }; |
mjr | 77:0b96f6867312 | 1431 | |
mjr | 77:0b96f6867312 | 1432 | // ----------------------------------------------------------------------- |
mjr | 77:0b96f6867312 | 1433 | // |
mjr | 77:0b96f6867312 | 1434 | // NEC protocol family. This is one of the more important proprietary |
mjr | 77:0b96f6867312 | 1435 | // protocols, since many CE companies use the standard NEC code or a |
mjr | 77:0b96f6867312 | 1436 | // variation of it. This class handles the following variations on the |
mjr | 77:0b96f6867312 | 1437 | // basic NEC code: |
mjr | 77:0b96f6867312 | 1438 | // |
mjr | 97:fc7727303038 | 1439 | // NEC-32: 32-bit payload, 9000us header mark, 4500us header space |
mjr | 97:fc7727303038 | 1440 | // NEC-32X: 32-bit payload, 4500us header mark, 4500us header space |
mjr | 97:fc7727303038 | 1441 | // NEC-48: 48-bit payload, 9000us header mark, 4500us header space |
mjr | 97:fc7727303038 | 1442 | // Pioneer: NEC-32 with address A0..AF + possible "shift" prefixes |
mjr | 97:fc7727303038 | 1443 | // TCL/Roku: NEC-32 with address EAC7 + doubled code XOR 0x8080 |
mjr | 77:0b96f6867312 | 1444 | // |
mjr | 77:0b96f6867312 | 1445 | // Each of the three NEC-nn protocol types comes in two sub-types: one |
mjr | 77:0b96f6867312 | 1446 | // that uses "ditto" codes for repeated keys, and one that simply sends |
mjr | 77:0b96f6867312 | 1447 | // the same code again on repeats. The ditto code, when used, varies with |
mjr | 77:0b96f6867312 | 1448 | // the main protocol type: |
mjr | 77:0b96f6867312 | 1449 | // |
mjr | 97:fc7727303038 | 1450 | // NEC-32: 9000us mark + 2250us space + 564us mark |
mjr | 97:fc7727303038 | 1451 | // NEC-32x: 4500us mark + 4500us space + one data bit + 564us mark |
mjr | 97:fc7727303038 | 1452 | // NEC-48: 9000us mark + 2250us space + 564us mark |
mjr | 97:fc7727303038 | 1453 | // Pioneer: no dittos |
mjr | 97:fc7727303038 | 1454 | // TCL/Roku: no dittos |
mjr | 77:0b96f6867312 | 1455 | // |
mjr | 77:0b96f6867312 | 1456 | // The NEC-32 and NEC-48 dittos can be detected from the header space |
mjr | 77:0b96f6867312 | 1457 | // length. The NEC-32x dittos can be detected by the one-bit code length. |
mjr | 77:0b96f6867312 | 1458 | // |
mjr | 77:0b96f6867312 | 1459 | // All variations of the NEC code are space-length encodings with 564us |
mjr | 77:0b96f6867312 | 1460 | // marks between bits, 564us '0' spaces, and 3*564us '1' spaces. All |
mjr | 77:0b96f6867312 | 1461 | // variations use a long header mark and a 564us stop mark. With those |
mjr | 77:0b96f6867312 | 1462 | // fixed features, there are three main variations: |
mjr | 77:0b96f6867312 | 1463 | // |
mjr | 77:0b96f6867312 | 1464 | // The bits in the NEC 32-bit codes are structured into four 8-bit fields, |
mjr | 77:0b96f6867312 | 1465 | // with the first bit transmitted in the most significant position: |
mjr | 77:0b96f6867312 | 1466 | // |
mjr | 77:0b96f6867312 | 1467 | // A1 A0 C1 C0 |
mjr | 77:0b96f6867312 | 1468 | // |
mjr | 77:0b96f6867312 | 1469 | // A1 is the high 8 bits of the address, and A0 is the low 8 bits of the |
mjr | 77:0b96f6867312 | 1470 | // address. The address specifies a particular type of device, such as |
mjr | 77:0b96f6867312 | 1471 | // "NEC VCR" or "Vizio TV". These are assigned by NEC. C1 and C0 form the |
mjr | 77:0b96f6867312 | 1472 | // command code, which has a meaning specific to the device type specified |
mjr | 77:0b96f6867312 | 1473 | // by the address field. |
mjr | 77:0b96f6867312 | 1474 | // |
mjr | 77:0b96f6867312 | 1475 | // In the original NEC protocol, the nominal address is in A1, and A0 is |
mjr | 77:0b96f6867312 | 1476 | // the 1's complement of A1 (A0 = ~A1), for error checking. This was removed |
mjr | 77:0b96f6867312 | 1477 | // in a later revision to expand the address space. Most modern equipment |
mjr | 77:0b96f6867312 | 1478 | // uses the newer system, so A0 is typically an independent value in remotes |
mjr | 77:0b96f6867312 | 1479 | // you'll find in use today. |
mjr | 77:0b96f6867312 | 1480 | // |
mjr | 77:0b96f6867312 | 1481 | // In the official version of the protocol, C1 is the nominal command code, |
mjr | 77:0b96f6867312 | 1482 | // and C0 = ~C1, for error checking. However, some other manufacturers who |
mjr | 77:0b96f6867312 | 1483 | // use the basic NEC protocol, notably Yamaha and Onkyo, violate this by using |
mjr | 77:0b96f6867312 | 1484 | // C0 as an independent byte to expand the command space. We therefore don't |
mjr | 77:0b96f6867312 | 1485 | // test for the complemented byte, so that we don't reject codes from devices |
mjr | 77:0b96f6867312 | 1486 | // that treat it as independent. |
mjr | 77:0b96f6867312 | 1487 | // |
mjr | 77:0b96f6867312 | 1488 | // Pioneer uses the NEC protocol with two changes. First, the carrier is |
mjr | 77:0b96f6867312 | 1489 | // 40kHz instead of 38kHz. The TSOP384xx seems to receive the 40kHz signal |
mjr | 77:0b96f6867312 | 1490 | // reliably, so the frequency doesn't matter on receive, but it might matter |
mjr | 77:0b96f6867312 | 1491 | // on transmit if the target equipment isn't as tolerant. Second, Pioneer |
mjr | 77:0b96f6867312 | 1492 | // sometimes transmits a second code for the same key. In these cases, the |
mjr | 77:0b96f6867312 | 1493 | // first code is a sort of "shift" code (shared among many keys), and the |
mjr | 77:0b96f6867312 | 1494 | // second code has the actual key-specific meaning. To learn or recognize |
mjr | 77:0b96f6867312 | 1495 | // these extended codes, we have to treat the pair of code words as a single |
mjr | 77:0b96f6867312 | 1496 | // command. We sense Pioneer codes based on the address field, and use |
mjr | 77:0b96f6867312 | 1497 | // special handling when we find a Pioneer address code. |
mjr | 77:0b96f6867312 | 1498 | // |
mjr | 97:fc7727303038 | 1499 | // TCL's Roku models (that is, their TVs that contain embedded Roku features) |
mjr | 97:fc7727303038 | 1500 | // use yet another proprietary variant. They use the standard low-level |
mjr | 97:fc7727303038 | 1501 | // protocol elements (PWM freq and bit timing), but they layer a high-level |
mjr | 97:fc7727303038 | 1502 | // protocol variation where every command consists of two 32-bit code words |
mjr | 97:fc7727303038 | 1503 | // in succession. The second code word repeats the first code word, but with |
mjr | 97:fc7727303038 | 1504 | // the last two bytes (the "command field") each XOR'd with 0x80. TCL's codes |
mjr | 97:fc7727303038 | 1505 | // are recognizable by EAC7 in the command field. The repeated code scheme is |
mjr | 97:fc7727303038 | 1506 | // presumably a redundancy check. Dittos aren't used in this scheme. |
mjr | 97:fc7727303038 | 1507 | // |
mjr | 77:0b96f6867312 | 1508 | class IRPNEC: public IRPSpaceLength<uint64_t> |
mjr | 77:0b96f6867312 | 1509 | { |
mjr | 77:0b96f6867312 | 1510 | public: |
mjr | 77:0b96f6867312 | 1511 | // code parameters |
mjr | 77:0b96f6867312 | 1512 | virtual uint32_t minRxGap() const { return 3400; } |
mjr | 77:0b96f6867312 | 1513 | virtual uint32_t txGap(IRTXState *state) const |
mjr | 77:0b96f6867312 | 1514 | { return 108000 - state->txTime.read_us(); } |
mjr | 97:fc7727303038 | 1515 | |
mjr | 97:fc7727303038 | 1516 | // The post-code transmit gap is special for TCL Roku for the gap between |
mjr | 97:fc7727303038 | 1517 | // the first and second half of the code. These appear to use a fixed |
mjr | 97:fc7727303038 | 1518 | // 37842us gap. The receiver interprets the normal NEC inter-code gap |
mjr | 97:fc7727303038 | 1519 | // of (108ms minus code transmit time) as a gap between repeats of the |
mjr | 97:fc7727303038 | 1520 | // code rather than as half-codes. |
mjr | 97:fc7727303038 | 1521 | virtual uint32_t txPostGap(IRTXState *state) const |
mjr | 97:fc7727303038 | 1522 | { |
mjr | 97:fc7727303038 | 1523 | // Check for TCL Roku models on even reps. An even rep is the |
mjr | 97:fc7727303038 | 1524 | // first half of a code pair, so we need to use the shorter |
mjr | 97:fc7727303038 | 1525 | // post-code gap for these. |
mjr | 97:fc7727303038 | 1526 | if (state->protocolId == IRPRO_TCLROKU |
mjr | 97:fc7727303038 | 1527 | && (state->rep == 0 || state->rep == 2)) |
mjr | 97:fc7727303038 | 1528 | return 37842; |
mjr | 97:fc7727303038 | 1529 | |
mjr | 97:fc7727303038 | 1530 | // use the standard NEC timing for others |
mjr | 97:fc7727303038 | 1531 | return txGap(state); |
mjr | 97:fc7727303038 | 1532 | } |
mjr | 97:fc7727303038 | 1533 | |
mjr | 77:0b96f6867312 | 1534 | // space length encoding parameters |
mjr | 77:0b96f6867312 | 1535 | virtual int tMark() const { return 560; } |
mjr | 77:0b96f6867312 | 1536 | virtual int tZero() const { return 560; } |
mjr | 77:0b96f6867312 | 1537 | virtual int tOne() const { return 1680; } |
mjr | 77:0b96f6867312 | 1538 | |
mjr | 77:0b96f6867312 | 1539 | // PWM period is 40kHz for Pioneer, 38kHz for everyone else |
mjr | 77:0b96f6867312 | 1540 | virtual float pwmPeriod(IRTXState *state) const |
mjr | 77:0b96f6867312 | 1541 | { |
mjr | 77:0b96f6867312 | 1542 | return state->protocolId == IRPRO_PIONEER ? 25.0e-6f : 26.31578947e-6f; |
mjr | 77:0b96f6867312 | 1543 | } |
mjr | 77:0b96f6867312 | 1544 | |
mjr | 97:fc7727303038 | 1545 | // For Pioneer, we have to send two codes if we have a shift field. |
mjr | 97:fc7727303038 | 1546 | // For TCL Roku models, we always send two codes. |
mjr | 77:0b96f6867312 | 1547 | virtual int txMinReps(IRTXState *state) const |
mjr | 77:0b96f6867312 | 1548 | { |
mjr | 77:0b96f6867312 | 1549 | if (state->protocolId == IRPRO_PIONEER |
mjr | 77:0b96f6867312 | 1550 | && (state->cmdCode & 0xFFFF0000) != 0) |
mjr | 77:0b96f6867312 | 1551 | return 2; |
mjr | 97:fc7727303038 | 1552 | else if (state->protocolId == IRPRO_TCLROKU) |
mjr | 97:fc7727303038 | 1553 | return 2; |
mjr | 77:0b96f6867312 | 1554 | else |
mjr | 77:0b96f6867312 | 1555 | return 1; |
mjr | 77:0b96f6867312 | 1556 | } |
mjr | 77:0b96f6867312 | 1557 | |
mjr | 77:0b96f6867312 | 1558 | // get the protocol to report for a given data packet bit count |
mjr | 77:0b96f6867312 | 1559 | virtual int necPro(int bits) const = 0; |
mjr | 77:0b96f6867312 | 1560 | |
mjr | 77:0b96f6867312 | 1561 | // close out a received bitstream |
mjr | 77:0b96f6867312 | 1562 | virtual void rxClose(IRRecvProIfc *receiver, bool ditto) |
mjr | 77:0b96f6867312 | 1563 | { |
mjr | 77:0b96f6867312 | 1564 | // Check the bit count. If nbits() says we can accept 48 bits, |
mjr | 77:0b96f6867312 | 1565 | // accept 48 bits, otherwise only accept 32. |
mjr | 77:0b96f6867312 | 1566 | if (bit == 32 || (nbits() >= 48 && bit == 48)) |
mjr | 77:0b96f6867312 | 1567 | { |
mjr | 77:0b96f6867312 | 1568 | uint64_t codeOut; |
mjr | 77:0b96f6867312 | 1569 | if (ditto) |
mjr | 77:0b96f6867312 | 1570 | { |
mjr | 77:0b96f6867312 | 1571 | // report 0 for dittos |
mjr | 77:0b96f6867312 | 1572 | codeOut = 0; |
mjr | 77:0b96f6867312 | 1573 | } |
mjr | 77:0b96f6867312 | 1574 | else |
mjr | 77:0b96f6867312 | 1575 | { |
mjr | 77:0b96f6867312 | 1576 | // Put the bytes in the right order. The bits are LSB-first, but |
mjr | 77:0b96f6867312 | 1577 | // we want the bytes to be ordered within the uint32 as (high to |
mjr | 77:0b96f6867312 | 1578 | // low) A0 A1 C0 C1. |
mjr | 77:0b96f6867312 | 1579 | uint8_t c1 = uint8_t((code >> 24) & 0xff); |
mjr | 77:0b96f6867312 | 1580 | uint8_t c0 = uint8_t((code >> 16) & 0xff); |
mjr | 77:0b96f6867312 | 1581 | uint8_t a1 = uint8_t((code >> 8) & 0xff); |
mjr | 77:0b96f6867312 | 1582 | uint8_t a0 = uint8_t(code & 0xff); |
mjr | 77:0b96f6867312 | 1583 | codeOut = (uint64_t(a0) << 24) | (uint64_t(a1) << 16) |
mjr | 77:0b96f6867312 | 1584 | | (uint64_t(c0) << 8) | uint64_t(c1); |
mjr | 77:0b96f6867312 | 1585 | |
mjr | 77:0b96f6867312 | 1586 | // If it's a 48-bit code, add the additional 16 bits for E0 E1 |
mjr | 77:0b96f6867312 | 1587 | // (the extended command code) at the low end. |
mjr | 77:0b96f6867312 | 1588 | if (bit == 48) |
mjr | 77:0b96f6867312 | 1589 | { |
mjr | 77:0b96f6867312 | 1590 | // get the E1 and E0 bytes from the top end of the code |
mjr | 77:0b96f6867312 | 1591 | uint8_t e1 = uint8_t((code >> 40) & 0xff); |
mjr | 77:0b96f6867312 | 1592 | uint8_t e0 = uint8_t((code >> 32) & 0xff); |
mjr | 77:0b96f6867312 | 1593 | |
mjr | 77:0b96f6867312 | 1594 | // insert them at the low end in E0 E1 order |
mjr | 77:0b96f6867312 | 1595 | codeOut <<= 16; |
mjr | 77:0b96f6867312 | 1596 | codeOut |= (uint64_t(e0) << 8) | uint64_t(e1); |
mjr | 77:0b96f6867312 | 1597 | } |
mjr | 77:0b96f6867312 | 1598 | } |
mjr | 77:0b96f6867312 | 1599 | |
mjr | 77:0b96f6867312 | 1600 | // report it |
mjr | 77:0b96f6867312 | 1601 | reportCode(receiver, necPro(bit), codeOut, bool3::null, ditto); |
mjr | 77:0b96f6867312 | 1602 | } |
mjr | 77:0b96f6867312 | 1603 | } |
mjr | 77:0b96f6867312 | 1604 | |
mjr | 77:0b96f6867312 | 1605 | // convert a code to a bitstream for sending |
mjr | 77:0b96f6867312 | 1606 | virtual void codeToBitstream(IRTXState *state) |
mjr | 77:0b96f6867312 | 1607 | { |
mjr | 77:0b96f6867312 | 1608 | if (state->protocolId == IRPRO_PIONEER) |
mjr | 77:0b96f6867312 | 1609 | { |
mjr | 77:0b96f6867312 | 1610 | // Check if we have an extended code |
mjr | 77:0b96f6867312 | 1611 | uint32_t c; |
mjr | 77:0b96f6867312 | 1612 | if ((state->cmdCode & 0xFFFF0000) != 0) |
mjr | 77:0b96f6867312 | 1613 | { |
mjr | 77:0b96f6867312 | 1614 | // Extended code. These are transmitted as two codes in |
mjr | 77:0b96f6867312 | 1615 | // a row, one for the shift code in the high 16 bits, and |
mjr | 77:0b96f6867312 | 1616 | // one for the subcode in the low 16 bits. Transmit the |
mjr | 77:0b96f6867312 | 1617 | // shift code on even reps and the subcode on odd reps. |
mjr | 77:0b96f6867312 | 1618 | if (state->rep == 0 || state->rep == 2) |
mjr | 77:0b96f6867312 | 1619 | { |
mjr | 77:0b96f6867312 | 1620 | // even rep - use the shift code |
mjr | 77:0b96f6867312 | 1621 | c = (state->cmdCode >> 16) & 0xFFFF; |
mjr | 77:0b96f6867312 | 1622 | |
mjr | 77:0b96f6867312 | 1623 | // wrap back to rep 0 on rep 2 |
mjr | 77:0b96f6867312 | 1624 | state->rep = 0; |
mjr | 77:0b96f6867312 | 1625 | } |
mjr | 77:0b96f6867312 | 1626 | else |
mjr | 77:0b96f6867312 | 1627 | { |
mjr | 77:0b96f6867312 | 1628 | // odd rep - use the subcode |
mjr | 77:0b96f6867312 | 1629 | c = state->cmdCode & 0xFFFF; |
mjr | 77:0b96f6867312 | 1630 | } |
mjr | 77:0b96f6867312 | 1631 | } |
mjr | 77:0b96f6867312 | 1632 | else |
mjr | 77:0b96f6867312 | 1633 | { |
mjr | 77:0b96f6867312 | 1634 | // it's a single-part code |
mjr | 77:0b96f6867312 | 1635 | c = state->cmdCode; |
mjr | 77:0b96f6867312 | 1636 | } |
mjr | 77:0b96f6867312 | 1637 | |
mjr | 77:0b96f6867312 | 1638 | // encode it in the 32-bit original NEC format with the address |
mjr | 77:0b96f6867312 | 1639 | // and command byte complemented |
mjr | 77:0b96f6867312 | 1640 | uint8_t a0 = uint8_t((c >> 8) & 0xff); |
mjr | 77:0b96f6867312 | 1641 | uint8_t a1 = uint8_t(~a0); |
mjr | 77:0b96f6867312 | 1642 | uint8_t c0 = uint8_t(c & 0xff); |
mjr | 77:0b96f6867312 | 1643 | uint8_t c1 = uint8_t(~c0); |
mjr | 77:0b96f6867312 | 1644 | state->bitstream = (uint64_t(c1) << 24) | (uint64_t(c0) << 16) |
mjr | 77:0b96f6867312 | 1645 | | (uint64_t(a1) << 8) | uint64_t(a0); |
mjr | 77:0b96f6867312 | 1646 | state->nbits = 32; |
mjr | 77:0b96f6867312 | 1647 | |
mjr | 77:0b96f6867312 | 1648 | // Pioneer *can't* use NEC dittos even if the caller thinks we |
mjr | 77:0b96f6867312 | 1649 | // should, because that breaks the shift-code model Pioneer uses |
mjr | 77:0b96f6867312 | 1650 | state->dittos = false; |
mjr | 77:0b96f6867312 | 1651 | } |
mjr | 97:fc7727303038 | 1652 | else if (state->protocolId == IRPRO_TCLROKU) |
mjr | 97:fc7727303038 | 1653 | { |
mjr | 97:fc7727303038 | 1654 | // TCL Roku models use doubled code words. The second code |
mjr | 97:fc7727303038 | 1655 | // word in the pair is always the same as the first with |
mjr | 97:fc7727303038 | 1656 | // the two bytes of the command field XOR'd with 0x80. |
mjr | 97:fc7727303038 | 1657 | uint32_t c; |
mjr | 97:fc7727303038 | 1658 | if (state->rep == 0 || state->rep == 2) |
mjr | 97:fc7727303038 | 1659 | { |
mjr | 97:fc7727303038 | 1660 | // even rep - use the nominal command code |
mjr | 97:fc7727303038 | 1661 | c = state->cmdCode; |
mjr | 97:fc7727303038 | 1662 | |
mjr | 97:fc7727303038 | 1663 | // wrap back to rep 0 on rep 2 |
mjr | 97:fc7727303038 | 1664 | state->rep = 0; |
mjr | 97:fc7727303038 | 1665 | } |
mjr | 97:fc7727303038 | 1666 | else |
mjr | 97:fc7727303038 | 1667 | { |
mjr | 97:fc7727303038 | 1668 | // odd rep - use the code XOR'd with 0x8080 |
mjr | 97:fc7727303038 | 1669 | c = state->cmdCode ^ 0x8080; |
mjr | 97:fc7727303038 | 1670 | } |
mjr | 97:fc7727303038 | 1671 | |
mjr | 97:fc7727303038 | 1672 | // use the normal NEC32 encoding, substituting the possibly |
mjr | 97:fc7727303038 | 1673 | // modified code field 'c' we calculated above |
mjr | 97:fc7727303038 | 1674 | uint32_t orig = uint32_t(state->cmdCode); |
mjr | 97:fc7727303038 | 1675 | uint8_t a0 = uint8_t((orig >> 24) & 0xff); |
mjr | 97:fc7727303038 | 1676 | uint8_t a1 = uint8_t((orig >> 16) & 0xff); |
mjr | 97:fc7727303038 | 1677 | uint8_t c0 = uint8_t((c >> 8) & 0xff); |
mjr | 97:fc7727303038 | 1678 | uint8_t c1 = uint8_t(c & 0xff); |
mjr | 97:fc7727303038 | 1679 | state->bitstream = |
mjr | 97:fc7727303038 | 1680 | (uint64_t(c1) << 24) | (uint64_t(c0) << 16) |
mjr | 97:fc7727303038 | 1681 | | (uint64_t(a1) << 8) | uint64_t(a0); |
mjr | 97:fc7727303038 | 1682 | state->nbits = 32; |
mjr | 97:fc7727303038 | 1683 | |
mjr | 97:fc7727303038 | 1684 | // this protocol doesn't use dittos |
mjr | 97:fc7727303038 | 1685 | state->dittos = false; |
mjr | 97:fc7727303038 | 1686 | } |
mjr | 77:0b96f6867312 | 1687 | else if (state->protocolId == IRPRO_NEC48) |
mjr | 77:0b96f6867312 | 1688 | { |
mjr | 77:0b96f6867312 | 1689 | // NEC 48-bit code. We store the bytes in the universal |
mjr | 77:0b96f6867312 | 1690 | // representation in order A0 A1 C0 C1 E0 E1. Reverse this |
mjr | 77:0b96f6867312 | 1691 | // order for transmission. |
mjr | 77:0b96f6867312 | 1692 | uint64_t code = state->cmdCode; |
mjr | 77:0b96f6867312 | 1693 | uint8_t a0 = uint8_t((code >> 40) & 0xff); |
mjr | 77:0b96f6867312 | 1694 | uint8_t a1 = uint8_t((code >> 32) & 0xff); |
mjr | 77:0b96f6867312 | 1695 | uint8_t c0 = uint8_t((code >> 24) & 0xff); |
mjr | 77:0b96f6867312 | 1696 | uint8_t c1 = uint8_t((code >> 16)& 0xff); |
mjr | 77:0b96f6867312 | 1697 | uint8_t e0 = uint8_t((code >> 8) & 0xff); |
mjr | 77:0b96f6867312 | 1698 | uint8_t e1 = uint8_t((code) & 0xff); |
mjr | 77:0b96f6867312 | 1699 | state->bitstream = |
mjr | 77:0b96f6867312 | 1700 | (uint64_t(e1) << 40) | (uint64_t(e0) << 32) |
mjr | 77:0b96f6867312 | 1701 | | (uint64_t(c1) << 24) | (uint64_t(c0) << 16) |
mjr | 77:0b96f6867312 | 1702 | | (uint64_t(a1) << 8) | uint64_t(a0); |
mjr | 77:0b96f6867312 | 1703 | state->nbits = 48; |
mjr | 77:0b96f6867312 | 1704 | } |
mjr | 77:0b96f6867312 | 1705 | else |
mjr | 77:0b96f6867312 | 1706 | { |
mjr | 77:0b96f6867312 | 1707 | // NEC 32-bit code. The universal representation stores |
mjr | 77:0b96f6867312 | 1708 | // the bytes in order A0 A1 C0 C1. For transmission, we |
mjr | 77:0b96f6867312 | 1709 | // need to reverse this to C1 C0 A1 A0. |
mjr | 77:0b96f6867312 | 1710 | uint32_t code = uint32_t(state->cmdCode); |
mjr | 77:0b96f6867312 | 1711 | uint8_t a0 = uint8_t((code >> 24) & 0xff); |
mjr | 77:0b96f6867312 | 1712 | uint8_t a1 = uint8_t((code >> 16) & 0xff); |
mjr | 77:0b96f6867312 | 1713 | uint8_t c0 = uint8_t((code >> 8) & 0xff); |
mjr | 77:0b96f6867312 | 1714 | uint8_t c1 = uint8_t(code & 0xff); |
mjr | 77:0b96f6867312 | 1715 | state->bitstream = |
mjr | 77:0b96f6867312 | 1716 | (uint64_t(c1) << 24) | (uint64_t(c0) << 16) |
mjr | 77:0b96f6867312 | 1717 | | (uint64_t(a1) << 8) | uint64_t(a0); |
mjr | 77:0b96f6867312 | 1718 | state->nbits = 32; |
mjr | 77:0b96f6867312 | 1719 | } |
mjr | 77:0b96f6867312 | 1720 | } |
mjr | 77:0b96f6867312 | 1721 | |
mjr | 77:0b96f6867312 | 1722 | // NEC uses a special "ditto" code for repeats. The ditto consists |
mjr | 77:0b96f6867312 | 1723 | // of the normal header mark, half a header space, a regular data |
mjr | 77:0b96f6867312 | 1724 | // mark. After this, a standard inter-message gap follows, and then |
mjr | 77:0b96f6867312 | 1725 | // we repeat the ditto as long as the button is held down. |
mjr | 77:0b96f6867312 | 1726 | virtual int txDittoStep(IRTXState *state) |
mjr | 77:0b96f6867312 | 1727 | { |
mjr | 77:0b96f6867312 | 1728 | // send the ditto |
mjr | 77:0b96f6867312 | 1729 | uint32_t t; |
mjr | 77:0b96f6867312 | 1730 | switch (state->step) |
mjr | 77:0b96f6867312 | 1731 | { |
mjr | 77:0b96f6867312 | 1732 | case 0: |
mjr | 77:0b96f6867312 | 1733 | // Ditto header mark |
mjr | 77:0b96f6867312 | 1734 | state->step++; |
mjr | 77:0b96f6867312 | 1735 | state->pin->write(pwmDutyCycle()); |
mjr | 77:0b96f6867312 | 1736 | |
mjr | 77:0b96f6867312 | 1737 | // use the special ditto mark timing if it's different; 0 means |
mjr | 77:0b96f6867312 | 1738 | // that we use the same timing as the standard data frame header |
mjr | 77:0b96f6867312 | 1739 | return (t = tDittoMark()) != 0 ? t : tHeaderMark(); |
mjr | 77:0b96f6867312 | 1740 | |
mjr | 77:0b96f6867312 | 1741 | case 1: |
mjr | 77:0b96f6867312 | 1742 | // Ditto header space |
mjr | 77:0b96f6867312 | 1743 | state->step++; |
mjr | 77:0b96f6867312 | 1744 | state->pin->write(0); |
mjr | 77:0b96f6867312 | 1745 | |
mjr | 77:0b96f6867312 | 1746 | // use the special ditto timing if it's different |
mjr | 77:0b96f6867312 | 1747 | return (t = tDittoSpace()) != 0 ? t : tHeaderSpace(); |
mjr | 77:0b96f6867312 | 1748 | |
mjr | 77:0b96f6867312 | 1749 | case 2: |
mjr | 77:0b96f6867312 | 1750 | // Data section. NEC-32X sends one data bit. The others |
mjr | 77:0b96f6867312 | 1751 | // send no data bits, so go straight to the stop bit. |
mjr | 77:0b96f6867312 | 1752 | if (state->protocolId == IRPRO_NEC32X && state->bit == 0) |
mjr | 77:0b96f6867312 | 1753 | return txDataStep(state); |
mjr | 77:0b96f6867312 | 1754 | |
mjr | 77:0b96f6867312 | 1755 | // for others, fall through to the stop mark |
mjr | 77:0b96f6867312 | 1756 | state->step++; |
mjr | 77:0b96f6867312 | 1757 | // FALL THROUGH... |
mjr | 77:0b96f6867312 | 1758 | |
mjr | 77:0b96f6867312 | 1759 | case 3: |
mjr | 77:0b96f6867312 | 1760 | // stop mark |
mjr | 77:0b96f6867312 | 1761 | state->step++; |
mjr | 77:0b96f6867312 | 1762 | state->pin->write(pwmDutyCycle()); |
mjr | 77:0b96f6867312 | 1763 | return tMark(); |
mjr | 77:0b96f6867312 | 1764 | |
mjr | 77:0b96f6867312 | 1765 | case 4: |
mjr | 77:0b96f6867312 | 1766 | // send a gap |
mjr | 77:0b96f6867312 | 1767 | state->step++; |
mjr | 77:0b96f6867312 | 1768 | state->pin->write(0); |
mjr | 77:0b96f6867312 | 1769 | return 108000 - state->txTime.read_us(); |
mjr | 77:0b96f6867312 | 1770 | |
mjr | 77:0b96f6867312 | 1771 | default: |
mjr | 77:0b96f6867312 | 1772 | // done |
mjr | 77:0b96f6867312 | 1773 | return txEnd(state); |
mjr | 77:0b96f6867312 | 1774 | } |
mjr | 77:0b96f6867312 | 1775 | } |
mjr | 77:0b96f6867312 | 1776 | |
mjr | 77:0b96f6867312 | 1777 | }; |
mjr | 77:0b96f6867312 | 1778 | |
mjr | 97:fc7727303038 | 1779 | // NEC-32, NEC-48, Pioneer, and TCL TVs with embedded Roku |
mjr | 77:0b96f6867312 | 1780 | class IRPNEC_32_48: public IRPNEC |
mjr | 77:0b96f6867312 | 1781 | { |
mjr | 77:0b96f6867312 | 1782 | public: |
mjr | 77:0b96f6867312 | 1783 | IRPNEC_32_48() |
mjr | 77:0b96f6867312 | 1784 | { |
mjr | 77:0b96f6867312 | 1785 | pioneerPrvCode = 0; |
mjr | 97:fc7727303038 | 1786 | tclRokuPrvCode = 0; |
mjr | 77:0b96f6867312 | 1787 | } |
mjr | 77:0b96f6867312 | 1788 | |
mjr | 77:0b96f6867312 | 1789 | // name and ID |
mjr | 77:0b96f6867312 | 1790 | virtual const char *name() const { return "NEC"; } |
mjr | 77:0b96f6867312 | 1791 | virtual int id() const { return IRPRO_NEC32; } |
mjr | 77:0b96f6867312 | 1792 | virtual int necPro(int bits) const |
mjr | 77:0b96f6867312 | 1793 | { |
mjr | 77:0b96f6867312 | 1794 | return bits == 48 ? IRPRO_NEC48 : IRPRO_NEC32; |
mjr | 77:0b96f6867312 | 1795 | } |
mjr | 77:0b96f6867312 | 1796 | |
mjr | 77:0b96f6867312 | 1797 | // we encode several protocols |
mjr | 77:0b96f6867312 | 1798 | virtual bool isSenderFor(int pro) const |
mjr | 77:0b96f6867312 | 1799 | { |
mjr | 97:fc7727303038 | 1800 | return pro == IRPRO_NEC32 |
mjr | 97:fc7727303038 | 1801 | || pro == IRPRO_NEC48 |
mjr | 97:fc7727303038 | 1802 | || pro == IRPRO_PIONEER |
mjr | 97:fc7727303038 | 1803 | || pro == IRPRO_TCLROKU; |
mjr | 77:0b96f6867312 | 1804 | } |
mjr | 77:0b96f6867312 | 1805 | |
mjr | 77:0b96f6867312 | 1806 | // NEC-32 and NEC-48 use the same framing |
mjr | 77:0b96f6867312 | 1807 | virtual uint32_t tHeaderMark() const { return 9000; } |
mjr | 77:0b96f6867312 | 1808 | virtual uint32_t tHeaderSpace() const { return 4500; } |
mjr | 77:0b96f6867312 | 1809 | virtual uint32_t tDittoMark() const { return 9000; } |
mjr | 77:0b96f6867312 | 1810 | virtual uint32_t tDittoSpace() const { return 2250; } |
mjr | 77:0b96f6867312 | 1811 | virtual int nbits() const { return 48; } |
mjr | 77:0b96f6867312 | 1812 | |
mjr | 77:0b96f6867312 | 1813 | // receiver format descriptor |
mjr | 77:0b96f6867312 | 1814 | // decode, check, and report a code value |
mjr | 77:0b96f6867312 | 1815 | virtual void rxClose(IRRecvProIfc *receiver, bool ditto) |
mjr | 77:0b96f6867312 | 1816 | { |
mjr | 77:0b96f6867312 | 1817 | // If we're in Pioneer mode, use special handling |
mjr | 77:0b96f6867312 | 1818 | if (isPioneerCode()) |
mjr | 77:0b96f6867312 | 1819 | { |
mjr | 77:0b96f6867312 | 1820 | rxClosePioneer(receiver); |
mjr | 77:0b96f6867312 | 1821 | return; |
mjr | 77:0b96f6867312 | 1822 | } |
mjr | 77:0b96f6867312 | 1823 | |
mjr | 77:0b96f6867312 | 1824 | // The new code isn't a pioneer code, so if we had a Pioneer |
mjr | 77:0b96f6867312 | 1825 | // code stashed, it doesn't have a second half. Report is as |
mjr | 77:0b96f6867312 | 1826 | // a standalone code. |
mjr | 77:0b96f6867312 | 1827 | if (pioneerPrvCode != 0) |
mjr | 77:0b96f6867312 | 1828 | { |
mjr | 77:0b96f6867312 | 1829 | reportPioneerFormat(receiver, pioneerPrvCode); |
mjr | 77:0b96f6867312 | 1830 | pioneerPrvCode = 0; |
mjr | 77:0b96f6867312 | 1831 | } |
mjr | 77:0b96f6867312 | 1832 | |
mjr | 97:fc7727303038 | 1833 | // If we're in TCL/Roku mode, use special handling |
mjr | 97:fc7727303038 | 1834 | if (isTCLRokuCode()) |
mjr | 97:fc7727303038 | 1835 | { |
mjr | 97:fc7727303038 | 1836 | rxCloseTCLRoku(receiver); |
mjr | 97:fc7727303038 | 1837 | return; |
mjr | 97:fc7727303038 | 1838 | } |
mjr | 97:fc7727303038 | 1839 | |
mjr | 97:fc7727303038 | 1840 | // The new code isn't a TCL/Roku code, so if we had a first half |
mjr | 97:fc7727303038 | 1841 | // code stashed, it doesn't have a second half forthcoming. Report |
mjr | 97:fc7727303038 | 1842 | // it as a standalone code. |
mjr | 97:fc7727303038 | 1843 | if (tclRokuPrvCode != 0) |
mjr | 97:fc7727303038 | 1844 | { |
mjr | 97:fc7727303038 | 1845 | reportTCLRokuFormat(receiver, tclRokuPrvCode); |
mjr | 97:fc7727303038 | 1846 | tclRokuPrvCode = 0; |
mjr | 97:fc7727303038 | 1847 | } |
mjr | 97:fc7727303038 | 1848 | |
mjr | 77:0b96f6867312 | 1849 | // use the generic NEC handling |
mjr | 77:0b96f6867312 | 1850 | IRPNEC::rxClose(receiver, ditto); |
mjr | 77:0b96f6867312 | 1851 | } |
mjr | 77:0b96f6867312 | 1852 | |
mjr | 77:0b96f6867312 | 1853 | virtual void rxIdle(IRRecvProIfc *receiver) |
mjr | 77:0b96f6867312 | 1854 | { |
mjr | 97:fc7727303038 | 1855 | // if we have a stashed prior Pioneer code, close it out, since |
mjr | 77:0b96f6867312 | 1856 | // no more codes are forthcoming |
mjr | 77:0b96f6867312 | 1857 | if (pioneerPrvCode != 0) |
mjr | 77:0b96f6867312 | 1858 | { |
mjr | 77:0b96f6867312 | 1859 | reportPioneerFormat(receiver, pioneerPrvCode); |
mjr | 77:0b96f6867312 | 1860 | pioneerPrvCode = 0; |
mjr | 77:0b96f6867312 | 1861 | } |
mjr | 97:fc7727303038 | 1862 | |
mjr | 97:fc7727303038 | 1863 | // likewise for TCL/Roku stashed prior codes |
mjr | 97:fc7727303038 | 1864 | if (tclRokuPrvCode != 0) |
mjr | 97:fc7727303038 | 1865 | { |
mjr | 97:fc7727303038 | 1866 | reportTCLRokuFormat(receiver, tclRokuPrvCode); |
mjr | 97:fc7727303038 | 1867 | tclRokuPrvCode = 0; |
mjr | 97:fc7727303038 | 1868 | } |
mjr | 77:0b96f6867312 | 1869 | } |
mjr | 77:0b96f6867312 | 1870 | |
mjr | 77:0b96f6867312 | 1871 | // close out a Pioneer code |
mjr | 77:0b96f6867312 | 1872 | virtual void rxClosePioneer(IRRecvProIfc *receiver) |
mjr | 77:0b96f6867312 | 1873 | { |
mjr | 77:0b96f6867312 | 1874 | // Check to see if we have a valid previous code and/or |
mjr | 77:0b96f6867312 | 1875 | // a valid new code. |
mjr | 77:0b96f6867312 | 1876 | if (pioneerPrvCode != 0) |
mjr | 77:0b96f6867312 | 1877 | { |
mjr | 77:0b96f6867312 | 1878 | // We have a stashed Pioneer code plus the new one. If |
mjr | 77:0b96f6867312 | 1879 | // they're different, we must have an extended code with |
mjr | 77:0b96f6867312 | 1880 | // a "shift" prefix. |
mjr | 77:0b96f6867312 | 1881 | if (pioneerPrvCode != code) |
mjr | 77:0b96f6867312 | 1882 | { |
mjr | 77:0b96f6867312 | 1883 | // distinct code - it's an extended code with a shift |
mjr | 77:0b96f6867312 | 1884 | reportPioneerFormat(receiver, pioneerPrvCode, code); |
mjr | 77:0b96f6867312 | 1885 | } |
mjr | 77:0b96f6867312 | 1886 | else |
mjr | 77:0b96f6867312 | 1887 | { |
mjr | 77:0b96f6867312 | 1888 | // same code - it's just a repeat, so report it twice |
mjr | 77:0b96f6867312 | 1889 | reportPioneerFormat(receiver, code); |
mjr | 77:0b96f6867312 | 1890 | reportPioneerFormat(receiver, code); |
mjr | 77:0b96f6867312 | 1891 | } |
mjr | 77:0b96f6867312 | 1892 | |
mjr | 77:0b96f6867312 | 1893 | // we've now consumed the previous code |
mjr | 77:0b96f6867312 | 1894 | pioneerPrvCode = 0; |
mjr | 77:0b96f6867312 | 1895 | } |
mjr | 77:0b96f6867312 | 1896 | else |
mjr | 77:0b96f6867312 | 1897 | { |
mjr | 77:0b96f6867312 | 1898 | // There's no stashed code. Don't report the new one yet, |
mjr | 77:0b96f6867312 | 1899 | // since it might be a "shift" prefix. Stash it until we |
mjr | 77:0b96f6867312 | 1900 | // find out if another code follows. |
mjr | 77:0b96f6867312 | 1901 | pioneerPrvCode = code; |
mjr | 77:0b96f6867312 | 1902 | bit = 0; |
mjr | 77:0b96f6867312 | 1903 | code = 0; |
mjr | 77:0b96f6867312 | 1904 | } |
mjr | 77:0b96f6867312 | 1905 | } |
mjr | 77:0b96f6867312 | 1906 | |
mjr | 77:0b96f6867312 | 1907 | // determine if we have Pioneer address |
mjr | 77:0b96f6867312 | 1908 | bool isPioneerCode() |
mjr | 77:0b96f6867312 | 1909 | { |
mjr | 97:fc7727303038 | 1910 | // pull out the command and address fields |
mjr | 77:0b96f6867312 | 1911 | uint8_t c1 = uint8_t((code >> 24) & 0xff); |
mjr | 77:0b96f6867312 | 1912 | uint8_t c0 = uint8_t((code >> 16) & 0xff); |
mjr | 77:0b96f6867312 | 1913 | uint8_t a1 = uint8_t((code >> 8) & 0xff); |
mjr | 77:0b96f6867312 | 1914 | uint8_t a0 = uint8_t(code & 0xff); |
mjr | 77:0b96f6867312 | 1915 | |
mjr | 77:0b96f6867312 | 1916 | // Pioneer uses device codes A0..AF, with A1 complemented, and |
mjr | 77:0b96f6867312 | 1917 | // uses only the 32-bit code format. Pioneer also always uses |
mjr | 77:0b96f6867312 | 1918 | // a complemented C0-C1 pair. |
mjr | 77:0b96f6867312 | 1919 | return bit == 32 |
mjr | 77:0b96f6867312 | 1920 | && (a0 >= 0xA0 && a0 <= 0xAF) |
mjr | 77:0b96f6867312 | 1921 | && a0 == uint8_t(~a1) |
mjr | 77:0b96f6867312 | 1922 | && c0 == uint8_t(~c1); |
mjr | 77:0b96f6867312 | 1923 | } |
mjr | 97:fc7727303038 | 1924 | |
mjr | 77:0b96f6867312 | 1925 | // Report a code in Pioneer format. This takes the first address |
mjr | 77:0b96f6867312 | 1926 | // byte and combines it with the first command byte to form a 16-bit |
mjr | 77:0b96f6867312 | 1927 | // code. Pioneer writes codes in this format because the second |
mjr | 77:0b96f6867312 | 1928 | // address and command bytes are always the complements of the first, |
mjr | 77:0b96f6867312 | 1929 | // so they contain no information, so it makes the codes more readable |
mjr | 77:0b96f6867312 | 1930 | // for human consumption to drop the redundant bits. |
mjr | 77:0b96f6867312 | 1931 | void reportPioneerFormat(IRRecvProIfc *receiver, uint32_t code) |
mjr | 77:0b96f6867312 | 1932 | { |
mjr | 77:0b96f6867312 | 1933 | uint8_t a0 = uint8_t(code & 0xff); |
mjr | 77:0b96f6867312 | 1934 | uint8_t c0 = uint8_t((code >> 16) & 0xff); |
mjr | 77:0b96f6867312 | 1935 | reportCode(receiver, IRPRO_PIONEER, (uint64_t(a0) << 8) | c0, |
mjr | 77:0b96f6867312 | 1936 | bool3::null, bool3::null); |
mjr | 77:0b96f6867312 | 1937 | } |
mjr | 77:0b96f6867312 | 1938 | |
mjr | 77:0b96f6867312 | 1939 | // Report an extended two-part code in Pioneer format. code1 is the |
mjr | 77:0b96f6867312 | 1940 | // first code received (the "shift" prefix code), and code2 is the |
mjr | 77:0b96f6867312 | 1941 | // second (the key-specific subcode). We'll convert each code to |
mjr | 77:0b96f6867312 | 1942 | // the standard Pioneer 16-bit format (<address>:<key>), then pack |
mjr | 77:0b96f6867312 | 1943 | // the two into a 32-bit int with the shift code in the high half. |
mjr | 77:0b96f6867312 | 1944 | void reportPioneerFormat(IRRecvProIfc *receiver, uint32_t code1, uint32_t code2) |
mjr | 77:0b96f6867312 | 1945 | { |
mjr | 77:0b96f6867312 | 1946 | uint8_t a1 = code1 & 0xff; |
mjr | 77:0b96f6867312 | 1947 | uint8_t c1 = (code1 >> 16) & 0xff; |
mjr | 77:0b96f6867312 | 1948 | uint8_t a2 = code2 & 0xff; |
mjr | 77:0b96f6867312 | 1949 | uint8_t c2 = (code2 >> 16) & 0xff; |
mjr | 77:0b96f6867312 | 1950 | reportCode( |
mjr | 77:0b96f6867312 | 1951 | receiver, IRPRO_PIONEER, |
mjr | 77:0b96f6867312 | 1952 | (uint64_t(a1) << 24) | (uint64_t(c1) << 16) |
mjr | 77:0b96f6867312 | 1953 | | (uint64_t(a2) << 8) | c2, |
mjr | 77:0b96f6867312 | 1954 | bool3::null, bool3::null); |
mjr | 77:0b96f6867312 | 1955 | } |
mjr | 77:0b96f6867312 | 1956 | |
mjr | 97:fc7727303038 | 1957 | // The previous Pioneer code value. This is used in decoding Pioneer |
mjr | 97:fc7727303038 | 1958 | // codes, since some keys in the Pioneer scheme send a "shift" prefix |
mjr | 97:fc7727303038 | 1959 | // code plus a subcode. |
mjr | 77:0b96f6867312 | 1960 | uint32_t pioneerPrvCode; |
mjr | 97:fc7727303038 | 1961 | |
mjr | 97:fc7727303038 | 1962 | // close out a TCL/Roku code |
mjr | 97:fc7727303038 | 1963 | virtual void rxCloseTCLRoku(IRRecvProIfc *receiver) |
mjr | 97:fc7727303038 | 1964 | { |
mjr | 97:fc7727303038 | 1965 | // Check to see if we have a valid previous code and/or |
mjr | 97:fc7727303038 | 1966 | // a valid new code. |
mjr | 97:fc7727303038 | 1967 | if (tclRokuPrvCode != 0) |
mjr | 97:fc7727303038 | 1968 | { |
mjr | 97:fc7727303038 | 1969 | // We have a stashed code for the TCL/Roku double-code-word |
mjr | 97:fc7727303038 | 1970 | // scheme. If this one matches the previous one with the |
mjr | 97:fc7727303038 | 1971 | // "command field" bytes XOR'd with 0x80, it's the second |
mjr | 97:fc7727303038 | 1972 | // code in the pair. Otherwise it must be a new code. |
mjr | 97:fc7727303038 | 1973 | if (tclRokuPrvCode == code ^ 0x80800000) |
mjr | 97:fc7727303038 | 1974 | { |
mjr | 97:fc7727303038 | 1975 | // it's the matching code from the pair - report it as one code |
mjr | 97:fc7727303038 | 1976 | reportTCLRokuFormat(receiver, tclRokuPrvCode); |
mjr | 97:fc7727303038 | 1977 | } |
mjr | 97:fc7727303038 | 1978 | else |
mjr | 97:fc7727303038 | 1979 | { |
mjr | 97:fc7727303038 | 1980 | // it's not a match, so it must be a distinct code - report |
mjr | 97:fc7727303038 | 1981 | // the two codes separately |
mjr | 97:fc7727303038 | 1982 | reportTCLRokuFormat(receiver, tclRokuPrvCode); |
mjr | 97:fc7727303038 | 1983 | reportTCLRokuFormat(receiver, code); |
mjr | 97:fc7727303038 | 1984 | } |
mjr | 97:fc7727303038 | 1985 | |
mjr | 97:fc7727303038 | 1986 | // we've now consumed the previous code |
mjr | 97:fc7727303038 | 1987 | tclRokuPrvCode = 0; |
mjr | 97:fc7727303038 | 1988 | } |
mjr | 97:fc7727303038 | 1989 | else |
mjr | 97:fc7727303038 | 1990 | { |
mjr | 97:fc7727303038 | 1991 | // There's no stashed code. Don't report the new one yet, since |
mjr | 97:fc7727303038 | 1992 | // it might be the first of a pair. |
mjr | 97:fc7727303038 | 1993 | tclRokuPrvCode = code; |
mjr | 97:fc7727303038 | 1994 | bit = 0; |
mjr | 97:fc7727303038 | 1995 | code = 0; |
mjr | 97:fc7727303038 | 1996 | } |
mjr | 97:fc7727303038 | 1997 | } |
mjr | 97:fc7727303038 | 1998 | |
mjr | 97:fc7727303038 | 1999 | // Report a code in TCL/Roku format. This just uses the standard NEC |
mjr | 97:fc7727303038 | 2000 | // reporting. |
mjr | 97:fc7727303038 | 2001 | void reportTCLRokuFormat(IRRecvProIfc *receiver, uint32_t code) |
mjr | 97:fc7727303038 | 2002 | { |
mjr | 97:fc7727303038 | 2003 | // put the bytes in the reporting order for NEC: A0 A1 C0 C1 |
mjr | 97:fc7727303038 | 2004 | uint8_t c1 = uint8_t((code >> 24) & 0xff); |
mjr | 97:fc7727303038 | 2005 | uint8_t c0 = uint8_t((code >> 16) & 0xff); |
mjr | 97:fc7727303038 | 2006 | uint8_t a1 = uint8_t((code >> 8) & 0xff); |
mjr | 97:fc7727303038 | 2007 | uint8_t a0 = uint8_t(code & 0xff); |
mjr | 97:fc7727303038 | 2008 | uint64_t codeOut = (uint64_t(a0) << 24) | (uint64_t(a1) << 16) |
mjr | 97:fc7727303038 | 2009 | | (uint64_t(c0) << 8) | uint64_t(c1); |
mjr | 97:fc7727303038 | 2010 | |
mjr | 97:fc7727303038 | 2011 | // report it |
mjr | 97:fc7727303038 | 2012 | reportCode(receiver, IRPRO_TCLROKU, codeOut, bool3::null, bool3::null); |
mjr | 97:fc7727303038 | 2013 | } |
mjr | 97:fc7727303038 | 2014 | |
mjr | 97:fc7727303038 | 2015 | // determine if we have a TCL/Roku address |
mjr | 97:fc7727303038 | 2016 | bool isTCLRokuCode() |
mjr | 97:fc7727303038 | 2017 | { |
mjr | 97:fc7727303038 | 2018 | // It's a TCL/Roku model if the address field is EA C7 |
mjr | 97:fc7727303038 | 2019 | return (code & 0xFFFF) == 0xC7EA; |
mjr | 97:fc7727303038 | 2020 | } |
mjr | 97:fc7727303038 | 2021 | |
mjr | 97:fc7727303038 | 2022 | // The previous TCL/Roku code value. All codes in this protocol use |
mjr | 97:fc7727303038 | 2023 | // doubled code words, so we keep track of the first word here. |
mjr | 97:fc7727303038 | 2024 | uint32_t tclRokuPrvCode; |
mjr | 77:0b96f6867312 | 2025 | }; |
mjr | 77:0b96f6867312 | 2026 | |
mjr | 77:0b96f6867312 | 2027 | // NEC-32x. This is a minor variation on the standard NEC-32 protocol, |
mjr | 77:0b96f6867312 | 2028 | // with a slightly different header signature and a different ditto |
mjr | 77:0b96f6867312 | 2029 | // pattern. |
mjr | 77:0b96f6867312 | 2030 | class IRPNEC_32x: public IRPNEC |
mjr | 77:0b96f6867312 | 2031 | { |
mjr | 77:0b96f6867312 | 2032 | public: |
mjr | 77:0b96f6867312 | 2033 | virtual int id() const { return IRPRO_NEC32X; } |
mjr | 77:0b96f6867312 | 2034 | virtual const char *name() const { return "NEC32x"; } |
mjr | 77:0b96f6867312 | 2035 | virtual int necPro(int bits) const { return IRPRO_NEC32X; } |
mjr | 77:0b96f6867312 | 2036 | |
mjr | 77:0b96f6867312 | 2037 | virtual int nbits() const { return 32; } |
mjr | 77:0b96f6867312 | 2038 | virtual uint32_t tHeaderMark() const { return 4500; } |
mjr | 77:0b96f6867312 | 2039 | virtual uint32_t tHeaderSpace() const { return 4500; } |
mjr | 77:0b96f6867312 | 2040 | |
mjr | 77:0b96f6867312 | 2041 | // Close out the code. NEC-32x has an unusual variation of the |
mjr | 77:0b96f6867312 | 2042 | // NEC ditto: it uses the same header as a regular code, but only |
mjr | 77:0b96f6867312 | 2043 | // has a 1-bit payload. |
mjr | 77:0b96f6867312 | 2044 | virtual void rxClose(IRRecvProIfc *receiver, bool ditto) |
mjr | 77:0b96f6867312 | 2045 | { |
mjr | 77:0b96f6867312 | 2046 | if (bit == 1) |
mjr | 77:0b96f6867312 | 2047 | reportCode(receiver, IRPRO_NEC32X, code, bool3::null, true); |
mjr | 77:0b96f6867312 | 2048 | else |
mjr | 77:0b96f6867312 | 2049 | IRPNEC::rxClose(receiver, ditto); |
mjr | 77:0b96f6867312 | 2050 | } |
mjr | 77:0b96f6867312 | 2051 | |
mjr | 77:0b96f6867312 | 2052 | }; |
mjr | 77:0b96f6867312 | 2053 | |
mjr | 77:0b96f6867312 | 2054 | // ----------------------------------------------------------------------- |
mjr | 77:0b96f6867312 | 2055 | // |
mjr | 77:0b96f6867312 | 2056 | // Kaseikyo protocol handler. Like NEC, this is a quasi industry standard |
mjr | 77:0b96f6867312 | 2057 | // used by many companies. Unlike NEC, it seems to be used consistently |
mjr | 77:0b96f6867312 | 2058 | // by just about everyone. There are only two main variations: a 48-bit |
mjr | 77:0b96f6867312 | 2059 | // coding and a 56-bit coding. |
mjr | 77:0b96f6867312 | 2060 | // |
mjr | 77:0b96f6867312 | 2061 | // For all versions, the first 16 bits in serial order provide an OEM ID. |
mjr | 77:0b96f6867312 | 2062 | // We use this to report manufacturer-specific protocol IDs, even though |
mjr | 77:0b96f6867312 | 2063 | // the low-level coding is the same for all of them. Differentiating |
mjr | 77:0b96f6867312 | 2064 | // by manufacturer is mostly for cosmetic reasons, so that human users |
mjr | 77:0b96f6867312 | 2065 | // looking at learned codes or looking for codes to program will see |
mjr | 77:0b96f6867312 | 2066 | // names matching their equipment. In some cases it's also useful for |
mjr | 77:0b96f6867312 | 2067 | // interpreting the internal data fields within the bit string; some |
mjr | 77:0b96f6867312 | 2068 | // OEMs use checksums or other fields that clients might want to |
mjr | 77:0b96f6867312 | 2069 | // interpret. |
mjr | 77:0b96f6867312 | 2070 | // |
mjr | 77:0b96f6867312 | 2071 | class IRPKaseikyo: public IRPSpaceLength<uint64_t> |
mjr | 77:0b96f6867312 | 2072 | { |
mjr | 77:0b96f6867312 | 2073 | public: |
mjr | 77:0b96f6867312 | 2074 | IRPKaseikyo() { } |
mjr | 77:0b96f6867312 | 2075 | |
mjr | 77:0b96f6867312 | 2076 | // name and ID |
mjr | 77:0b96f6867312 | 2077 | virtual const char *name() const { return "Kaseikyo"; } |
mjr | 77:0b96f6867312 | 2078 | virtual int id() const { return IRPRO_KASEIKYO48; } |
mjr | 77:0b96f6867312 | 2079 | |
mjr | 77:0b96f6867312 | 2080 | // we handle all of the OEM-specific protocols |
mjr | 77:0b96f6867312 | 2081 | virtual bool isSenderFor(int id) const |
mjr | 77:0b96f6867312 | 2082 | { |
mjr | 77:0b96f6867312 | 2083 | switch (id) |
mjr | 77:0b96f6867312 | 2084 | { |
mjr | 77:0b96f6867312 | 2085 | case IRPRO_KASEIKYO48: |
mjr | 77:0b96f6867312 | 2086 | case IRPRO_KASEIKYO56: |
mjr | 77:0b96f6867312 | 2087 | case IRPRO_DENONK: |
mjr | 77:0b96f6867312 | 2088 | case IRPRO_FUJITSU48: |
mjr | 77:0b96f6867312 | 2089 | case IRPRO_FUJITSU56: |
mjr | 77:0b96f6867312 | 2090 | case IRPRO_JVC48: |
mjr | 77:0b96f6867312 | 2091 | case IRPRO_JVC56: |
mjr | 77:0b96f6867312 | 2092 | case IRPRO_MITSUBISHIK: |
mjr | 77:0b96f6867312 | 2093 | case IRPRO_PANASONIC48: |
mjr | 77:0b96f6867312 | 2094 | case IRPRO_PANASONIC56: |
mjr | 77:0b96f6867312 | 2095 | case IRPRO_SHARPK: |
mjr | 77:0b96f6867312 | 2096 | case IRPRO_TEACK: |
mjr | 77:0b96f6867312 | 2097 | return true; |
mjr | 77:0b96f6867312 | 2098 | |
mjr | 77:0b96f6867312 | 2099 | default: |
mjr | 77:0b96f6867312 | 2100 | return false; |
mjr | 77:0b96f6867312 | 2101 | } |
mjr | 77:0b96f6867312 | 2102 | } |
mjr | 77:0b96f6867312 | 2103 | |
mjr | 77:0b96f6867312 | 2104 | // code boundary parameters |
mjr | 77:0b96f6867312 | 2105 | virtual uint32_t tHeaderMark() const { return 3500; } |
mjr | 77:0b96f6867312 | 2106 | virtual uint32_t tHeaderSpace() const { return 1750; } |
mjr | 77:0b96f6867312 | 2107 | virtual uint32_t minRxGap() const { return 2500; } |
mjr | 77:0b96f6867312 | 2108 | virtual uint32_t txGap(IRTXState *state) const { return 173000; } |
mjr | 77:0b96f6867312 | 2109 | |
mjr | 77:0b96f6867312 | 2110 | // space length coding |
mjr | 77:0b96f6867312 | 2111 | virtual int nbits() const { return 56; } |
mjr | 77:0b96f6867312 | 2112 | virtual int tMark() const { return 420; } |
mjr | 77:0b96f6867312 | 2113 | virtual int tZero() const { return 420; } |
mjr | 77:0b96f6867312 | 2114 | virtual int tOne() const { return 1300; } |
mjr | 77:0b96f6867312 | 2115 | |
mjr | 77:0b96f6867312 | 2116 | // protocol/OEM mappings |
mjr | 77:0b96f6867312 | 2117 | struct OEMMap |
mjr | 77:0b96f6867312 | 2118 | { |
mjr | 77:0b96f6867312 | 2119 | uint16_t oem; |
mjr | 77:0b96f6867312 | 2120 | uint8_t pro; |
mjr | 77:0b96f6867312 | 2121 | uint8_t bits; |
mjr | 77:0b96f6867312 | 2122 | }; |
mjr | 77:0b96f6867312 | 2123 | static const OEMMap oemMap[]; |
mjr | 77:0b96f6867312 | 2124 | static const int nOemMap; |
mjr | 77:0b96f6867312 | 2125 | |
mjr | 77:0b96f6867312 | 2126 | // close code reception |
mjr | 77:0b96f6867312 | 2127 | virtual void rxClose(IRRecvProIfc *receiver, bool ditto) |
mjr | 77:0b96f6867312 | 2128 | { |
mjr | 77:0b96f6867312 | 2129 | // it must be a 48-bit or 56-bit code |
mjr | 77:0b96f6867312 | 2130 | if (bit != 48 && bit != 56) |
mjr | 77:0b96f6867312 | 2131 | return; |
mjr | 77:0b96f6867312 | 2132 | |
mjr | 77:0b96f6867312 | 2133 | // pull out the OEM code in the low 16 bits |
mjr | 77:0b96f6867312 | 2134 | int oem = int(code & 0xFFFF); |
mjr | 77:0b96f6867312 | 2135 | |
mjr | 77:0b96f6867312 | 2136 | // Find the protocol based on the OEM |
mjr | 77:0b96f6867312 | 2137 | uint8_t pro = bit == 48 ? IRPRO_KASEIKYO48 : IRPRO_KASEIKYO56; |
mjr | 77:0b96f6867312 | 2138 | for (int i = 0 ; i < nOemMap ; ++i) |
mjr | 77:0b96f6867312 | 2139 | { |
mjr | 77:0b96f6867312 | 2140 | if (oemMap[i].oem == oem && oemMap[i].bits == bit) |
mjr | 77:0b96f6867312 | 2141 | { |
mjr | 77:0b96f6867312 | 2142 | pro = oemMap[i].pro; |
mjr | 77:0b96f6867312 | 2143 | break; |
mjr | 77:0b96f6867312 | 2144 | } |
mjr | 77:0b96f6867312 | 2145 | } |
mjr | 77:0b96f6867312 | 2146 | |
mjr | 77:0b96f6867312 | 2147 | // report the code |
mjr | 77:0b96f6867312 | 2148 | reportCode(receiver, pro, code, bool3::null, bool3::null); |
mjr | 77:0b96f6867312 | 2149 | } |
mjr | 77:0b96f6867312 | 2150 | |
mjr | 77:0b96f6867312 | 2151 | virtual void codeToBitstream(IRTXState *state) |
mjr | 77:0b96f6867312 | 2152 | { |
mjr | 77:0b96f6867312 | 2153 | // presume no OEM and a 48-bit version of the protocol |
mjr | 77:0b96f6867312 | 2154 | uint16_t oem = 0; |
mjr | 77:0b96f6867312 | 2155 | state->nbits = 48; |
mjr | 77:0b96f6867312 | 2156 | |
mjr | 77:0b96f6867312 | 2157 | // find the protocol variation in the table |
mjr | 77:0b96f6867312 | 2158 | for (int i = 0 ; i < nOemMap ; ++i) |
mjr | 77:0b96f6867312 | 2159 | { |
mjr | 77:0b96f6867312 | 2160 | if (state->protocolId == oemMap[i].pro) |
mjr | 77:0b96f6867312 | 2161 | { |
mjr | 77:0b96f6867312 | 2162 | state->nbits = oemMap[i].bits; |
mjr | 77:0b96f6867312 | 2163 | oem = oemMap[i].oem; |
mjr | 77:0b96f6867312 | 2164 | break; |
mjr | 77:0b96f6867312 | 2165 | } |
mjr | 77:0b96f6867312 | 2166 | } |
mjr | 77:0b96f6867312 | 2167 | |
mjr | 77:0b96f6867312 | 2168 | // if we found a non-zero OEM code, and it doesn't match the |
mjr | 77:0b96f6867312 | 2169 | // low-order 16 data bits, replace the OEM code in the data |
mjr | 77:0b96f6867312 | 2170 | uint64_t code = state->cmdCode; |
mjr | 77:0b96f6867312 | 2171 | if (oem != 0 && int(code & 0xFFFF) != oem) |
mjr | 77:0b96f6867312 | 2172 | code = (code & ~0xFFFFLL) | oem; |
mjr | 77:0b96f6867312 | 2173 | |
mjr | 77:0b96f6867312 | 2174 | // store the code (with possibly updated OEM coding) |
mjr | 77:0b96f6867312 | 2175 | state->bitstream = code; |
mjr | 77:0b96f6867312 | 2176 | } |
mjr | 77:0b96f6867312 | 2177 | }; |
mjr | 77:0b96f6867312 | 2178 | |
mjr | 77:0b96f6867312 | 2179 | // ----------------------------------------------------------------------- |
mjr | 77:0b96f6867312 | 2180 | // |
mjr | 77:0b96f6867312 | 2181 | // Philips RC5 protocol handler. This (along with RC6) is a quasi industry |
mjr | 77:0b96f6867312 | 2182 | // standard among European CE companies, so this protocol gives us |
mjr | 77:0b96f6867312 | 2183 | // compatibility with many devices from companies besides Philips. |
mjr | 77:0b96f6867312 | 2184 | // |
mjr | 77:0b96f6867312 | 2185 | // RC5 is a 14-bit Manchester-coded protocol. '1' bits are encoded as |
mjr | 77:0b96f6867312 | 2186 | // low->high transitions. |
mjr | 77:0b96f6867312 | 2187 | // |
mjr | 77:0b96f6867312 | 2188 | // The 14 bits of the command are internally structured as follows: |
mjr | 77:0b96f6867312 | 2189 | // |
mjr | 77:0b96f6867312 | 2190 | // S F T AAAAA CCCCCC |
mjr | 77:0b96f6867312 | 2191 | // |
mjr | 77:0b96f6867312 | 2192 | // S = "start bit". Always 1. We omit this from the reported code |
mjr | 77:0b96f6867312 | 2193 | // since it's always the same. |
mjr | 77:0b96f6867312 | 2194 | // |
mjr | 77:0b96f6867312 | 2195 | // F = "field bit", which selects a default (1) or extended (0) set of |
mjr | 77:0b96f6867312 | 2196 | // commands. Note the reverse sensing, with '1' being the default. |
mjr | 77:0b96f6867312 | 2197 | // This is because this position was a second stop bit in the original |
mjr | 77:0b96f6867312 | 2198 | // code, always set to '1'. When Philips repurposed the bit as the |
mjr | 77:0b96f6867312 | 2199 | // field code in a later version of the protocol, they used '1' as |
mjr | 77:0b96f6867312 | 2200 | // the default for compatibility with older devices. We pass the bit |
mjr | 77:0b96f6867312 | 2201 | // through as-is to the universal representation, so be aware that |
mjr | 77:0b96f6867312 | 2202 | // you might have to flip it to match some published code tables. |
mjr | 77:0b96f6867312 | 2203 | // |
mjr | 77:0b96f6867312 | 2204 | // T = "toggle bit". This changes on each successive key press, to allow |
mjr | 77:0b96f6867312 | 2205 | // the receiver to distinguish pressing the same key twice from auto- |
mjr | 77:0b96f6867312 | 2206 | // repeat due to holding down the key. |
mjr | 77:0b96f6867312 | 2207 | // |
mjr | 77:0b96f6867312 | 2208 | // A = "address", most significant bit first; specifies which type of |
mjr | 77:0b96f6867312 | 2209 | // device the command is for (e.g., "TV", "VCR", etc). The meanings |
mjr | 77:0b96f6867312 | 2210 | // of the possible numeric values are arbitrarily assigned by Philips; |
mjr | 77:0b96f6867312 | 2211 | // you can Philips; you can find tables online (e.g., at Wikipedia) |
mjr | 77:0b96f6867312 | 2212 | // with the published assignments. |
mjr | 77:0b96f6867312 | 2213 | // |
mjr | 77:0b96f6867312 | 2214 | // C = "command", most significant bit first; the command code. The |
mjr | 77:0b96f6867312 | 2215 | // meaning of the command code varies according to the type of device |
mjr | 77:0b96f6867312 | 2216 | // in the address field. Published tables with the standard codes can |
mjr | 77:0b96f6867312 | 2217 | // be found online. |
mjr | 77:0b96f6867312 | 2218 | // |
mjr | 77:0b96f6867312 | 2219 | // Note that this protocol doesn't have a "header" per se; it just starts |
mjr | 77:0b96f6867312 | 2220 | // in directly with the first bit. As soon as we see a long enough gap, |
mjr | 77:0b96f6867312 | 2221 | // we're ready for the start bit. |
mjr | 77:0b96f6867312 | 2222 | // |
mjr | 77:0b96f6867312 | 2223 | class IRPRC5: public IRPManchester<uint16_t> |
mjr | 77:0b96f6867312 | 2224 | { |
mjr | 77:0b96f6867312 | 2225 | public: |
mjr | 77:0b96f6867312 | 2226 | IRPRC5() { } |
mjr | 77:0b96f6867312 | 2227 | |
mjr | 77:0b96f6867312 | 2228 | // name and ID |
mjr | 77:0b96f6867312 | 2229 | virtual const char *name() const { return "Philips RC5"; } |
mjr | 77:0b96f6867312 | 2230 | virtual int id() const { return IRPRO_RC5; } |
mjr | 77:0b96f6867312 | 2231 | |
mjr | 77:0b96f6867312 | 2232 | // code parameters |
mjr | 77:0b96f6867312 | 2233 | virtual float pwmPeriod(IRTXState *state) const { return 27.7777778e-6; } // 36kHz |
mjr | 77:0b96f6867312 | 2234 | virtual uint32_t minRxGap() const { return 3600; } |
mjr | 77:0b96f6867312 | 2235 | virtual uint32_t txGap(IRTXState *state) const { return 114000; } |
mjr | 77:0b96f6867312 | 2236 | virtual bool lsbFirst() const { return false; } |
mjr | 77:0b96f6867312 | 2237 | virtual int nbits() const { return 14; } |
mjr | 77:0b96f6867312 | 2238 | |
mjr | 77:0b96f6867312 | 2239 | // RC5 has no header; the start bit immediately follows the gap |
mjr | 77:0b96f6867312 | 2240 | virtual uint32_t tHeaderMark() const { return 0; } |
mjr | 77:0b96f6867312 | 2241 | virtual uint32_t tHeaderSpace() const { return 0; } |
mjr | 77:0b96f6867312 | 2242 | |
mjr | 77:0b96f6867312 | 2243 | // Manchester coding parameters |
mjr | 77:0b96f6867312 | 2244 | virtual int tHalfBit(int bit) const { return 1778/2; } |
mjr | 77:0b96f6867312 | 2245 | |
mjr | 77:0b96f6867312 | 2246 | // After the gap, the start of the next mark is in the middle |
mjr | 77:0b96f6867312 | 2247 | // of the start bit. A '1' start bit always follows the gap, |
mjr | 77:0b96f6867312 | 2248 | // and a '1' bit is represented by a space-to-mark transition, |
mjr | 77:0b96f6867312 | 2249 | // so the end of the gap is the middle of the start bit. |
mjr | 77:0b96f6867312 | 2250 | virtual void rxReset() |
mjr | 77:0b96f6867312 | 2251 | { |
mjr | 77:0b96f6867312 | 2252 | IRPManchester<uint16_t>::rxReset(); |
mjr | 77:0b96f6867312 | 2253 | halfBitPos = 1; |
mjr | 77:0b96f6867312 | 2254 | } |
mjr | 77:0b96f6867312 | 2255 | |
mjr | 77:0b96f6867312 | 2256 | virtual void codeToBitstream(IRTXState *state) |
mjr | 77:0b96f6867312 | 2257 | { |
mjr | 77:0b96f6867312 | 2258 | // add the start bit and toggle bit to the command code |
mjr | 77:0b96f6867312 | 2259 | state->nbits = nbits(); |
mjr | 77:0b96f6867312 | 2260 | state->bitstream = (state->cmdCode & DataMask) | StartMask; |
mjr | 77:0b96f6867312 | 2261 | if (state->toggle) |
mjr | 77:0b96f6867312 | 2262 | state->bitstream |= ToggleMask; |
mjr | 77:0b96f6867312 | 2263 | } |
mjr | 77:0b96f6867312 | 2264 | |
mjr | 77:0b96f6867312 | 2265 | // report the code |
mjr | 77:0b96f6867312 | 2266 | virtual void rxClose(IRRecvProIfc *receiver, bool ditto) |
mjr | 77:0b96f6867312 | 2267 | { |
mjr | 77:0b96f6867312 | 2268 | // make sure we have the full code |
mjr | 77:0b96f6867312 | 2269 | if (bit == 14) |
mjr | 77:0b96f6867312 | 2270 | { |
mjr | 77:0b96f6867312 | 2271 | // Pull out the toggle bit to report separately, and zero it |
mjr | 77:0b96f6867312 | 2272 | // out in the code word, so that a given key always reports |
mjr | 77:0b96f6867312 | 2273 | // the same code value. Also zero out the start bit, since |
mjr | 77:0b96f6867312 | 2274 | // it's really just structural. |
mjr | 77:0b96f6867312 | 2275 | reportCode( |
mjr | 77:0b96f6867312 | 2276 | receiver, id(), |
mjr | 77:0b96f6867312 | 2277 | code & ~(StartMask | ToggleMask), |
mjr | 77:0b96f6867312 | 2278 | (code & ToggleMask) != 0, bool3::null); |
mjr | 77:0b96f6867312 | 2279 | } |
mjr | 77:0b96f6867312 | 2280 | } |
mjr | 77:0b96f6867312 | 2281 | |
mjr | 77:0b96f6867312 | 2282 | // masks for the internal fields |
mjr | 77:0b96f6867312 | 2283 | static const int CmdMask = 0x3F; |
mjr | 77:0b96f6867312 | 2284 | static const int AddrMask = 0x1F << 6; |
mjr | 77:0b96f6867312 | 2285 | static const int ToggleMask = 1 << 11; |
mjr | 77:0b96f6867312 | 2286 | static const int FieldMask = 1 << 12; |
mjr | 77:0b96f6867312 | 2287 | static const int StartMask = 1 << 13; |
mjr | 77:0b96f6867312 | 2288 | static const int DataMask = FieldMask | AddrMask | CmdMask; |
mjr | 77:0b96f6867312 | 2289 | }; |
mjr | 77:0b96f6867312 | 2290 | |
mjr | 77:0b96f6867312 | 2291 | // ----------------------------------------------------------------------- |
mjr | 77:0b96f6867312 | 2292 | // |
mjr | 77:0b96f6867312 | 2293 | // RC6 protocol handler. This (along with RC5) is a quasi industry |
mjr | 77:0b96f6867312 | 2294 | // standard among European CE companies, so this protocol gives us |
mjr | 77:0b96f6867312 | 2295 | // compatibility with many devices from companies besides Philips. |
mjr | 77:0b96f6867312 | 2296 | // |
mjr | 77:0b96f6867312 | 2297 | // RC6 is a 21-bit Manchester-coded protocol. '1' bits are coded as |
mjr | 77:0b96f6867312 | 2298 | // High->Low transitions. The bits are nominally structured into |
mjr | 77:0b96f6867312 | 2299 | // fields as follows: |
mjr | 77:0b96f6867312 | 2300 | // |
mjr | 77:0b96f6867312 | 2301 | // S FFF T AAAAAAAA CCCCCCCC |
mjr | 77:0b96f6867312 | 2302 | // |
mjr | 77:0b96f6867312 | 2303 | // The fields are: |
mjr | 77:0b96f6867312 | 2304 | // |
mjr | 77:0b96f6867312 | 2305 | // S = start bit; always 1. We omit this from the reported value since |
mjr | 77:0b96f6867312 | 2306 | // it's always the same. |
mjr | 77:0b96f6867312 | 2307 | // |
mjr | 77:0b96f6867312 | 2308 | // F = "field". These bits are used to select different command sets, |
mjr | 77:0b96f6867312 | 2309 | // so they're effectively three additional bits (added as the three |
mjr | 77:0b96f6867312 | 2310 | // most significant bits) for the command code. |
mjr | 77:0b96f6867312 | 2311 | // |
mjr | 77:0b96f6867312 | 2312 | // A = "address", specifying the type of device the command is for. |
mjr | 77:0b96f6867312 | 2313 | // This has the same meanings as the address field in RC5. |
mjr | 77:0b96f6867312 | 2314 | // |
mjr | 77:0b96f6867312 | 2315 | // C = "command". The command code, specific to the device type |
mjr | 77:0b96f6867312 | 2316 | // in the address field. |
mjr | 77:0b96f6867312 | 2317 | // |
mjr | 77:0b96f6867312 | 2318 | // As with all protocols, we don't reproduce the internal field structure |
mjr | 77:0b96f6867312 | 2319 | // in the decoded command value; we simply pack all of the bits into a |
mjr | 77:0b96f6867312 | 2320 | // 18-bit integer, in the order shown above, field bits at the high end. |
mjr | 77:0b96f6867312 | 2321 | // (We omit the start bit, since it's a fixed element that's more properly |
mjr | 77:0b96f6867312 | 2322 | // part of the protocol than part of the code.) |
mjr | 77:0b96f6867312 | 2323 | // |
mjr | 77:0b96f6867312 | 2324 | // Note that this protocol contains an odd exception to normal Manchester |
mjr | 77:0b96f6867312 | 2325 | // coding for the "toggle" bit. This bit has a period 2X that of the other |
mjr | 77:0b96f6867312 | 2326 | // bits. |
mjr | 77:0b96f6867312 | 2327 | // |
mjr | 77:0b96f6867312 | 2328 | class IRPRC6: public IRPManchester<uint32_t> |
mjr | 77:0b96f6867312 | 2329 | { |
mjr | 77:0b96f6867312 | 2330 | public: |
mjr | 77:0b96f6867312 | 2331 | IRPRC6() { } |
mjr | 77:0b96f6867312 | 2332 | |
mjr | 77:0b96f6867312 | 2333 | // name and ID |
mjr | 77:0b96f6867312 | 2334 | virtual const char *name() const { return "Philips RC6"; } |
mjr | 77:0b96f6867312 | 2335 | virtual int id() const { return IRPRO_RC6; } |
mjr | 77:0b96f6867312 | 2336 | |
mjr | 77:0b96f6867312 | 2337 | // code parameters |
mjr | 77:0b96f6867312 | 2338 | virtual float pwmPeriod(IRTXState *state) const { return 27.7777778e-6; } // 36kHz |
mjr | 77:0b96f6867312 | 2339 | virtual uint32_t tHeaderMark() const { return 2695; } |
mjr | 77:0b96f6867312 | 2340 | virtual uint32_t tHeaderSpace() const { return 895; } |
mjr | 77:0b96f6867312 | 2341 | virtual uint32_t minRxGap() const { return 2650; } |
mjr | 77:0b96f6867312 | 2342 | virtual uint32_t txGap(IRTXState *state) const { return 107000; } |
mjr | 77:0b96f6867312 | 2343 | virtual bool lsbFirst() const { return false; } |
mjr | 77:0b96f6867312 | 2344 | virtual int nbits() const { return 21; } |
mjr | 77:0b96f6867312 | 2345 | |
mjr | 77:0b96f6867312 | 2346 | // Manchester coding parameters |
mjr | 77:0b96f6867312 | 2347 | virtual int spaceToMarkBit() const { return 0; } |
mjr | 77:0b96f6867312 | 2348 | |
mjr | 77:0b96f6867312 | 2349 | // RC6 has the weird exception to the bit timing in the Toggle bit, |
mjr | 77:0b96f6867312 | 2350 | // which is twice as long as the other bits. The toggle bit is the |
mjr | 77:0b96f6867312 | 2351 | // 5th bit (bit==4). |
mjr | 77:0b96f6867312 | 2352 | virtual int tHalfBit(int bit) const { return bit == 4 ? 895 : 895/2; } |
mjr | 77:0b96f6867312 | 2353 | |
mjr | 77:0b96f6867312 | 2354 | // create the bit stream for the command code |
mjr | 77:0b96f6867312 | 2355 | virtual void codeToBitstream(IRTXState *state) |
mjr | 77:0b96f6867312 | 2356 | { |
mjr | 77:0b96f6867312 | 2357 | // add the start bit and toggle bit to the command code |
mjr | 77:0b96f6867312 | 2358 | state->nbits = nbits(); |
mjr | 77:0b96f6867312 | 2359 | state->bitstream = (state->cmdCode & DataMask) | StartMask; |
mjr | 77:0b96f6867312 | 2360 | if (state->toggle) |
mjr | 77:0b96f6867312 | 2361 | state->bitstream |= ToggleMask; |
mjr | 77:0b96f6867312 | 2362 | } |
mjr | 77:0b96f6867312 | 2363 | |
mjr | 77:0b96f6867312 | 2364 | // report the code |
mjr | 77:0b96f6867312 | 2365 | virtual void rxClose(IRRecvProIfc *receiver, bool ditto) |
mjr | 77:0b96f6867312 | 2366 | { |
mjr | 77:0b96f6867312 | 2367 | // make sure we have the full code |
mjr | 77:0b96f6867312 | 2368 | if (bit == nbits()) |
mjr | 77:0b96f6867312 | 2369 | { |
mjr | 77:0b96f6867312 | 2370 | // Pull out the toggle bit to report separately, and zero it |
mjr | 77:0b96f6867312 | 2371 | // out in the code word, so that a given key always reports |
mjr | 77:0b96f6867312 | 2372 | // the same code value. Also clear the start bit, since it's |
mjr | 77:0b96f6867312 | 2373 | // just structural. |
mjr | 77:0b96f6867312 | 2374 | reportCode( |
mjr | 77:0b96f6867312 | 2375 | receiver, id(), |
mjr | 77:0b96f6867312 | 2376 | code & ~(ToggleMask | StartMask), |
mjr | 77:0b96f6867312 | 2377 | (code & ToggleMask) != 0, bool3::null); |
mjr | 77:0b96f6867312 | 2378 | } |
mjr | 77:0b96f6867312 | 2379 | } |
mjr | 77:0b96f6867312 | 2380 | |
mjr | 77:0b96f6867312 | 2381 | // field masks |
mjr | 77:0b96f6867312 | 2382 | static const int CmdMask = 0xFF; |
mjr | 77:0b96f6867312 | 2383 | static const int AddrMask = 0xFF << 8; |
mjr | 77:0b96f6867312 | 2384 | static const int ToggleMask = 1 << 16; |
mjr | 77:0b96f6867312 | 2385 | static const int FieldMask = 0x07 << 17; |
mjr | 77:0b96f6867312 | 2386 | static const int StartMask = 1 << 20; |
mjr | 77:0b96f6867312 | 2387 | static const int DataMask = FieldMask | AddrMask | CmdMask; |
mjr | 77:0b96f6867312 | 2388 | }; |
mjr | 77:0b96f6867312 | 2389 | |
mjr | 77:0b96f6867312 | 2390 | // ----------------------------------------------------------------------- |
mjr | 77:0b96f6867312 | 2391 | // |
mjr | 77:0b96f6867312 | 2392 | // Ortek MCE remote. This device uses Manchester coding, with either 16 |
mjr | 77:0b96f6867312 | 2393 | // or 17 bits, depending on which keys are pressed. The low-order 5 bits |
mjr | 77:0b96f6867312 | 2394 | // of either code are the device code. The interpretation of the rest of |
mjr | 77:0b96f6867312 | 2395 | // the bits depends on the device code. Bits are sent least-significant |
mjr | 77:0b96f6867312 | 2396 | // first (little-endian) for interpretation as integer values. |
mjr | 77:0b96f6867312 | 2397 | // |
mjr | 77:0b96f6867312 | 2398 | // Device code = 0x14 = Mouse. This is a 16-bit code, with the fields |
mjr | 77:0b96f6867312 | 2399 | // as follows (low bit first): |
mjr | 77:0b96f6867312 | 2400 | // |
mjr | 77:0b96f6867312 | 2401 | // DDDDD L R MMMMM CCCC |
mjr | 77:0b96f6867312 | 2402 | // |
mjr | 77:0b96f6867312 | 2403 | // D = device code (common field for all codes) |
mjr | 77:0b96f6867312 | 2404 | // L = left-click (1=pressed, 0=not pressed) |
mjr | 77:0b96f6867312 | 2405 | // R = right-click (1=pressed, 0=not pressed) |
mjr | 77:0b96f6867312 | 2406 | // M = mouse movement direction. This type of device is a mouse stick |
mjr | 77:0b96f6867312 | 2407 | // rather than a mouse, so it gives only the direction of travel rather |
mjr | 77:0b96f6867312 | 2408 | // than X/Y motion in pixels. There are 15 increments of motion numbered |
mjr | 77:0b96f6867312 | 2409 | // 0 to 15, starting with 0 at North (straight up), going clockwise: |
mjr | 77:0b96f6867312 | 2410 | // 0 = N |
mjr | 77:0b96f6867312 | 2411 | // 1 = NNE |
mjr | 77:0b96f6867312 | 2412 | // 2 = NE |
mjr | 77:0b96f6867312 | 2413 | // 3 = ENE |
mjr | 77:0b96f6867312 | 2414 | // 4 = E |
mjr | 77:0b96f6867312 | 2415 | // 5 = ESE |
mjr | 77:0b96f6867312 | 2416 | // 6 = SE |
mjr | 77:0b96f6867312 | 2417 | // 7 = SSE |
mjr | 77:0b96f6867312 | 2418 | // 8 = S |
mjr | 77:0b96f6867312 | 2419 | // 9 = SSW |
mjr | 77:0b96f6867312 | 2420 | // 10 = SW |
mjr | 77:0b96f6867312 | 2421 | // 11 = WSW |
mjr | 77:0b96f6867312 | 2422 | // 12 = W |
mjr | 77:0b96f6867312 | 2423 | // 13 = WNW |
mjr | 77:0b96f6867312 | 2424 | // 14 = NW |
mjr | 77:0b96f6867312 | 2425 | // 15 = NNW |
mjr | 77:0b96f6867312 | 2426 | // The MMMMM field contains 0x10 | the value above when a mouse motion |
mjr | 77:0b96f6867312 | 2427 | // direction is pressed, so the codes will be 0x10 (N), 0x11 (NNE), ..., |
mjr | 77:0b96f6867312 | 2428 | // 0x1F (NNW). These are shifted left by two in the reported function |
mjr | 77:0b96f6867312 | 2429 | // code, so you'll actually see 0x40 ... 0x7C. |
mjr | 77:0b96f6867312 | 2430 | // C = checksum |
mjr | 77:0b96f6867312 | 2431 | // |
mjr | 77:0b96f6867312 | 2432 | // There's no equivalent of the "position" code (see device 0x15 below) for |
mjr | 77:0b96f6867312 | 2433 | // the mouse commands, so there's no coding for auto-repeat per se. The |
mjr | 77:0b96f6867312 | 2434 | // remote does let the receiver when the last key is released, though, by |
mjr | 77:0b96f6867312 | 2435 | // sending one code word with the L, R, and M bits all set to zero; this |
mjr | 77:0b96f6867312 | 2436 | // apparently signifies "key up". One of these is always sent after the |
mjr | 77:0b96f6867312 | 2437 | // last key is released, but it's not sent between auto-repeated codes, |
mjr | 77:0b96f6867312 | 2438 | // so if you hold a key down you'll see a sequence of repeating codes |
mjr | 77:0b96f6867312 | 2439 | // for that key (or key combination), followed by one "key up" code when |
mjr | 77:0b96f6867312 | 2440 | // you release the last key. |
mjr | 77:0b96f6867312 | 2441 | // |
mjr | 77:0b96f6867312 | 2442 | // Receivers who interpret these codes will probably want to separate the |
mjr | 77:0b96f6867312 | 2443 | // L, R, and M bits and treat them separately, rather than treating any given |
mjr | 77:0b96f6867312 | 2444 | // combination as a discrete command. The reason is that the L and R bits |
mjr | 77:0b96f6867312 | 2445 | // can combine with the mouse movement field when a left-click or right-click |
mjr | 77:0b96f6867312 | 2446 | // button is held down while the movement keys are pressed. This can be used |
mjr | 77:0b96f6867312 | 2447 | // to perform click-and-drag operations on a GUI. |
mjr | 77:0b96f6867312 | 2448 | // |
mjr | 77:0b96f6867312 | 2449 | // Device code = 0x15 = MCE buttons. This is a 17-bit code, with the |
mjr | 77:0b96f6867312 | 2450 | // fields as follows (low bit first): |
mjr | 77:0b96f6867312 | 2451 | // |
mjr | 77:0b96f6867312 | 2452 | // DDDDD PP FFFFFF CCCC |
mjr | 77:0b96f6867312 | 2453 | // |
mjr | 77:0b96f6867312 | 2454 | // D = device code (common field for all codes) |
mjr | 77:0b96f6867312 | 2455 | // P = "position code", for sensing repeats: 00=first, 01=middle, 10=last |
mjr | 77:0b96f6867312 | 2456 | // F = function (key code) |
mjr | 77:0b96f6867312 | 2457 | // C = checksum |
mjr | 77:0b96f6867312 | 2458 | // |
mjr | 77:0b96f6867312 | 2459 | // The checksum is the 3 + the total number of '1' bits in the other fields |
mjr | 77:0b96f6867312 | 2460 | // combined. |
mjr | 77:0b96f6867312 | 2461 | // |
mjr | 77:0b96f6867312 | 2462 | // We report these codes in our own 16-bit format, with D in the high byte, |
mjr | 77:0b96f6867312 | 2463 | // and the function code in the low byte. For the mouse commands (device 0x14), |
mjr | 77:0b96f6867312 | 2464 | // the low byte is (high bit to low bit) 0MMMMMRL. For the MCE buttons, the |
mjr | 77:0b96f6867312 | 2465 | // low byte is the function code (F). We drop the position code for uniformity |
mjr | 77:0b96f6867312 | 2466 | // with other protocols and instead report a synthetic toggle bit, which we |
mjr | 77:0b96f6867312 | 2467 | // flip whenever we see a new transmission as signified by P=0. We don't do |
mjr | 77:0b96f6867312 | 2468 | // anything with the toggle bit on mouse commands. |
mjr | 77:0b96f6867312 | 2469 | // |
mjr | 77:0b96f6867312 | 2470 | class IRPOrtekMCE: public IRPManchester<uint32_t> |
mjr | 77:0b96f6867312 | 2471 | { |
mjr | 77:0b96f6867312 | 2472 | public: |
mjr | 77:0b96f6867312 | 2473 | IRPOrtekMCE() |
mjr | 77:0b96f6867312 | 2474 | { |
mjr | 77:0b96f6867312 | 2475 | toggle = 0; |
mjr | 77:0b96f6867312 | 2476 | } |
mjr | 77:0b96f6867312 | 2477 | |
mjr | 77:0b96f6867312 | 2478 | // name and ID |
mjr | 77:0b96f6867312 | 2479 | virtual const char *name() const { return "OrtekMCE"; } |
mjr | 77:0b96f6867312 | 2480 | virtual int id() const { return IRPRO_ORTEKMCE; } |
mjr | 77:0b96f6867312 | 2481 | |
mjr | 77:0b96f6867312 | 2482 | // code parameters |
mjr | 77:0b96f6867312 | 2483 | virtual float pwmPeriod(IRTXState *state) const { return 25.906736e-6; } // 38.6kHz |
mjr | 77:0b96f6867312 | 2484 | virtual uint32_t tHeaderMark() const { return 4*480; } |
mjr | 77:0b96f6867312 | 2485 | virtual uint32_t tHeaderSpace() const { return 1*480; } |
mjr | 77:0b96f6867312 | 2486 | virtual bool headerSpaceToData() const { return true; } |
mjr | 77:0b96f6867312 | 2487 | virtual uint32_t minRxGap() const { return 32000; } |
mjr | 77:0b96f6867312 | 2488 | virtual uint32_t txGap(IRTXState *state) const { return 40000; } |
mjr | 77:0b96f6867312 | 2489 | |
mjr | 77:0b96f6867312 | 2490 | // We always require a final rep with position code 2, so |
mjr | 77:0b96f6867312 | 2491 | // ask for a minimum of 3 reps (0, 1, 2). |
mjr | 77:0b96f6867312 | 2492 | virtual int txMinReps(IRTXState *) const { return 3; } |
mjr | 77:0b96f6867312 | 2493 | |
mjr | 77:0b96f6867312 | 2494 | // Manchester coding parameters |
mjr | 77:0b96f6867312 | 2495 | virtual int nbits() const { return 17; } |
mjr | 77:0b96f6867312 | 2496 | virtual int spaceToMarkBit() const { return 1; } |
mjr | 77:0b96f6867312 | 2497 | virtual int tHalfBit(int bit) const { return 480; } |
mjr | 77:0b96f6867312 | 2498 | |
mjr | 77:0b96f6867312 | 2499 | // encode the bitstream for a given code |
mjr | 77:0b96f6867312 | 2500 | virtual void codeToBitstream(IRTXState *state) |
mjr | 77:0b96f6867312 | 2501 | { |
mjr | 77:0b96f6867312 | 2502 | // Set the repeat count. If we're on any repeat > 0 and |
mjr | 77:0b96f6867312 | 2503 | // the key is still pressed, reset the repeat counter to 1. |
mjr | 77:0b96f6867312 | 2504 | // All repeats are "1" as long as the key is down. As soon |
mjr | 77:0b96f6867312 | 2505 | // as the key is up, advance directly to "2", even if there |
mjr | 77:0b96f6867312 | 2506 | // was never a "1". Our txMinRep() count is 2, so this will |
mjr | 77:0b96f6867312 | 2507 | // ensure that we always transmit that last position 2 code, |
mjr | 77:0b96f6867312 | 2508 | // as required by the protocol. |
mjr | 77:0b96f6867312 | 2509 | if (state->rep > 0) |
mjr | 77:0b96f6867312 | 2510 | state->rep = state->pressed ? 1 : 2; |
mjr | 77:0b96f6867312 | 2511 | |
mjr | 77:0b96f6867312 | 2512 | // Function field and checksum bit position - we'll fill |
mjr | 77:0b96f6867312 | 2513 | // these in momentarily according to the device type. |
mjr | 77:0b96f6867312 | 2514 | uint32_t f; |
mjr | 77:0b96f6867312 | 2515 | int c_shift; |
mjr | 77:0b96f6867312 | 2516 | |
mjr | 77:0b96f6867312 | 2517 | // check the product code to determine the encoding |
mjr | 77:0b96f6867312 | 2518 | uint32_t cmdcode = uint32_t(state->cmdCode); |
mjr | 77:0b96f6867312 | 2519 | int dev = (int(cmdcode) >> 8) & 0xff; |
mjr | 77:0b96f6867312 | 2520 | if (dev == 0x14) |
mjr | 77:0b96f6867312 | 2521 | { |
mjr | 77:0b96f6867312 | 2522 | // Mouse function: DDDDD L R MMMMM CCCC |
mjr | 77:0b96f6867312 | 2523 | // There's no position field in the mouse messages, but |
mjr | 77:0b96f6867312 | 2524 | // we always have to send a final message with all bits |
mjr | 77:0b96f6867312 | 2525 | // zeroed. Do this when our rep count is 2, indicating |
mjr | 77:0b96f6867312 | 2526 | // that this is the final close-out rep. |
mjr | 77:0b96f6867312 | 2527 | if (state->rep == 2) |
mjr | 77:0b96f6867312 | 2528 | f = 0; |
mjr | 77:0b96f6867312 | 2529 | else |
mjr | 77:0b96f6867312 | 2530 | f = cmdcode & 0xff; |
mjr | 77:0b96f6867312 | 2531 | |
mjr | 77:0b96f6867312 | 2532 | // the checksum starts at the 12th bit |
mjr | 77:0b96f6867312 | 2533 | c_shift = 12; |
mjr | 77:0b96f6867312 | 2534 | } |
mjr | 77:0b96f6867312 | 2535 | else if (dev == 0x15) |
mjr | 77:0b96f6867312 | 2536 | { |
mjr | 77:0b96f6867312 | 2537 | // MCE button: DDDDD PP FFFFFF CCCC |
mjr | 77:0b96f6867312 | 2538 | |
mjr | 77:0b96f6867312 | 2539 | // Set the position field to the rep counter |
mjr | 77:0b96f6867312 | 2540 | int p = state->rep; |
mjr | 77:0b96f6867312 | 2541 | |
mjr | 77:0b96f6867312 | 2542 | // fill in the function fields: PP FFFFFF |
mjr | 77:0b96f6867312 | 2543 | f = (p) | ((cmdcode & 0x3F) << 2); |
mjr | 77:0b96f6867312 | 2544 | |
mjr | 77:0b96f6867312 | 2545 | // the checksum starts at the 13th bit |
mjr | 77:0b96f6867312 | 2546 | c_shift = 13; |
mjr | 77:0b96f6867312 | 2547 | } |
mjr | 77:0b96f6867312 | 2548 | else |
mjr | 77:0b96f6867312 | 2549 | { |
mjr | 77:0b96f6867312 | 2550 | // unknown device code - just transmit the low byte as given |
mjr | 77:0b96f6867312 | 2551 | f = cmdcode & 0xff; |
mjr | 77:0b96f6867312 | 2552 | c_shift = 13; |
mjr | 77:0b96f6867312 | 2553 | } |
mjr | 77:0b96f6867312 | 2554 | |
mjr | 77:0b96f6867312 | 2555 | // construct the bit vector with the device code in the low 5 |
mjr | 77:0b96f6867312 | 2556 | // bits and code in the next 7 or 8 bits |
mjr | 77:0b96f6867312 | 2557 | uint32_t bitvec = dev | (f << 5); |
mjr | 77:0b96f6867312 | 2558 | |
mjr | 77:0b96f6867312 | 2559 | // figure the checksum: it's the number of '1' bits in |
mjr | 77:0b96f6867312 | 2560 | // the rest of the fields, plus 3 |
mjr | 77:0b96f6867312 | 2561 | uint32_t checksum = 3; |
mjr | 77:0b96f6867312 | 2562 | for (uint32_t v = bitvec ; v != 0 ; checksum += (v & 1), v >>= 1) ; |
mjr | 77:0b96f6867312 | 2563 | |
mjr | 77:0b96f6867312 | 2564 | // construct the bit stream |
mjr | 77:0b96f6867312 | 2565 | state->bitstream = bitvec | (checksum << c_shift); |
mjr | 77:0b96f6867312 | 2566 | state->nbits = c_shift + 4; |
mjr | 77:0b96f6867312 | 2567 | } |
mjr | 77:0b96f6867312 | 2568 | |
mjr | 77:0b96f6867312 | 2569 | // report the code value |
mjr | 77:0b96f6867312 | 2570 | virtual void rxClose(IRRecvProIfc *receiver, bool ditto) |
mjr | 77:0b96f6867312 | 2571 | { |
mjr | 77:0b96f6867312 | 2572 | // common field masks |
mjr | 77:0b96f6867312 | 2573 | const uint32_t D_mask = 0x001F; // lowest 5 bits = device code |
mjr | 77:0b96f6867312 | 2574 | |
mjr | 77:0b96f6867312 | 2575 | // pull out the common fields |
mjr | 77:0b96f6867312 | 2576 | uint32_t d = code & D_mask; |
mjr | 77:0b96f6867312 | 2577 | |
mjr | 77:0b96f6867312 | 2578 | // assume no post-toggle |
mjr | 77:0b96f6867312 | 2579 | bool postToggle = false; |
mjr | 77:0b96f6867312 | 2580 | |
mjr | 77:0b96f6867312 | 2581 | // check for the different code types, and pull out the fields |
mjr | 77:0b96f6867312 | 2582 | uint32_t f, c, checkedBits; |
mjr | 77:0b96f6867312 | 2583 | if (bit == 16 && d == 0x14) |
mjr | 77:0b96f6867312 | 2584 | { |
mjr | 77:0b96f6867312 | 2585 | // field masks for the mouse codes |
mjr | 77:0b96f6867312 | 2586 | const int F_shift = 5; |
mjr | 77:0b96f6867312 | 2587 | const uint32_t F_mask = 0x7f << F_shift; // next 7 bits |
mjr | 77:0b96f6867312 | 2588 | const int C_shift = 12; |
mjr | 77:0b96f6867312 | 2589 | const uint32_t C_mask = 0x3F << C_shift; // top 4 bits |
mjr | 77:0b96f6867312 | 2590 | |
mjr | 77:0b96f6867312 | 2591 | // separate the fields |
mjr | 77:0b96f6867312 | 2592 | f = (code & F_mask) >> F_shift; |
mjr | 77:0b96f6867312 | 2593 | c = (code & C_mask) >> C_shift; |
mjr | 77:0b96f6867312 | 2594 | checkedBits = code & ~C_mask; |
mjr | 77:0b96f6867312 | 2595 | |
mjr | 77:0b96f6867312 | 2596 | // if the F bits are all zero, take it as a "key up" sequence, |
mjr | 77:0b96f6867312 | 2597 | // so flip the toggle bit next time |
mjr | 77:0b96f6867312 | 2598 | if (f == 0) |
mjr | 77:0b96f6867312 | 2599 | postToggle = true; |
mjr | 77:0b96f6867312 | 2600 | } |
mjr | 77:0b96f6867312 | 2601 | else if (bit == 17 && d == 0x15) |
mjr | 77:0b96f6867312 | 2602 | { |
mjr | 77:0b96f6867312 | 2603 | // 17 bits with device code 0x15 = MCE keyboard commands |
mjr | 77:0b96f6867312 | 2604 | |
mjr | 77:0b96f6867312 | 2605 | // field masks for the keyboard codes |
mjr | 77:0b96f6867312 | 2606 | const int P_shift = 5; |
mjr | 77:0b96f6867312 | 2607 | const uint32_t P_mask = 0x0003 << P_shift; // next 2 bits |
mjr | 77:0b96f6867312 | 2608 | const int F_shift = 7; |
mjr | 77:0b96f6867312 | 2609 | const uint32_t F_mask = 0x3F << F_shift; // next 6 bits |
mjr | 77:0b96f6867312 | 2610 | const int C_shift = 13; |
mjr | 77:0b96f6867312 | 2611 | const uint32_t C_mask = 0x0F << C_shift; // top 4 bits |
mjr | 77:0b96f6867312 | 2612 | |
mjr | 77:0b96f6867312 | 2613 | // separate the fields |
mjr | 77:0b96f6867312 | 2614 | uint32_t p = (code & P_mask) >> P_shift; |
mjr | 77:0b96f6867312 | 2615 | f = (code & F_mask) >> F_shift; |
mjr | 77:0b96f6867312 | 2616 | c = (code & C_mask) >> C_shift; |
mjr | 77:0b96f6867312 | 2617 | checkedBits = code & ~C_mask; |
mjr | 77:0b96f6867312 | 2618 | |
mjr | 77:0b96f6867312 | 2619 | /// validate the position code - 0,1,2 are valid, 3 is invalid |
mjr | 77:0b96f6867312 | 2620 | if (p == (0x03 << P_shift)) |
mjr | 77:0b96f6867312 | 2621 | return; |
mjr | 77:0b96f6867312 | 2622 | |
mjr | 77:0b96f6867312 | 2623 | // flip the toggle bit if this is the first frame in a group |
mjr | 77:0b96f6867312 | 2624 | // as signified by P=0 |
mjr | 77:0b96f6867312 | 2625 | if (p == 0) |
mjr | 77:0b96f6867312 | 2626 | toggle ^= 1; |
mjr | 77:0b96f6867312 | 2627 | } |
mjr | 77:0b96f6867312 | 2628 | else |
mjr | 77:0b96f6867312 | 2629 | { |
mjr | 77:0b96f6867312 | 2630 | // invalid bit length or device code - reject the code |
mjr | 77:0b96f6867312 | 2631 | return; |
mjr | 77:0b96f6867312 | 2632 | } |
mjr | 77:0b96f6867312 | 2633 | |
mjr | 77:0b96f6867312 | 2634 | // count the '1' bits in the other fields to get the checksum value |
mjr | 77:0b96f6867312 | 2635 | int ones = 0; |
mjr | 77:0b96f6867312 | 2636 | for ( ; checkedBits != 0 ; checkedBits >>= 1) |
mjr | 77:0b96f6867312 | 2637 | ones += (checkedBits & 1); |
mjr | 77:0b96f6867312 | 2638 | |
mjr | 77:0b96f6867312 | 2639 | // check the checksum |
mjr | 77:0b96f6867312 | 2640 | if (c != ones + 3) |
mjr | 77:0b96f6867312 | 2641 | return; |
mjr | 77:0b96f6867312 | 2642 | |
mjr | 77:0b96f6867312 | 2643 | // rearrange the code into our canonical format and report it |
mjr | 77:0b96f6867312 | 2644 | // along with the synthetic toggle |
mjr | 77:0b96f6867312 | 2645 | reportCode(receiver, id(), (d << 8) | f, toggle, bool3::null); |
mjr | 77:0b96f6867312 | 2646 | |
mjr | 77:0b96f6867312 | 2647 | // flip the toggle for next time, if desired |
mjr | 77:0b96f6867312 | 2648 | if (postToggle) |
mjr | 77:0b96f6867312 | 2649 | toggle ^= 1; |
mjr | 77:0b96f6867312 | 2650 | } |
mjr | 77:0b96f6867312 | 2651 | |
mjr | 77:0b96f6867312 | 2652 | // synthetic toggle bit |
mjr | 77:0b96f6867312 | 2653 | uint8_t toggle : 1; |
mjr | 77:0b96f6867312 | 2654 | }; |
mjr | 77:0b96f6867312 | 2655 | |
mjr | 77:0b96f6867312 | 2656 | // ----------------------------------------------------------------------- |
mjr | 77:0b96f6867312 | 2657 | // |
mjr | 77:0b96f6867312 | 2658 | // Sony protocol handler. Sony uses mark-length encoding, with |
mjr | 77:0b96f6867312 | 2659 | // 8, 12, 15, and 20 bit code words. We use a common receiver |
mjr | 77:0b96f6867312 | 2660 | // base class that determines how many bits are in the code from |
mjr | 77:0b96f6867312 | 2661 | // the input itself, plus separate base classes for each bit size. |
mjr | 77:0b96f6867312 | 2662 | // The common receiver class reports the appropriate sub-protocol |
mjr | 77:0b96f6867312 | 2663 | // ID for the bit size, so that the appropriate sender class is |
mjr | 77:0b96f6867312 | 2664 | // used if we want to transmit the captured code. |
mjr | 77:0b96f6867312 | 2665 | // |
mjr | 77:0b96f6867312 | 2666 | class IRPSony: public IRPMarkLength<uint32_t> |
mjr | 77:0b96f6867312 | 2667 | { |
mjr | 77:0b96f6867312 | 2668 | public: |
mjr | 77:0b96f6867312 | 2669 | IRPSony() { } |
mjr | 77:0b96f6867312 | 2670 | |
mjr | 77:0b96f6867312 | 2671 | // Name and ID. We handle all of the Sony bit size variations, |
mjr | 77:0b96f6867312 | 2672 | // so we use IRPRO_NONE as our nominal ID, but set the actual code |
mjr | 77:0b96f6867312 | 2673 | // on a successful receive based on the actual bit size. We |
mjr | 77:0b96f6867312 | 2674 | // transmit any of the Sony codes. |
mjr | 77:0b96f6867312 | 2675 | virtual const char *name() const { return "Sony"; } |
mjr | 77:0b96f6867312 | 2676 | virtual int id() const { return IRPRO_NONE; } |
mjr | 77:0b96f6867312 | 2677 | virtual bool isSenderFor(int protocolId) const |
mjr | 77:0b96f6867312 | 2678 | { |
mjr | 77:0b96f6867312 | 2679 | return protocolId == IRPRO_SONY8 |
mjr | 77:0b96f6867312 | 2680 | || protocolId == IRPRO_SONY12 |
mjr | 77:0b96f6867312 | 2681 | || protocolId == IRPRO_SONY15 |
mjr | 77:0b96f6867312 | 2682 | || protocolId == IRPRO_SONY20; |
mjr | 77:0b96f6867312 | 2683 | } |
mjr | 77:0b96f6867312 | 2684 | |
mjr | 77:0b96f6867312 | 2685 | // code boundary parameters |
mjr | 77:0b96f6867312 | 2686 | virtual uint32_t tHeaderMark() const { return 2400; } |
mjr | 77:0b96f6867312 | 2687 | virtual uint32_t tHeaderSpace() const { return 600; } |
mjr | 77:0b96f6867312 | 2688 | virtual uint32_t minRxGap() const { return 5000; } |
mjr | 77:0b96f6867312 | 2689 | virtual uint32_t txGap(IRTXState *state) const { return 45000; } |
mjr | 77:0b96f6867312 | 2690 | |
mjr | 77:0b96f6867312 | 2691 | // mark-length coding parameters |
mjr | 77:0b96f6867312 | 2692 | virtual int nbits() const { return 20; } // maximum - can also be 8, 12, or 15 |
mjr | 77:0b96f6867312 | 2693 | virtual int tSpace() const { return 600; } |
mjr | 77:0b96f6867312 | 2694 | virtual int tZero() const { return 600; } |
mjr | 77:0b96f6867312 | 2695 | virtual int tOne() const { return 1200; } |
mjr | 77:0b96f6867312 | 2696 | |
mjr | 77:0b96f6867312 | 2697 | // Sony requires at least 3 sends per key press |
mjr | 77:0b96f6867312 | 2698 | virtual int txMinReps(IRTXState *state) const { return 3; } |
mjr | 77:0b96f6867312 | 2699 | |
mjr | 77:0b96f6867312 | 2700 | // set up the bitstream for a code value |
mjr | 77:0b96f6867312 | 2701 | virtual void codeToBitstream(IRTXState *state) |
mjr | 77:0b96f6867312 | 2702 | { |
mjr | 77:0b96f6867312 | 2703 | // store the code, and set the bit counter according to |
mjr | 77:0b96f6867312 | 2704 | // the Sony protocol subtype |
mjr | 77:0b96f6867312 | 2705 | state->bitstream = state->cmdCode; |
mjr | 77:0b96f6867312 | 2706 | switch (state->protocolId) |
mjr | 77:0b96f6867312 | 2707 | { |
mjr | 77:0b96f6867312 | 2708 | case IRPRO_SONY8: |
mjr | 77:0b96f6867312 | 2709 | state->nbits = 8; |
mjr | 77:0b96f6867312 | 2710 | break; |
mjr | 77:0b96f6867312 | 2711 | |
mjr | 77:0b96f6867312 | 2712 | case IRPRO_SONY12: |
mjr | 77:0b96f6867312 | 2713 | state->nbits = 12; |
mjr | 77:0b96f6867312 | 2714 | break; |
mjr | 77:0b96f6867312 | 2715 | |
mjr | 77:0b96f6867312 | 2716 | case IRPRO_SONY15: |
mjr | 77:0b96f6867312 | 2717 | state->nbits = 15; |
mjr | 77:0b96f6867312 | 2718 | break; |
mjr | 77:0b96f6867312 | 2719 | |
mjr | 77:0b96f6867312 | 2720 | case IRPRO_SONY20: |
mjr | 77:0b96f6867312 | 2721 | default: |
mjr | 77:0b96f6867312 | 2722 | state->nbits = 20; |
mjr | 77:0b96f6867312 | 2723 | break; |
mjr | 77:0b96f6867312 | 2724 | } |
mjr | 77:0b96f6867312 | 2725 | } |
mjr | 77:0b96f6867312 | 2726 | |
mjr | 77:0b96f6867312 | 2727 | // report a code value |
mjr | 77:0b96f6867312 | 2728 | virtual void rxClose(IRRecvProIfc *receiver, bool ditto) |
mjr | 77:0b96f6867312 | 2729 | { |
mjr | 77:0b96f6867312 | 2730 | // we have a valid code if we have 8, 12, 15, or 20 bits |
mjr | 77:0b96f6867312 | 2731 | switch (bit) |
mjr | 77:0b96f6867312 | 2732 | { |
mjr | 77:0b96f6867312 | 2733 | case 8: |
mjr | 77:0b96f6867312 | 2734 | reportCode(receiver, IRPRO_SONY8, code, bool3::null, bool3::null); |
mjr | 77:0b96f6867312 | 2735 | break; |
mjr | 77:0b96f6867312 | 2736 | |
mjr | 77:0b96f6867312 | 2737 | case 12: |
mjr | 77:0b96f6867312 | 2738 | reportCode(receiver, IRPRO_SONY12, code, bool3::null, bool3::null); |
mjr | 77:0b96f6867312 | 2739 | break; |
mjr | 77:0b96f6867312 | 2740 | |
mjr | 77:0b96f6867312 | 2741 | case 15: |
mjr | 77:0b96f6867312 | 2742 | reportCode(receiver, IRPRO_SONY15, code, bool3::null, bool3::null); |
mjr | 77:0b96f6867312 | 2743 | break; |
mjr | 77:0b96f6867312 | 2744 | |
mjr | 77:0b96f6867312 | 2745 | case 20: |
mjr | 77:0b96f6867312 | 2746 | reportCode(receiver, IRPRO_SONY20, code, bool3::null, bool3::null); |
mjr | 77:0b96f6867312 | 2747 | break; |
mjr | 77:0b96f6867312 | 2748 | } |
mjr | 77:0b96f6867312 | 2749 | } |
mjr | 77:0b96f6867312 | 2750 | }; |
mjr | 77:0b96f6867312 | 2751 | |
mjr | 77:0b96f6867312 | 2752 | // ----------------------------------------------------------------------- |
mjr | 77:0b96f6867312 | 2753 | // |
mjr | 77:0b96f6867312 | 2754 | // Denon protocol handler. Denon uses a 15-bit space-length coding, but |
mjr | 77:0b96f6867312 | 2755 | // has two unusual features. First, it has no header; it just starts |
mjr | 77:0b96f6867312 | 2756 | // right off with the data bits. Second, every code is transmitted a |
mjr | 77:0b96f6867312 | 2757 | // second time, starting 65ms after the start of the first iteration, |
mjr | 77:0b96f6867312 | 2758 | // with the "F" and "S" fields inverted: |
mjr | 77:0b96f6867312 | 2759 | // |
mjr | 77:0b96f6867312 | 2760 | // DDDDD FFFFFFFF SS |
mjr | 77:0b96f6867312 | 2761 | // |
mjr | 77:0b96f6867312 | 2762 | // D = device code |
mjr | 77:0b96f6867312 | 2763 | // F = function code (key) |
mjr | 77:0b96f6867312 | 2764 | // S = sequence; 0 for the first half of the pair, 1 for the second half |
mjr | 77:0b96f6867312 | 2765 | // |
mjr | 77:0b96f6867312 | 2766 | // The first half-code is transmitted with the actual function code and |
mjr | 77:0b96f6867312 | 2767 | // SS=00; the second half uses ~F (1's complement of the function code) |
mjr | 77:0b96f6867312 | 2768 | // and SS=11. |
mjr | 77:0b96f6867312 | 2769 | // |
mjr | 77:0b96f6867312 | 2770 | // Many learning remotes get this wrong, only learning one or the other |
mjr | 77:0b96f6867312 | 2771 | // half. That's understandable, since learning remotes often only collect |
mjr | 77:0b96f6867312 | 2772 | // the raw bit codes and thus wouldn't be able to detect the multi-code |
mjr | 77:0b96f6867312 | 2773 | // structure. It's a little less forgiveable that some pre-programmed |
mjr | 77:0b96f6867312 | 2774 | // universal remotes, such as the Logitech Harmony series, also miss the |
mjr | 77:0b96f6867312 | 2775 | // half-code nuance. To be robust against errant remotes, we'll accept |
mjr | 77:0b96f6867312 | 2776 | // and report lone half-codes, and we'll invert the F bits if the S bits |
mjr | 77:0b96f6867312 | 2777 | // indicate that a second half was learned, to ensure that the client sees |
mjr | 77:0b96f6867312 | 2778 | // the correct version of the codes. We'll also internally track the |
mjr | 77:0b96f6867312 | 2779 | // pairs so that, when we *do* see a properly formed inverted pair, we'll |
mjr | 77:0b96f6867312 | 2780 | // only report one code out of the pair to the client. |
mjr | 77:0b96f6867312 | 2781 | // |
mjr | 77:0b96f6867312 | 2782 | class IRPDenon: public IRPSpaceLength<uint16_t> |
mjr | 77:0b96f6867312 | 2783 | { |
mjr | 77:0b96f6867312 | 2784 | public: |
mjr | 77:0b96f6867312 | 2785 | IRPDenon() |
mjr | 77:0b96f6867312 | 2786 | { |
mjr | 77:0b96f6867312 | 2787 | prvCode = 0; |
mjr | 77:0b96f6867312 | 2788 | } |
mjr | 77:0b96f6867312 | 2789 | |
mjr | 77:0b96f6867312 | 2790 | // name and ID |
mjr | 77:0b96f6867312 | 2791 | virtual const char *name() const { return "Denon"; } |
mjr | 77:0b96f6867312 | 2792 | virtual int id() const { return IRPRO_DENON; } |
mjr | 77:0b96f6867312 | 2793 | |
mjr | 77:0b96f6867312 | 2794 | // Code parameters. The Denon protocol has no header; it just |
mjr | 77:0b96f6867312 | 2795 | // jumps right in with the first bit. |
mjr | 77:0b96f6867312 | 2796 | virtual uint32_t tHeaderMark() const { return 0; } |
mjr | 77:0b96f6867312 | 2797 | virtual uint32_t tHeaderSpace() const { return 0; } |
mjr | 77:0b96f6867312 | 2798 | virtual uint32_t minRxGap() const { return 30000; } |
mjr | 77:0b96f6867312 | 2799 | |
mjr | 77:0b96f6867312 | 2800 | // use a short gap between paired complemented codes, and a longer |
mjr | 77:0b96f6867312 | 2801 | // gap between repeats |
mjr | 77:0b96f6867312 | 2802 | virtual uint32_t txGap(IRTXState *state) const { return 165000; } |
mjr | 77:0b96f6867312 | 2803 | virtual uint32_t txPostGap(IRTXState *state) const |
mjr | 77:0b96f6867312 | 2804 | { |
mjr | 77:0b96f6867312 | 2805 | if (state->rep == 0) |
mjr | 77:0b96f6867312 | 2806 | { |
mjr | 77:0b96f6867312 | 2807 | // Even rep - this is the gap between the two halves of a |
mjr | 77:0b96f6867312 | 2808 | // complemented pair. The next code starts 65ms from the |
mjr | 77:0b96f6867312 | 2809 | // *start* of the last code, so the gap is 65ms minus the |
mjr | 77:0b96f6867312 | 2810 | // transmission time for the last code. |
mjr | 77:0b96f6867312 | 2811 | return 65000 - state->txTime.read_us(); |
mjr | 77:0b96f6867312 | 2812 | } |
mjr | 77:0b96f6867312 | 2813 | else |
mjr | 77:0b96f6867312 | 2814 | { |
mjr | 77:0b96f6867312 | 2815 | // odd rep - use the normal long gap |
mjr | 77:0b96f6867312 | 2816 | return 165000; |
mjr | 77:0b96f6867312 | 2817 | } |
mjr | 77:0b96f6867312 | 2818 | } |
mjr | 77:0b96f6867312 | 2819 | |
mjr | 77:0b96f6867312 | 2820 | // on idle, clear the previous code |
mjr | 77:0b96f6867312 | 2821 | virtual void rxIdle(IRRecvProIfc *receiver) |
mjr | 77:0b96f6867312 | 2822 | { |
mjr | 77:0b96f6867312 | 2823 | prvCode = 0; |
mjr | 77:0b96f6867312 | 2824 | } |
mjr | 77:0b96f6867312 | 2825 | |
mjr | 77:0b96f6867312 | 2826 | // space length coding |
mjr | 77:0b96f6867312 | 2827 | virtual int nbits() const { return 15; } |
mjr | 77:0b96f6867312 | 2828 | virtual int tMark() const { return 264; } |
mjr | 77:0b96f6867312 | 2829 | virtual int tZero() const { return 792; } |
mjr | 77:0b96f6867312 | 2830 | virtual int tOne() const { return 1848; } |
mjr | 77:0b96f6867312 | 2831 | |
mjr | 77:0b96f6867312 | 2832 | // handle a space |
mjr | 77:0b96f6867312 | 2833 | virtual bool rxSpace(IRRecvProIfc *receiver, uint32_t t) |
mjr | 77:0b96f6867312 | 2834 | { |
mjr | 77:0b96f6867312 | 2835 | // If this space is longer than the standard space between |
mjr | 77:0b96f6867312 | 2836 | // adjacent codes, clear out any previous first-half code |
mjr | 77:0b96f6867312 | 2837 | // we've stored. Complementary pairs have to be transmitted |
mjr | 77:0b96f6867312 | 2838 | // with the standard inter-code gap between them. Anything |
mjr | 77:0b96f6867312 | 2839 | // longer must be separate key presses. |
mjr | 77:0b96f6867312 | 2840 | if (t > 65000) |
mjr | 77:0b96f6867312 | 2841 | prvCode = 0; |
mjr | 77:0b96f6867312 | 2842 | |
mjr | 77:0b96f6867312 | 2843 | // do the normal work |
mjr | 77:0b96f6867312 | 2844 | return IRPSpaceLength<uint16_t>::rxSpace(receiver, t); |
mjr | 77:0b96f6867312 | 2845 | } |
mjr | 77:0b96f6867312 | 2846 | |
mjr | 77:0b96f6867312 | 2847 | // always send twice, once for the base version, once for the |
mjr | 77:0b96f6867312 | 2848 | // inverted follow-up version |
mjr | 77:0b96f6867312 | 2849 | virtual int txMinReps(IRTXState *state) const { return 2; } |
mjr | 77:0b96f6867312 | 2850 | |
mjr | 77:0b96f6867312 | 2851 | // encode the bitstream |
mjr | 77:0b96f6867312 | 2852 | virtual void codeToBitstream(IRTXState *state) |
mjr | 77:0b96f6867312 | 2853 | { |
mjr | 77:0b96f6867312 | 2854 | // If we're on an even repetition, just send the code |
mjr | 77:0b96f6867312 | 2855 | // exactly as given. If we're on an odd repetition, |
mjr | 77:0b96f6867312 | 2856 | // send the inverted F and S fields. |
mjr | 77:0b96f6867312 | 2857 | state->nbits = 15; |
mjr | 77:0b96f6867312 | 2858 | if (state->rep == 0 || state->rep == 2) |
mjr | 77:0b96f6867312 | 2859 | { |
mjr | 77:0b96f6867312 | 2860 | // even rep - send the base version |
mjr | 77:0b96f6867312 | 2861 | state->bitstream = state->cmdCode & (D_mask | F_mask); |
mjr | 77:0b96f6867312 | 2862 | |
mjr | 77:0b96f6867312 | 2863 | // If we're on rep 2, switch back to rep 0. This will |
mjr | 77:0b96f6867312 | 2864 | // combine with our minimum rep count of 2 to ensure that |
mjr | 77:0b96f6867312 | 2865 | // we always transmit an even number of copies. Note that |
mjr | 77:0b96f6867312 | 2866 | // this loop terminates: when the key is up after we finish |
mjr | 77:0b96f6867312 | 2867 | // rep 1, the rep counter will advance to 2 and we'll stop. |
mjr | 77:0b96f6867312 | 2868 | // We'll only reset the rep counter here if we actually |
mjr | 77:0b96f6867312 | 2869 | // enter rep 2, which means the key is still down at that |
mjr | 77:0b96f6867312 | 2870 | // point, which means that we'll have to go at least another |
mjr | 77:0b96f6867312 | 2871 | // two iterations. |
mjr | 77:0b96f6867312 | 2872 | state->rep = 0; |
mjr | 77:0b96f6867312 | 2873 | } |
mjr | 77:0b96f6867312 | 2874 | else |
mjr | 77:0b96f6867312 | 2875 | { |
mjr | 77:0b96f6867312 | 2876 | // odd rep - invert the F field, and use all 1's in the S field |
mjr | 77:0b96f6867312 | 2877 | uint32_t d = state->cmdCode & D_mask; |
mjr | 77:0b96f6867312 | 2878 | uint32_t f = (~state->cmdCode) & F_mask; |
mjr | 77:0b96f6867312 | 2879 | state->bitstream = d | f | S_mask; |
mjr | 77:0b96f6867312 | 2880 | } |
mjr | 77:0b96f6867312 | 2881 | } |
mjr | 77:0b96f6867312 | 2882 | |
mjr | 77:0b96f6867312 | 2883 | // report the code |
mjr | 77:0b96f6867312 | 2884 | virtual void rxClose(IRRecvProIfc *receiver, bool ditto) |
mjr | 77:0b96f6867312 | 2885 | { |
mjr | 77:0b96f6867312 | 2886 | // If this code matches the inverted prior code, we have |
mjr | 77:0b96f6867312 | 2887 | // a matching pair. Otherwise, hang onto this code until |
mjr | 77:0b96f6867312 | 2888 | // next time. |
mjr | 77:0b96f6867312 | 2889 | if (bit == 15) |
mjr | 77:0b96f6867312 | 2890 | { |
mjr | 77:0b96f6867312 | 2891 | // get the current and previous code's subfields |
mjr | 77:0b96f6867312 | 2892 | uint16_t curD = code & D_mask; |
mjr | 77:0b96f6867312 | 2893 | uint16_t curF = code & F_mask; |
mjr | 77:0b96f6867312 | 2894 | uint16_t curS = code & S_mask; |
mjr | 77:0b96f6867312 | 2895 | uint16_t prvD = prvCode & D_mask; |
mjr | 77:0b96f6867312 | 2896 | uint16_t prvF = prvCode & F_mask; |
mjr | 77:0b96f6867312 | 2897 | uint16_t prvS = prvCode & S_mask; |
mjr | 77:0b96f6867312 | 2898 | |
mjr | 77:0b96f6867312 | 2899 | // check to see if the current FS fields match the inverted |
mjr | 77:0b96f6867312 | 2900 | // prior FS fields, to make up a complementary pair as required |
mjr | 77:0b96f6867312 | 2901 | // by the protocol |
mjr | 77:0b96f6867312 | 2902 | if (curD == prvD && curF == (~prvF & F_mask) && curS == (~prvS & S_mask)) |
mjr | 77:0b96f6867312 | 2903 | { |
mjr | 77:0b96f6867312 | 2904 | // The current code is the second half of the first code. |
mjr | 77:0b96f6867312 | 2905 | // Don't report the current code, since it's just an |
mjr | 77:0b96f6867312 | 2906 | // error-checking copy of the previous code, which we already |
mjr | 77:0b96f6867312 | 2907 | // reported. We've now seen both halves, so clear the |
mjr | 77:0b96f6867312 | 2908 | // previous code. |
mjr | 77:0b96f6867312 | 2909 | prvCode = 0; |
mjr | 77:0b96f6867312 | 2910 | } |
mjr | 77:0b96f6867312 | 2911 | else |
mjr | 77:0b96f6867312 | 2912 | { |
mjr | 77:0b96f6867312 | 2913 | // This isn't the second half of an complementary pair, so |
mjr | 77:0b96f6867312 | 2914 | // report it as a separate code. If the 'S' bits are 1's |
mjr | 77:0b96f6867312 | 2915 | // in this code, it's the second half of the pair, so invert |
mjr | 77:0b96f6867312 | 2916 | // the F and S fields to get the original code. It might |
mjr | 77:0b96f6867312 | 2917 | // have been sent by a learning remote or universal remote |
mjr | 77:0b96f6867312 | 2918 | // that only captured the second half field. |
mjr | 77:0b96f6867312 | 2919 | if (curS == S_mask) |
mjr | 77:0b96f6867312 | 2920 | { |
mjr | 77:0b96f6867312 | 2921 | // The S bits are 1's, so it's a second-half code. We |
mjr | 77:0b96f6867312 | 2922 | // don't have a matching first-half code, so either the |
mjr | 77:0b96f6867312 | 2923 | // first half was lost, or it was never transmitted. In |
mjr | 77:0b96f6867312 | 2924 | // either case, reconstruct the original first-half code |
mjr | 77:0b96f6867312 | 2925 | // by inverting the F bits and clearing the S bits. |
mjr | 77:0b96f6867312 | 2926 | reportCode(receiver, id(), curD | (~curF & F_mask), |
mjr | 77:0b96f6867312 | 2927 | bool3::null, bool3::null); |
mjr | 77:0b96f6867312 | 2928 | |
mjr | 77:0b96f6867312 | 2929 | // Forget any previous code. This is a second-half |
mjr | 77:0b96f6867312 | 2930 | // code, so if we do get a first-half code after this, |
mjr | 77:0b96f6867312 | 2931 | // it's a brand new key press or repetition. |
mjr | 77:0b96f6867312 | 2932 | prvCode = 0; |
mjr | 77:0b96f6867312 | 2933 | } |
mjr | 77:0b96f6867312 | 2934 | else if (curS == 0) |
mjr | 77:0b96f6867312 | 2935 | { |
mjr | 77:0b96f6867312 | 2936 | // The S bits are 0, so it's a first-half code. Report |
mjr | 77:0b96f6867312 | 2937 | // the code, and save it for next time, so that we can |
mjr | 77:0b96f6867312 | 2938 | // check the next code to see if it's the second half. |
mjr | 77:0b96f6867312 | 2939 | reportCode(receiver, id(), code, bool3::null, bool3::null); |
mjr | 77:0b96f6867312 | 2940 | prvCode = code; |
mjr | 77:0b96f6867312 | 2941 | } |
mjr | 77:0b96f6867312 | 2942 | else |
mjr | 77:0b96f6867312 | 2943 | { |
mjr | 77:0b96f6867312 | 2944 | // The S bits are invalid, so this isn't a valid code. |
mjr | 77:0b96f6867312 | 2945 | // Clear out any previous code and reject this one. |
mjr | 77:0b96f6867312 | 2946 | prvCode = 0; |
mjr | 77:0b96f6867312 | 2947 | } |
mjr | 77:0b96f6867312 | 2948 | } |
mjr | 77:0b96f6867312 | 2949 | } |
mjr | 77:0b96f6867312 | 2950 | else |
mjr | 77:0b96f6867312 | 2951 | { |
mjr | 77:0b96f6867312 | 2952 | // we seem to have an invalid code; clear out any |
mjr | 77:0b96f6867312 | 2953 | // previous code so we can start from scratch |
mjr | 77:0b96f6867312 | 2954 | prvCode = 0; |
mjr | 77:0b96f6867312 | 2955 | } |
mjr | 77:0b96f6867312 | 2956 | } |
mjr | 77:0b96f6867312 | 2957 | |
mjr | 77:0b96f6867312 | 2958 | // stored first code - we hang onto this until we see the |
mjr | 77:0b96f6867312 | 2959 | // inverted second copy, so that we can verify a matching pair |
mjr | 77:0b96f6867312 | 2960 | uint16_t prvCode; |
mjr | 77:0b96f6867312 | 2961 | |
mjr | 77:0b96f6867312 | 2962 | // masks for the subfields |
mjr | 77:0b96f6867312 | 2963 | static const int F_shift = 5; |
mjr | 77:0b96f6867312 | 2964 | static const int S_shift = 13; |
mjr | 77:0b96f6867312 | 2965 | static const uint16_t D_mask = 0x1F; |
mjr | 77:0b96f6867312 | 2966 | static const uint16_t F_mask = 0x00FF << F_shift; |
mjr | 77:0b96f6867312 | 2967 | static const uint16_t S_mask = 0x0003 << S_shift; |
mjr | 77:0b96f6867312 | 2968 | }; |
mjr | 77:0b96f6867312 | 2969 | |
mjr | 77:0b96f6867312 | 2970 | // ----------------------------------------------------------------------- |
mjr | 77:0b96f6867312 | 2971 | // |
mjr | 77:0b96f6867312 | 2972 | // Samsung 20-bit protocol handler. This is a simple space-length |
mjr | 77:0b96f6867312 | 2973 | // encoding. |
mjr | 77:0b96f6867312 | 2974 | // |
mjr | 77:0b96f6867312 | 2975 | class IRPSamsung20: public IRPSpaceLength<uint32_t> |
mjr | 77:0b96f6867312 | 2976 | { |
mjr | 77:0b96f6867312 | 2977 | public: |
mjr | 77:0b96f6867312 | 2978 | IRPSamsung20() { } |
mjr | 77:0b96f6867312 | 2979 | |
mjr | 77:0b96f6867312 | 2980 | // name and ID |
mjr | 77:0b96f6867312 | 2981 | virtual const char *name() const { return "Samsung20"; } |
mjr | 77:0b96f6867312 | 2982 | virtual int id() const { return IRPRO_SAMSUNG20; } |
mjr | 77:0b96f6867312 | 2983 | |
mjr | 77:0b96f6867312 | 2984 | // code parameters |
mjr | 77:0b96f6867312 | 2985 | virtual float pwmPeriod(IRTXState *state) const { return 26.0416667e-6; } // 38.4 kHz |
mjr | 77:0b96f6867312 | 2986 | virtual uint32_t tHeaderMark() const { return 8*564; } |
mjr | 77:0b96f6867312 | 2987 | virtual uint32_t tHeaderSpace() const { return 8*564; } |
mjr | 77:0b96f6867312 | 2988 | virtual uint32_t minRxGap() const { return 2*564; } |
mjr | 77:0b96f6867312 | 2989 | virtual uint32_t txGap(IRTXState *state) const { return 118000; } |
mjr | 77:0b96f6867312 | 2990 | |
mjr | 77:0b96f6867312 | 2991 | // space length coding |
mjr | 77:0b96f6867312 | 2992 | virtual int nbits() const { return 20; } |
mjr | 77:0b96f6867312 | 2993 | virtual int tMark() const { return 1*564; } |
mjr | 77:0b96f6867312 | 2994 | virtual int tZero() const { return 1*564; } |
mjr | 77:0b96f6867312 | 2995 | virtual int tOne() const { return 3*564; } |
mjr | 77:0b96f6867312 | 2996 | }; |
mjr | 77:0b96f6867312 | 2997 | |
mjr | 77:0b96f6867312 | 2998 | // Samsung 36-bit protocol. This is similar to the NEC protocol, |
mjr | 77:0b96f6867312 | 2999 | // with different header timing. |
mjr | 77:0b96f6867312 | 3000 | class IRPSamsung36: public IRPSpaceLength<uint32_t> |
mjr | 77:0b96f6867312 | 3001 | { |
mjr | 77:0b96f6867312 | 3002 | public: |
mjr | 77:0b96f6867312 | 3003 | IRPSamsung36() { } |
mjr | 77:0b96f6867312 | 3004 | |
mjr | 77:0b96f6867312 | 3005 | // name and ID |
mjr | 77:0b96f6867312 | 3006 | virtual const char *name() const { return "Samsung36"; } |
mjr | 77:0b96f6867312 | 3007 | virtual int id() const { return IRPRO_SAMSUNG36; } |
mjr | 77:0b96f6867312 | 3008 | |
mjr | 77:0b96f6867312 | 3009 | // code parameters |
mjr | 77:0b96f6867312 | 3010 | virtual uint32_t tHeaderMark() const { return 9*500; } |
mjr | 77:0b96f6867312 | 3011 | virtual uint32_t tHeaderSpace() const { return 9*500; } |
mjr | 77:0b96f6867312 | 3012 | virtual uint32_t minRxGap() const { return 40000; } |
mjr | 77:0b96f6867312 | 3013 | virtual uint32_t txGap(IRTXState *state) const { return 118000; } |
mjr | 77:0b96f6867312 | 3014 | |
mjr | 77:0b96f6867312 | 3015 | // space length coding |
mjr | 77:0b96f6867312 | 3016 | virtual int nbits() const { return 36; } |
mjr | 77:0b96f6867312 | 3017 | virtual int tMark() const { return 1*500; } |
mjr | 77:0b96f6867312 | 3018 | virtual int tZero() const { return 1*500; } |
mjr | 77:0b96f6867312 | 3019 | virtual int tOne() const { return 3*500; } |
mjr | 77:0b96f6867312 | 3020 | }; |
mjr | 77:0b96f6867312 | 3021 | |
mjr | 77:0b96f6867312 | 3022 | // ----------------------------------------------------------------------- |
mjr | 77:0b96f6867312 | 3023 | // |
mjr | 77:0b96f6867312 | 3024 | // Lutron lights, fans, and home automation. Lutron uses a simple async |
mjr | 77:0b96f6867312 | 3025 | // bit coding: a 2280us space represents '0', a 2280us mark represents '1'. |
mjr | 77:0b96f6867312 | 3026 | // Each code consists of 36 bits. The start of a code word is indicated |
mjr | 77:0b96f6867312 | 3027 | // by a space of at least 4*2280us followed by a mark of at least 8*2280us. |
mjr | 77:0b96f6867312 | 3028 | // The mark amounts to 8 consecutive '1' bits, which Lutron includes as |
mjr | 77:0b96f6867312 | 3029 | // digits in the published codes, so we'll include them in our representation |
mjr | 77:0b96f6867312 | 3030 | // as well (as opposed to treating them as a separate header). |
mjr | 77:0b96f6867312 | 3031 | |
mjr | 77:0b96f6867312 | 3032 | // These raw 36-bit strings use an internal error correction coding. This |
mjr | 77:0b96f6867312 | 3033 | // isn't mentioned in Lutron's technical documentation (they just list the |
mjr | 77:0b96f6867312 | 3034 | // raw async bit strings), but there's a description on the Web (see, e.g., |
mjr | 77:0b96f6867312 | 3035 | // hifi-remote.com; the original source is unclear). According to that, |
mjr | 77:0b96f6867312 | 3036 | // you start with a pair of 8-bit values (device code + function). Append |
mjr | 77:0b96f6867312 | 3037 | // two parity bits for even parity in each byte. This gives us 18 bits. |
mjr | 77:0b96f6867312 | 3038 | // Divide the 18 bits into 6 groups of 3 bits. For each 3-bit group, take |
mjr | 77:0b96f6867312 | 3039 | // the binary value (0..7), recode the bits as a reflected Gray code for |
mjr | 77:0b96f6867312 | 3040 | // the same number (e.g., '111' binary = 7 = '100' Gray coded), then add |
mjr | 77:0b96f6867312 | 3041 | // an even parity bit. We now have 24 bits. Concatenate these together |
mjr | 77:0b96f6867312 | 3042 | // and sandwich them between the 8 x '1' start bits and the 4 x '0' stop |
mjr | 77:0b96f6867312 | 3043 | // bits, and we have the 36-bit async bit stream. |
mjr | 77:0b96f6867312 | 3044 | // |
mjr | 77:0b96f6867312 | 3045 | // The error correction code lets us apply at least one validation check: |
mjr | 77:0b96f6867312 | 3046 | // we can verify that each 4-bit group in the raw data has odd parity. If |
mjr | 77:0b96f6867312 | 3047 | // it doesn't, we'll reject the code. Ideally, we could also reverse the |
mjr | 77:0b96f6867312 | 3048 | // whole coding scheme above and check the two even parity bits for the |
mjr | 77:0b96f6867312 | 3049 | // recovered bytes. Unfortunately, I haven't figured out how to do this; |
mjr | 77:0b96f6867312 | 3050 | // taking the Web description of the coding at face value doesn't yield |
mjr | 77:0b96f6867312 | 3051 | // correct parity bits for all of the codes published by Lutron. Either |
mjr | 77:0b96f6867312 | 3052 | // the description is wrong, or I'm just misinterpreting it (which is |
mjr | 77:0b96f6867312 | 3053 | // likely given that it's pretty sketchy). |
mjr | 77:0b96f6867312 | 3054 | // |
mjr | 77:0b96f6867312 | 3055 | class IRPLutron: public IRPAsync<uint32_t> |
mjr | 77:0b96f6867312 | 3056 | { |
mjr | 77:0b96f6867312 | 3057 | public: |
mjr | 77:0b96f6867312 | 3058 | IRPLutron() { } |
mjr | 77:0b96f6867312 | 3059 | |
mjr | 77:0b96f6867312 | 3060 | // name and ID |
mjr | 77:0b96f6867312 | 3061 | virtual const char *name() const { return "Lutron"; } |
mjr | 77:0b96f6867312 | 3062 | virtual int id() const { return IRPRO_LUTRON; } |
mjr | 77:0b96f6867312 | 3063 | |
mjr | 77:0b96f6867312 | 3064 | // carrier is 40kHz |
mjr | 77:0b96f6867312 | 3065 | virtual float pwmPeriod(IRTXState *state) const { return 25.0e-6f; } // 40kHz |
mjr | 77:0b96f6867312 | 3066 | |
mjr | 77:0b96f6867312 | 3067 | // All codes end with 4 '0' bits, which is as much of a gap as these |
mjr | 77:0b96f6867312 | 3068 | // remotes provide. |
mjr | 77:0b96f6867312 | 3069 | virtual uint32_t minRxGap() const { return 2280*4 - 700; } |
mjr | 77:0b96f6867312 | 3070 | virtual uint32_t txGap(IRTXState *state) const { return 2280*16; } |
mjr | 77:0b96f6867312 | 3071 | |
mjr | 77:0b96f6867312 | 3072 | // Lutron doesn't have a formal header, but there's a long mark at |
mjr | 77:0b96f6867312 | 3073 | // the beginning of every code. |
mjr | 77:0b96f6867312 | 3074 | virtual uint32_t tHeaderMark() const { return 2280*8; } |
mjr | 77:0b96f6867312 | 3075 | virtual uint32_t tHeaderSpace() const { return 0; } |
mjr | 77:0b96f6867312 | 3076 | |
mjr | 77:0b96f6867312 | 3077 | // Bit code parameters. The Lutron codes are 36-bits, each bit |
mjr | 77:0b96f6867312 | 3078 | // is 2280us long, and bits are stored MSB first. |
mjr | 77:0b96f6867312 | 3079 | virtual bool lsbFirst() const { return false; } |
mjr | 77:0b96f6867312 | 3080 | virtual int nbits() const { return 24; } |
mjr | 77:0b96f6867312 | 3081 | virtual int tBit() const { return 2280; } |
mjr | 77:0b96f6867312 | 3082 | |
mjr | 77:0b96f6867312 | 3083 | // Validate a received code value, using the decoding process |
mjr | 77:0b96f6867312 | 3084 | // described above. The code value given here is the low-order |
mjr | 77:0b96f6867312 | 3085 | // 32 bits of the 36-bit code, with the initial FF stripped. |
mjr | 77:0b96f6867312 | 3086 | bool rxValidate(uint32_t c) |
mjr | 77:0b96f6867312 | 3087 | { |
mjr | 77:0b96f6867312 | 3088 | // the low 4 bits must be zeroes |
mjr | 77:0b96f6867312 | 3089 | if ((c & 0x0000000F) != 0) |
mjr | 77:0b96f6867312 | 3090 | return false; |
mjr | 77:0b96f6867312 | 3091 | |
mjr | 77:0b96f6867312 | 3092 | // drop the 4 '0' bits at the end |
mjr | 77:0b96f6867312 | 3093 | c >>= 4; |
mjr | 77:0b96f6867312 | 3094 | |
mjr | 77:0b96f6867312 | 3095 | // parity table for 4-bit values 0x00..0x0F - 0=even, 1=odd |
mjr | 77:0b96f6867312 | 3096 | static const int parity[] = { |
mjr | 77:0b96f6867312 | 3097 | 0, 1, 1, 0, // 0, 1, 2, 3 |
mjr | 77:0b96f6867312 | 3098 | 1, 0, 0, 1, // 4, 5, 6, 7 |
mjr | 77:0b96f6867312 | 3099 | 1, 0, 0, 1, // 8, 9, A, B |
mjr | 77:0b96f6867312 | 3100 | 0, 1, 1, 0 // C, D, E, F |
mjr | 77:0b96f6867312 | 3101 | }; |
mjr | 77:0b96f6867312 | 3102 | |
mjr | 77:0b96f6867312 | 3103 | // add up the number of groups with odd parity |
mjr | 77:0b96f6867312 | 3104 | int odds = 0; |
mjr | 77:0b96f6867312 | 3105 | for (int i = 0 ; i < 6 ; ++i, c >>= 4) |
mjr | 77:0b96f6867312 | 3106 | odds += parity[c & 0x0F]; |
mjr | 77:0b96f6867312 | 3107 | |
mjr | 77:0b96f6867312 | 3108 | // we need all 6 groups to have odd parity |
mjr | 77:0b96f6867312 | 3109 | return odds == 6; |
mjr | 77:0b96f6867312 | 3110 | } |
mjr | 77:0b96f6867312 | 3111 | |
mjr | 77:0b96f6867312 | 3112 | // Return the code. Lutron's code tables have all codes starting |
mjr | 77:0b96f6867312 | 3113 | // with FF (8 consecutive '1' bits), but we treat this as a header, |
mjr | 77:0b96f6867312 | 3114 | // since it can never occur within a code, so it doesn't show up |
mjr | 77:0b96f6867312 | 3115 | // in the bit string. We also count the tailing four '0' bits, |
mjr | 77:0b96f6867312 | 3116 | // which Lutron also encodes in each string, as a gap. So we only |
mjr | 77:0b96f6867312 | 3117 | // actually capture 24 data bits. To make our codes match the |
mjr | 77:0b96f6867312 | 3118 | // Lutron tables, add the structural bits back in for reporting. |
mjr | 77:0b96f6867312 | 3119 | virtual void rxClose(IRRecvProIfc *receiver, bool ditto) |
mjr | 77:0b96f6867312 | 3120 | { |
mjr | 77:0b96f6867312 | 3121 | if (bit == 24) |
mjr | 77:0b96f6867312 | 3122 | { |
mjr | 77:0b96f6867312 | 3123 | uint64_t report = 0xFF0000000LL | (code << 4); |
mjr | 77:0b96f6867312 | 3124 | if (rxValidate(report)) |
mjr | 77:0b96f6867312 | 3125 | reportCode(receiver, id(), report, bool3::null, bool3::null); |
mjr | 77:0b96f6867312 | 3126 | } |
mjr | 77:0b96f6867312 | 3127 | } |
mjr | 77:0b96f6867312 | 3128 | |
mjr | 77:0b96f6867312 | 3129 | // For transmission, convert back to the 24 data bits, stripping out |
mjr | 77:0b96f6867312 | 3130 | // the structural leading 8 '1' bits and trailing 4 '0' bits. |
mjr | 77:0b96f6867312 | 3131 | virtual void codeToBitstream(IRTXState *state) |
mjr | 77:0b96f6867312 | 3132 | { |
mjr | 77:0b96f6867312 | 3133 | state->bitstream = (state->cmdCode >> 4) & 0x00FFFFFF; |
mjr | 77:0b96f6867312 | 3134 | state->nbits = 24; |
mjr | 77:0b96f6867312 | 3135 | } |
mjr | 77:0b96f6867312 | 3136 | }; |
mjr | 77:0b96f6867312 | 3137 | |
mjr | 77:0b96f6867312 | 3138 | // ------------------------------------------------------------------------- |
mjr | 77:0b96f6867312 | 3139 | // |
mjr | 77:0b96f6867312 | 3140 | // Protocol singletons. We combine these into a container structure |
mjr | 77:0b96f6867312 | 3141 | // so that we can allocate the whole set of protocol handlers in one |
mjr | 77:0b96f6867312 | 3142 | // 'new'. |
mjr | 77:0b96f6867312 | 3143 | // |
mjr | 77:0b96f6867312 | 3144 | struct IRProtocols |
mjr | 77:0b96f6867312 | 3145 | { |
mjr | 77:0b96f6867312 | 3146 | #define IR_PROTOCOL_RXTX(cls) cls s_##cls; |
mjr | 77:0b96f6867312 | 3147 | #include "IRProtocolList.h" |
mjr | 77:0b96f6867312 | 3148 | }; |
mjr | 77:0b96f6867312 | 3149 | |
mjr | 77:0b96f6867312 | 3150 | #endif |