From a5c58e92f187b72111774f417d82238ec7e3b6eb Mon Sep 17 00:00:00 2001 From: Peter Hinch Date: Fri, 20 Mar 2020 17:24:05 +0000 Subject: [PATCH] Add MCE. Fix bug with RC5X. --- README.md | 69 ++++++++++++++++++++++++++------- RECEIVER.md | 75 ++++++++++++++++++++++++----------- TRANSMITTER.md | 78 +++++++++++++++++++++++++++++-------- ir_rx/acquire.py | 11 +----- ir_rx/mce.py | 69 +++++++++++++++++++++++++++++++++ ir_rx/philips.py | 46 ++++++++++------------ ir_rx/test.py | 21 ++++++---- ir_tx/__init__.py | 9 +++-- ir_tx/mce.py | 43 ++++++++++++++++++++ ir_tx/mcetest.py | 99 +++++++++++++++++++++++++++++++++++++++++++++++ ir_tx/nec.py | 2 +- ir_tx/philips.py | 8 ++-- ir_tx/test.py | 4 +- 13 files changed, 428 insertions(+), 106 deletions(-) create mode 100644 ir_rx/mce.py create mode 100644 ir_tx/mce.py create mode 100644 ir_tx/mcetest.py diff --git a/README.md b/README.md index 44318d5..2eb3a47 100644 --- a/README.md +++ b/README.md @@ -21,12 +21,23 @@ IR communication uses a carrier frequency to pulse the IR source. Modulation takes the form of OOK (on-off keying). There are multiple protocols and at least three options for carrier frequency: 36, 38 and 40KHz. +In the case of the transmitter the carrier frequency is a runtime parameter: +any value may be specified. The receiver uses a hardware demodulator which +should be purchased for the correct frequency. The receiver device driver sees +the demodulated signal and is hence carrier frequency agnostic. + +Remotes transmit an address and a data byte, plus in some cases an extra value. +The address denotes the physical device being controlled. The data defines the +button on the remote. Provision usually exists for differentiating between a +button repeatedly pressed and one which is held down; the mechanism is protocol +dependent. + +# 2. Supported protocols + The drivers support NEC and Sony protocols plus two Philips protocols, namely -RC-5 and RC-6 mode 0. In the case of the transmitter the carrier frequency is a -runtime parameter: any value may be specified. The receiver uses a hardware -demodulator which should be purchased for the correct frequency. The receiver -device driver sees the demodulated signal and is hence carrier frequency -agnostic. +RC-5 and RC-6 mode 0. There is also support for the OrtekMCE protocol used on +VRC-1100 remotes. These originally supported Microsoft Media Center but can be +used to control Kodi and (with a suitable receiver) to emulate a PC keyboard. Examining waveforms from various remote controls it is evident that numerous protocols exist. Some are doubtless proprietary and undocumented. The supported @@ -37,13 +48,7 @@ timing. A remote using the NEC protocol is [this one](https://www.adafruit.com/products/389). -Remotes transmit an address and a data byte, plus in some cases an extra value. -The address denotes the physical device being controlled. The data defines the -button on the remote. Provision usually exists for differentiating between a -button repeatedly pressed and one which is held down; the mechanism is protocol -dependent. - -# 2. Hardware Requirements +# 3. Hardware Requirements These are discussed in detail in the relevant docs; the following provides an overview. @@ -60,5 +65,41 @@ is obviously neither guaranteed nor optimal. The transmitter requires a Pyboard 1.x (not Lite), a Pyboard D or an ESP32. Output is via an IR LED which will need a transistor to provide sufficient -current. The ESP32 has significant limitations as a transmitter discussed -[here](./TRANSMITTER.md#52-esp32). +current. The ESP32 requires an extra transistor to work as a transmitter. + +## 3.1 Carrier frequencies + +These are as follows. The Samsung and Panasonic remotes appear to use +proprietary protocols and are not supported by these drivers. + +| Protocol | F KHz | How found | Support | +|:---------:|:-----:|:-------------:|:-------:| +| NEC | 38 | Measured | Y | +| RC-5 RC-6 | 36 | Spec/measured | Y | +| Sony | 40 | Spec/measured | Y | +| MCE | 38 | Measured | Y | +| Samsung | 38 | Measured | N | +| Panasonic | 36.3 | Measured | N | + +# 4. References + +Sources of information about IR protocols. +[General information about IR](https://www.sbprojects.net/knowledge/ir/) + +The NEC protocol: +[altium](http://techdocs.altium.com/display/FPGA/NEC+Infrared+Transmission+Protocol) +[circuitvalley](http://www.circuitvalley.com/2013/09/nec-protocol-ir-infrared-remote-control.html) + +Philips protocols: +[RC5](https://en.wikipedia.org/wiki/RC-5) +[RC5](https://www.sbprojects.net/knowledge/ir/rc5.php) +[RC6](https://www.sbprojects.net/knowledge/ir/rc6.php) + +Sony protocol: +[SIRC](https://www.sbprojects.net/knowledge/ir/sirc.php) + +MCE protocol: +[OrtekMCE](http://www.hifi-remote.com/johnsfine/DecodeIR.html#OrtekMCE) + +IR decoders (C sourcecode): +[in the Linux kernel](https://github.com/torvalds/linux/tree/master/drivers/media/rc) diff --git a/RECEIVER.md b/RECEIVER.md index 12d9577..ac20e18 100644 --- a/RECEIVER.md +++ b/RECEIVER.md @@ -52,11 +52,10 @@ the above script or run the following: from ir_rx.acquire import test test() ``` -This script is under development. - -It waits for a single burst from the remote and prints the timing of the pulses -followed by its best guess at the protocol. It correctly identifies supported -protocols, but can wrongly identify some unsupported proprietary protocols. +This script waits for a single burst from the remote and prints the timing of +the pulses followed by its best guess at the protocol. It correctly identifies +supported protocols, but can wrongly identify unsupported protocols. The +behaviour of the script exposed to an unknown protocol is unpredictable. # 3. The driver @@ -172,7 +171,40 @@ Typical invocation: from ir_rx.philips import RC5_IR ``` -These support the RC-5 and RC-6 mode 0 protocols respectively. +These support the RC-5 (including RC-5X) and RC-6 mode 0 protocols +respectively. + +#### Microsoft MCE class + +`MCE` + +Typical invocation: +```python +from ir_rx.mce import MCE +``` + +I have been unable to locate a definitive specification: the protocol was +analysed by a mixture of googling and experiment. Behaviour may change if I +acquire new information. The protocol is known as OrtekMCE and the remote +control is sold on eBay as VRC-1100. + +The remote was designed for Microsoft Media Center and is used to control Kodi +on boxes such as the Raspberry Pi. With a suitable PC driver it can emulate a +PC keyboard and mouse. The mouse emulation uses a different protocol: the class +does not currently support it. Pressing mouse buttons and pad will cause the +error function (if provided) to be called. + +Args passed to the callback comprise 4 bit `addr`, 6 bit `data` and 2 bit `ctrl` +with the latter having the value 0 for the first message and 2 for the message +sent on key release. Intermediate messages (where the key is held down) have +value 1. + +There is a 4-bit checksum which is used by default. The algorithm requires an +initial 'seed' value which my testing proved to be 4. However the only +[documentation](http://www.hifi-remote.com/johnsfine/DecodeIR.html#OrtekMCE) I +could find stated that the value should be 3. I implemented this as a class +variable `MCE.init_cs=4`. This enables it to be changed if some remotes use 3. +If the value is set to -1 the check will be skipped. # 4. Errors @@ -248,22 +280,6 @@ CPU times used by `.decode` (not including the user callback) were measured on a Pyboard D SF2W at stock frequency. They were: NEC 1ms for normal data, 100μs for a repeat code. Philips codes: RC-5 900μs, RC-6 mode 0 5.5ms. -# 7. References - -[General information about IR](https://www.sbprojects.net/knowledge/ir/) - -The NEC protocol: -[altium](http://techdocs.altium.com/display/FPGA/NEC+Infrared+Transmission+Protocol) -[circuitvalley](http://www.circuitvalley.com/2013/09/nec-protocol-ir-infrared-remote-control.html) - -Philips protocols: -[RC5](https://en.wikipedia.org/wiki/RC-5) -[RC5](https://www.sbprojects.net/knowledge/ir/rc5.php) -[RC6](https://www.sbprojects.net/knowledge/ir/rc6.php) - -Sony protocol: -[SIRC](https://www.sbprojects.net/knowledge/ir/sirc.php) - # Appendix 1 NEC Protocol description A normal burst comprises exactly 68 edges, the exception being a repeat code @@ -279,3 +295,18 @@ provide error checking. This also ensures that the number of 1's and 0's in a burst is constant, giving a constant burst length of 67.5ms. In extended address mode this constancy is lost. The burst length can (by my calculations) run to 76.5ms. + +# Appendix 2 MCE Protocol + +The bitstream comprises a header (2ms mark, 1ms space) followed by 16 bits of +Manchester encoded data with a bit time of 500μs. Data are encoded +``` +ccccddddddppaaaa +``` +Where `aaaa` is the address, `pp` is the position (toggle) field, `dddddd` is +data and `cccc` is a checksum. This is calculated by counting the ones in +`ddddddppaaaa` and adding 4. Data are transmitted LSB first. + +The only [doc](http://www.hifi-remote.com/johnsfine/DecodeIR.html#OrtekMCE) I +could find states that the checksum seed value is 3, but this did not match the +remote I have. diff --git a/TRANSMITTER.md b/TRANSMITTER.md index c29a0dd..4f15c65 100644 --- a/TRANSMITTER.md +++ b/TRANSMITTER.md @@ -31,7 +31,7 @@ The transistor type is not critical. The driver assumes circuits as shown. Here the carrier "off" state is 0V, which is the driver default. If using a circuit where "off" is required to be -3.3V, the constant `_SPACE` in `ir_tx.__init__.py` should be changed to 100. +3.3V, the class variable `active_high` should be set `False`. ## 1.2 ESP32 Wiring @@ -106,8 +106,18 @@ occurs as a background process, on the Pyboard controlled by timers 2 and 5. Execution times on a Pyboard 1.1 were 3.3ms for NEC, 1.5ms for RC5 and 2ms for RC6. +Class variable: + 1. `active_high=True` Normally the IR LED drive circuit turns the LED on if + the pin goes high. If it works with the opposite polarity the variable should + be set `False` before instantiating. + #### NEC class +Class `NEC`. Example invocation: +```python +from ir_tx.nec import NEC +``` + This has an additional method `.repeat` (no args). This causes a repeat code to be transmitted. Should be called every 108ms if a button is held down. @@ -121,6 +131,11 @@ A value passed in `toggle` is ignored. #### Sony classes +Classes `SONY_12`, `SONY_15` and `SONY_20`. Example invocation: +```python +from ir_tx.sony import SONY_15 +``` + The SIRC protocol supports three sizes, supported by the following classes: 1. 12 bit (7 data, 5 address) `SONY_12` 2. 15 bit (7 data, 8 address) `SONY_15` @@ -132,6 +147,11 @@ value. #### Philips classes +Classes `RC5` and `RC6_M0`. Example invocation: +```python +from ir_tx.philips import RC5 +``` + The RC-5 protocol supports a 5 bit address and 6 or 7 bit (RC5X) data. The driver uses the appropriate mode depending on the `data` value provided. @@ -141,6 +161,42 @@ Both send a `toggle` bit which remains constant if a button is held down, but changes when the button is released. The application should implement this behaviour, setting the `toggle` arg of `.transmit` to 0 or 1 as required. +#### Microsoft MCE class + +Class `MCE`. Example invocation: +```python +from ir_tx.mce import MCE +# MCE.init_cs = 3 +``` +There is a separate demo for the `MCE` class because of the need to send a +message on key release. It is run by issuing: +```python +from ir_tx.mcetest import test +``` +Instructions will be displayed at the REPL. + +I have been unable to locate a definitive specification: the protocol was +analysed by a mixture of googling and experiment. Behaviour may change if I +acquire new information. The protocol is known as OrtekMCE and the remote +control is sold on eBay as VRC-1100. + +The remote was designed for Microsoft Media Center and is used to control Kodi +on boxes such as the Raspberry Pi. With a suitable PC driver it can emulate a +PC keyboard and mouse. The mouse emulation uses a different protocol: the class +does not currently support it. Pressing mouse buttons and pad will cause the +error function (if provided) to be called. + +This supports a 4 bit address, 6 bit data and 2 bit toggle. The latter should +have a value of 0 for the first message, 1 for repeat messages, and 2 for a +final message sent on button release. + +The remaining four bits are a checksum which the driver creates. The algorithm +requires an initial 'seed' value which my testing proved to be 4. However the +only [documentation](http://www.hifi-remote.com/johnsfine/DecodeIR.html#OrtekMCE) +I could find stated that the value should be 3. I implemented this as a class +variable `MCE.init_cs=4`. This enables it to be changed if some receivers +require 3. + # 4. Principle of operation ## 4.1 Pyboard @@ -163,8 +219,7 @@ This is performed by two hardware timers initiated in the constructor. Timer 2, channel 1 is used to configure the output pin as a PWM channel. Its frequency is set in the constructor. The OOK is performed by dynamically changing the duty ratio using the timer channel's `pulse_width_percent` method: this varies -the pulse width from 0 to a duty ratio passed to the constructor. The NEC -protocol defaults to 50%, the Sony and Philips ones to 30%. +the pulse width from 0 to a duty ratio passed to the constructor. The duty ratio is changed by the Timer 5 callback `._cb`. This retrieves the next duration from the array. If it is not `STOP` it toggles the duty cycle @@ -180,18 +235,7 @@ The carrier is generated by PWM instance `.pwm` running continuously. The ABC constructor converts the 0-100 duty ratio specified by the subclass to the 0-1023 range used by ESP32. -# 5. References - -[General information about IR](https://www.sbprojects.net/knowledge/ir/) - -The NEC protocol: -[altium](http://techdocs.altium.com/display/FPGA/NEC+Infrared+Transmission+Protocol) -[circuitvalley](http://www.circuitvalley.com/2013/09/nec-protocol-ir-infrared-remote-control.html) - -Philips protocols: -[RC5](https://en.wikipedia.org/wiki/RC-5) -[RC5](https://www.sbprojects.net/knowledge/ir/rc5.php) -[RC6](https://www.sbprojects.net/knowledge/ir/rc6.php) +## 4.3 Duty ratio -Sony protocol: -[SIRC](https://www.sbprojects.net/knowledge/ir/sirc.php) +In every case where I could find a specified figure it was 30%. I measured +that from a variety of remotes, and in every case it was close to that figure. diff --git a/ir_rx/acquire.py b/ir_rx/acquire.py index 0f094f9..e8c6a12 100644 --- a/ir_rx/acquire.py +++ b/ir_rx/acquire.py @@ -63,9 +63,9 @@ class IR_GET(IR_RX): print('Philips RC-6 mode 0') ok = True - if not ok and near(burst[0], 2056) and near(burst[1], 945): + if not ok and near(burst[0], 2000) and near(burst[1], 1000): if near(duration, 19000): - print('Microsoft MCE edition protocol. Not yet supported.') + print('Microsoft MCE edition protocol.') # Constant duration, variable burst length, presumably bi-phase print('Protocol start {} {} Burst length {} duration {}'.format(burst[0], burst[1], lb, duration)) ok = True @@ -104,10 +104,3 @@ def test(): irg = IR_GET(pin) print('Waiting for IR data...') irg.acquire() - -# Yamaha NEC -# Virgin RC-5 - -# Samsung Unknown protocol 4576 4472 67 60755 -# Panasonic Unknown protocol 3526 1679 99 54303 -# Vista MCE edition Unknown protocol 2056 945 25 18935 diff --git a/ir_rx/mce.py b/ir_rx/mce.py new file mode 100644 index 0000000..218c859 --- /dev/null +++ b/ir_rx/mce.py @@ -0,0 +1,69 @@ +# mce.py Decoder for IR remote control using synchronous code +# Supports Microsoft MCE edition remote protocol. + +# Author: Peter Hinch +# Copyright Peter Hinch 2020 Released under the MIT license + +# WARNING: This is experimental and subject to change. + +from utime import ticks_us, ticks_diff +from ir_rx import IR_RX + +class MCE(IR_RX): + init_cs = 4 # http://www.hifi-remote.com/johnsfine/DecodeIR.html#OrtekMCE says 3 + def __init__(self, pin, callback, *args): + # Block lasts ~19ms and has <= 34 edges + super().__init__(pin, 34, 25, callback, *args) + + def decode(self, _): + def check(v): + if self.init_cs == -1: + return True + csum = v >> 12 + cs = self.init_cs + for _ in range(12): + if v & 1: + cs += 1 + v >>= 1 + return cs == csum + + try: + t0 = ticks_diff(self._times[1], self._times[0]) # 2000μs mark + t1 = ticks_diff(self._times[2], self._times[1]) # 1000μs space + if not ((1800 < t0 < 2200) and (800 < t1 < 1200)): + raise RuntimeError(self.BADSTART) + nedges = self.edge # No. of edges detected + if not 14 <= nedges <= 34: + raise RuntimeError(self.OVERRUN if nedges > 28 else self.BADSTART) + # Manchester decode + mask = 1 + bit = 1 + v = 0 + x = 2 + for _ in range(16): + # -1 convert count to index, -1 because we look ahead + if x > nedges - 2: + raise RuntimeError(self.BADBLOCK) + # width is 500/1000 nominal + width = ticks_diff(self._times[x + 1], self._times[x]) + if not 250 < width < 1350: + self.verbose and print('Bad block 3 Width', width, 'x', x) + raise RuntimeError(self.BADBLOCK) + short = int(width < 750) + bit ^= short ^ 1 + v |= mask if bit else 0 + mask <<= 1 + x += 1 + short + + self.verbose and print(bin(v)) + print(bin(v)) # TEST + #if not check(v): + #raise RuntimeError(self.BADDATA) + val = (v >> 6) & 0x3f + addr = v & 0xf # Constant for all buttons on my remote + ctrl = (v >> 4) & 3 + + except RuntimeError as e: + val, addr, ctrl = e.args[0], 0, 0 + # Set up for new data burst and run user callback/error function + self.do_callback(val, addr, ctrl) diff --git a/ir_rx/philips.py b/ir_rx/philips.py index cbd96f8..cd7feaa 100644 --- a/ir_rx/philips.py +++ b/ir_rx/philips.py @@ -18,35 +18,30 @@ class RC5_IR(IR_RX): if not 14 <= nedges <= 28: raise RuntimeError(self.OVERRUN if nedges > 28 else self.BADSTART) # Regenerate bitstream - bits = 0 + bits = 1 bit = 1 - for x in range(1, nedges): - width = ticks_diff(self._times[x], self._times[x - 1]) - if not 500 < width < 2000: + v = 1 # 14 bit bitstream, MSB always 1 + x = 0 + while bits < 14: + # -1 convert count to index, -1 because we look ahead + if x > nedges - 2: + print('Bad block 1 edges', nedges, 'x', x) raise RuntimeError(self.BADBLOCK) - for _ in range(1 if width < 1334 else 2): - bits <<= 1 - bits |= bit - bit ^= 1 - self.verbose and print(bin(bits)) # Matches inverted scope waveform - # Decode Manchester code - x = 30 - while not bits >> x: - x -= 1 - m0 = 1 << x # Mask MS two bits (always 01) - m1 = m0 << 1 - v = 0 # 14 bit bitstream - for _ in range(14): - v <<= 1 - b0 = (bits & m0) > 0 - b1 = (bits & m1) > 0 - if b0 == b1: + # width is 889/1778 nominal + width = ticks_diff(self._times[x + 1], self._times[x]) + if not 500 < width < 2100: + self.verbose and print('Bad block 3 Width', width, 'x', x) raise RuntimeError(self.BADBLOCK) - v |= b0 - m0 >>= 2 - m1 >>= 2 + short = width < 1334 + if not short: + bit ^= 1 + v <<= 1 + v |= bit + bits += 1 + x += 1 + int(short) + self.verbose and print(bin(v)) # Split into fields (val, addr, ctrl) - val = (v & 0x3f) | (0x40 if ((v >> 12) & 1) else 0) + val = (v & 0x3f) | (0 if ((v >> 12) & 1) else 0x40) # Correct the polarity of S2 addr = (v >> 6) & 0x1f ctrl = (v >> 11) & 1 @@ -55,6 +50,7 @@ class RC5_IR(IR_RX): # Set up for new data burst and run user callback self.do_callback(val, addr, ctrl) + class RC6_M0(IR_RX): # Even on Pyboard D the 444μs nominal pulses can be recorded as up to 705μs # Scope shows 360-520 μs (-84μs +76μs relative to nominal) diff --git a/ir_rx/test.py b/ir_rx/test.py index 112db2e..9a5daca 100644 --- a/ir_rx/test.py +++ b/ir_rx/test.py @@ -1,5 +1,5 @@ # test.py Test program for IR remote control decoder -# Supports Pyboard ESP32 and ESP8266 +# Supports Pyboard, ESP32 and ESP8266 # Author: Peter Hinch # Copyright Peter Hinch 2020 Released under the MIT license @@ -15,6 +15,7 @@ from ir_rx.print_error import print_error # Optional print of error codes from ir_rx.nec import NEC_8, NEC_16 from ir_rx.sony import SONY_12, SONY_15, SONY_20 from ir_rx.philips import RC5_IR, RC6_M0 +from ir_rx.mce import MCE # Define pin according to platform if platform == 'pyboard': @@ -23,7 +24,7 @@ elif platform == 'esp8266': freq(160000000) p = Pin(13, Pin.IN) elif platform == 'esp32' or platform == 'esp32_LoBo': - p = Pin(23, Pin.IN) # was 27 + p = Pin(23, Pin.IN) # User callback def cb(data, addr, ctrl): @@ -33,19 +34,22 @@ def cb(data, addr, ctrl): print('Data {:02x} Addr {:04x} Ctrl {:02x}'.format(data, addr, ctrl)) def test(proto=0): - classes = (NEC_8, NEC_16, SONY_12, SONY_15, SONY_20, RC5_IR, RC6_M0) + classes = (NEC_8, NEC_16, SONY_12, SONY_15, SONY_20, RC5_IR, RC6_M0, MCE) ir = classes[proto](p, cb) # Instantiate receiver ir.error_function(print_error) # Show debug information #ir.verbose = True # A real application would do something here... - while True: - print('running') - time.sleep(5) - gc.collect() + try: + while True: + print('running') + time.sleep(5) + gc.collect() + except KeyboardInterrupt: + ir.close() # **** DISPLAY GREETING **** s = '''Test for IR receiver. Run: -from ir_rx import test +from ir_rx.test import test test() for NEC 8 bit protocol, test(1) for NEC 16 bit, test(2) for Sony SIRC 12 bit, @@ -53,6 +57,7 @@ test(3) for Sony SIRC 15 bit, test(4) for Sony SIRC 20 bit, test(5) for Philips RC-5 protocol, test(6) for RC6 mode 0. +test(7) for Microsoft Vista MCE. Hit ctrl-c to stop, then ctrl-d to soft reset.''' diff --git a/ir_tx/__init__.py b/ir_tx/__init__.py index 6fe7710..5f00718 100644 --- a/ir_tx/__init__.py +++ b/ir_tx/__init__.py @@ -18,9 +18,7 @@ import micropython # micropython.alloc_emergency_exception_buf(100) -# ABC and Pyboard only: ESP32 ignores this value. -# Duty ratio in carrier off state: if driver is such that 3.3V turns the LED -# off, set _SPACE = 100 +# Duty ratio in carrier off state. _SPACE = const(0) # On ESP32 gate hardware design is led_on = rmt and carrier @@ -31,8 +29,11 @@ STOP = const(0) # End of data # carrier on or off. Physical transmission occurs in an ISR context controlled # by timer 2 and timer 5. See README.md for details of operation. class IR: + active_high = True # Hardware turns IRLED on if pin goes high. def __init__(self, pin, cfreq, asize, duty, verbose): + if not IR.active_high: + duty = 100 - duty if ESP32: self._pwm = PWM(pin[0]) # Continuous 36/38/40KHz carrier self._pwm.deinit() @@ -44,7 +45,7 @@ class IR: self._ch = tim.channel(1, Timer.PWM, pin=pin) self._ch.pulse_width_percent(_SPACE) # Turn off IR LED # Pyboard: 0 <= pulse_width_percent <= 100 - self._duty = duty if not _SPACE else (100 - duty) + self._duty = duty self._tim = Timer(5) # Timer 5 controls carrier on/off times self._tcb = self._cb # Pre-allocate self._arr = array('H', 0 for _ in range(asize)) # on/off times (μs) diff --git a/ir_tx/mce.py b/ir_tx/mce.py new file mode 100644 index 0000000..24f1aef --- /dev/null +++ b/ir_tx/mce.py @@ -0,0 +1,43 @@ +# mce.py Encoder for IR remote control using synchronous code +# Supports Microsoft MCE edition remote protocol. + +# Author: Peter Hinch +# Copyright Peter Hinch 2020 Released under the MIT license + +# WARNING: This is experimental and subject to change. + +from micropython import const +from ir_tx import IR + +_TBIT = const(500) # Time (μs) for pulse of carrier + + +class MCE(IR): + init_cs = 4 # http://www.hifi-remote.com/johnsfine/DecodeIR.html#OrtekMCE says 3 + + def __init__(self, pin, freq=38000, verbose=False): + super().__init__(pin, freq, 34, 30, verbose) + + def tx(self, addr, data, toggle): + def checksum(v): + cs = self.init_cs + for _ in range(12): + if v & 1: + cs += 1 + v >>= 1 + return cs + + self.append(2000, 1000, _TBIT) + d = ((data & 0x3f) << 6) | (addr & 0xf) | ((toggle & 3) << 4) + d |= checksum(d) << 12 + self.verbose and print(bin(d)) + + mask = 1 + while mask < 0x10000: + bit = bool(d & mask) + if bit ^ self.carrier: + self.add(_TBIT) + self.append(_TBIT) + else: + self.append(_TBIT, _TBIT) + mask <<= 1 diff --git a/ir_tx/mcetest.py b/ir_tx/mcetest.py new file mode 100644 index 0000000..2b4af96 --- /dev/null +++ b/ir_tx/mcetest.py @@ -0,0 +1,99 @@ +# ir_tx.mcetest Test for nonblocking MCE IR blaster. + +# Released under the MIT License (MIT). See LICENSE. + +# Copyright (c) 2020 Peter Hinch + +# Implements a 2-button remote control on a Pyboard with auto repeat. +from sys import platform +ESP32 = platform == 'esp32' +if ESP32: + from machine import Pin +else: + from pyb import Pin, LED + +from micropython import const +import uasyncio as asyncio +from aswitch import Switch, Delay_ms +from ir_tx.mce import MCE + +loop = asyncio.get_event_loop() +_FIRST = const(0) +_REP = const(1) +_END = const(2) +_REP_DELAY = const(60) + +class Rbutton: + def __init__(self, irb, pin, addr, data, rep_code=False): + self.irb = irb + self.sw = Switch(pin) + self.addr = addr + self.data = data + self.rep_code = rep_code + self.sw.close_func(self.cfunc) + self.sw.open_func(self.ofunc) + self.tim = Delay_ms(self.repeat) + self.stop = False + + def cfunc(self): # Button push: send data and set up for repeats + print('start') + self.irb.transmit(self.addr, self.data, _FIRST) + self.tim.trigger(_REP_DELAY) + + def ofunc(self): # Button release: cancel repeat timer + self.stop = True + + async def repeat(self): + await asyncio.sleep(0) # Let timer stop before retriggering + if self.stop: # Button has been released: send last message + self.stop = False + self.tim.stop() # Not strictly necessary + self.irb.transmit(self.addr, self.data, _END) + print('stop') + else: + print('rep') + self.tim.trigger(_REP_DELAY) + self.irb.transmit(self.addr, self.data, _REP) + +async def main(): + if ESP32: # Pins for IR LED gate + pin = (Pin(23, Pin.OUT, value = 0), Pin(21, Pin.OUT, value = 0)) + else: + pin = Pin('X1') + irb = MCE(pin, verbose=True) + + b = [] # Rbutton instances + px3 = Pin(18, Pin.IN, Pin.PULL_UP) if ESP32 else Pin('X3', Pin.IN, Pin.PULL_UP) + px4 = Pin(19, Pin.IN, Pin.PULL_UP) if ESP32 else Pin('X4', Pin.IN, Pin.PULL_UP) + b.append(Rbutton(irb, px3, 0x1, 0x7)) + b.append(Rbutton(irb, px4, 0xe, 0xb)) + if ESP32: + while True: + print('Running') + await asyncio.sleep(5) + else: + led = LED(1) + while True: + await asyncio.sleep_ms(500) # Obligatory flashing LED. + led.toggle() + +# Greeting strings. Common: +s = '''Test for IR transmitter. Run: +from ir_tx.mcetest import test +test() +''' +# Pyboard: +spb = ''' +IR LED on pin X1 +Ground pin X3 to send addr 1 data 7 +Ground pin X4 to send addr 0xe data 0x0b.''' +# ESP32 +sesp = ''' +IR LED gate on pins 23, 21 +Ground pin 18 to send addr 1 data 7 +Ground pin 19 to send addr 0xe data 0x0b.''' + +print(''.join((s, sesp)) if ESP32 else ''.join((s, spb))) + +def test(): + loop.run_until_complete(main()) diff --git a/ir_tx/nec.py b/ir_tx/nec.py index 1e02c9f..10cc67b 100644 --- a/ir_tx/nec.py +++ b/ir_tx/nec.py @@ -13,7 +13,7 @@ _T_ONE = const(1687) class NEC(IR): def __init__(self, pin, freq=38000, verbose=False): # NEC specifies 38KHz - super().__init__(pin, freq, 68, 50, verbose) + super().__init__(pin, freq, 68, 33, verbose) # Measured duty ratio 33% def _bit(self, b): self.append(_TBURST, _T_ONE if b else _TBURST) diff --git a/ir_tx/philips.py b/ir_tx/philips.py index a37dd5b..3ca270a 100644 --- a/ir_tx/philips.py +++ b/ir_tx/philips.py @@ -5,19 +5,19 @@ # Copyright Peter Hinch 2020 Released under the MIT license from micropython import const -from sys import platform from ir_tx import IR # Philips RC5 protocol _T_RC5 = const(889) # Time for pulse of carrier -ermsg = 'ESP32 does not support Philips protocols' + + class RC5(IR): def __init__(self, pin, freq=36000, verbose=False): super().__init__(pin, freq, 28, 30, verbose) - def tx(self, addr, data, toggle): - d = (data & 0x3f) | ((addr & 0x1f) << 6) | ((data & 0x40) << 6) | ((toggle & 1) << 11) + def tx(self, addr, data, toggle): # Fix RC5X S2 bit polarity + d = (data & 0x3f) | ((addr & 0x1f) << 6) | (((data & 0x40) ^ 0x40) << 6) | ((toggle & 1) << 11) self.verbose and print(bin(d)) mask = 0x2000 while mask: diff --git a/ir_tx/test.py b/ir_tx/test.py index b175dcd..9e199dd 100644 --- a/ir_tx/test.py +++ b/ir_tx/test.py @@ -1,4 +1,4 @@ -# ir_tx_test.py Test for nonblocking NEC/SONY/RC-5/RC-6 mode 0 IR blaster. +# ir_tx.test Test for nonblocking NEC/SONY/RC-5/RC-6 mode 0 IR blaster. # Released under the MIT License (MIT). See LICENSE. @@ -80,7 +80,7 @@ async def main(proto): # Greeting strings. Common: s = '''Test for IR transmitter. Run: -from ir_tx_test import test +from ir_tx.test import test test() for NEC protocol test(1) for Sony SIRC 12 bit test(2) for Sony SIRC 15 bit -- 2.47.3