]> vault307.fbx.one Git - micorpython_ir.git/commitdiff
Add MCE. Fix bug with RC5X.
authorPeter Hinch <peter@hinch.me.uk>
Fri, 20 Mar 2020 17:24:05 +0000 (17:24 +0000)
committerPeter Hinch <peter@hinch.me.uk>
Fri, 20 Mar 2020 17:24:05 +0000 (17:24 +0000)
13 files changed:
README.md
RECEIVER.md
TRANSMITTER.md
ir_rx/acquire.py
ir_rx/mce.py [new file with mode: 0644]
ir_rx/philips.py
ir_rx/test.py
ir_tx/__init__.py
ir_tx/mce.py [new file with mode: 0644]
ir_tx/mcetest.py [new file with mode: 0644]
ir_tx/nec.py
ir_tx/philips.py
ir_tx/test.py

index 44318d546476d17cccac2cbe812d0139d497aead..2eb3a47f35bbe7760bbc3cb6288f4ea8538efd5e 100644 (file)
--- 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.
 
 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
 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
 
 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).
 
 
 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.
 
 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
 
 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)
index 12d9577ad02cb1a59ff4e5fb356e4adfba2f6a76..ac20e18053898572badb6fe7eae1d0865ef06767 100644 (file)
@@ -52,11 +52,10 @@ the above script or run the following:
 from ir_rx.acquire import test
 test()
 ```
 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
 
 
 # 3. The driver
 
@@ -172,7 +171,40 @@ Typical invocation:
 from ir_rx.philips import RC5_IR
 ```
 
 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
 
 
 # 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.
 
 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
 # 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.
 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.
index c29a0ddbd840f0a9c924cbe500ee7db3e3365629..4f15c656e190e56494ff0a6f3f76593f7a3bf445 100644 (file)
@@ -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
 
 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
 
 
 ## 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.
 
 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
 
 #### 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.
 
 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
 
 
 #### 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`
 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
 
 
 #### 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.
 
 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.
 
 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
 # 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
 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
 
 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.
 
 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.
index 0f094f9a5244ae10f41a06d874c41fd3473758e7..e8c6a1243b3174db935296a4f9d46b291d2546fa 100644 (file)
@@ -63,9 +63,9 @@ class IR_GET(IR_RX):
                     print('Philips RC-6 mode 0')
                     ok = True
 
                     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):
                 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
                     # 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()
     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 (file)
index 0000000..218c859
--- /dev/null
@@ -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)
index cbd96f8e4de7eb1a2e58a1ba601d0e4e7b50a039..cd7feaa49d97873d7fa69d1b17ab7be9be0c32ba 100644 (file)
@@ -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
             if not 14 <= nedges <= 28:
                 raise RuntimeError(self.OVERRUN if nedges > 28 else self.BADSTART)
             # Regenerate bitstream
-            bits = 0
+            bits = 1
             bit = 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)
                     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)
                     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)
             # 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
 
             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)
 
         # 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)
 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)
index 112db2ee9cc3291fe07a6d1b12981ea536fb81c5..9a5dacaa42ef2cbe392c900566368fc886c7c383 100644 (file)
@@ -1,5 +1,5 @@
 # test.py Test program for IR remote control decoder
 # 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
 
 # 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.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':
 
 # 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':
     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):
 
 # 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):
         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...
     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:
 
 # **** 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,
 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(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.'''
 
 
 Hit ctrl-c to stop, then ctrl-d to soft reset.'''
 
index 6fe771048d3d535c921d6aec0185580b70609f0f..5f0071871abfab5e7c6b3efa1393a24cf204ef04 100644 (file)
@@ -18,9 +18,7 @@ import micropython
 
 # micropython.alloc_emergency_exception_buf(100)
 
 
 # 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
 
 _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:
 # 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):
 
     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()
         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._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)
             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 (file)
index 0000000..24f1aef
--- /dev/null
@@ -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 (file)
index 0000000..2b4af96
--- /dev/null
@@ -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())
index 1e02c9f9068acb74b442dfd2f85e2399ce47fac6..10cc67bfe164b72bcb5822dbbc02af3aefa3caf9 100644 (file)
@@ -13,7 +13,7 @@ _T_ONE = const(1687)
 class NEC(IR):
 
     def __init__(self, pin, freq=38000, verbose=False):  # NEC specifies 38KHz
 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)
 
     def _bit(self, b):
         self.append(_TBURST, _T_ONE if b else _TBURST)
index a37dd5b282217c0ed5bce634af432905ba95544f..3ca270ae746fd79734029a4b9b22fb51566dd32b 100644 (file)
@@ -5,19 +5,19 @@
 # Copyright Peter Hinch 2020 Released under the MIT license
 
 from micropython import const
 # 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
 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)
 
 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:
         self.verbose and print(bin(d))
         mask = 0x2000
         while mask:
index b175dcd2ef8fec1a87be167140448debd5df45b4..9e199dd6a886ed8e86c3ba1ea6bbe22b20a5d923 100644 (file)
@@ -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.
 
 
 # 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:
 
 # 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
 test() for NEC protocol
 test(1) for Sony SIRC 12 bit
 test(2) for Sony SIRC 15 bit