]> vault307.fbx.one Git - micorpython_ir.git/commitdiff
Receiver now a package.
authorPeter Hinch <peter@hinch.me.uk>
Sat, 7 Mar 2020 12:23:06 +0000 (12:23 +0000)
committerPeter Hinch <peter@hinch.me.uk>
Sat, 7 Mar 2020 12:23:06 +0000 (12:23 +0000)
README.md
ir_rx.py [deleted file]
ir_rx/__init__.py [new file with mode: 0644]
ir_rx/nec.py [new file with mode: 0644]
ir_rx/philips.py [new file with mode: 0644]
ir_rx/print_error.py [new file with mode: 0644]
ir_rx/sony.py [new file with mode: 0644]
ir_rx_test.py

index c107a784dd7d79f2eee252c0f0998889c9def5d5..1e1fa25b39e2374ea9718214a92945c219b009fc 100644 (file)
--- a/README.md
+++ b/README.md
@@ -69,8 +69,11 @@ On import, demos print an explanation of how to run them.
 
 ## 3.1 Receiver
 
 
 ## 3.1 Receiver
 
-Copy the following files to the target filesystem:
- 1. `ir_rx.py` The receiver device driver.
+This is a Python package. This minimises RAM usage: applications only import
+the device driver for the protocol in use.
+
+Copy the following to the target filesystem:
+ 1. `ir_rx` Directory and contents. Contains the device drivers.
  2. `ir_rx_test.py` Demo of a receiver.
 
 There are no dependencies.
  2. `ir_rx_test.py` Demo of a receiver.
 
 There are no dependencies.
@@ -91,10 +94,29 @@ from the official library and `aswitch.py` from
 
 # 4. Receiver
 
 
 # 4. Receiver
 
-This implements a class for each supported protocol, namely `NEC_IR`,
-`SONY_IR`, `RC5_IR` and `RC6_M0`. Applications should instantiate the
-appropriate class with a callback. The callback will run whenever an IR pulse
-train is received.
+This implements a class for each supported protocol. Applications should
+instantiate the appropriate class with a callback. The callback will run
+whenever an IR pulse train is received. Example running on a Pyboard:
+
+```python
+import time
+from machine import Pin
+from pyb import LED
+from ir_rx.nec import NEC_8  # NEC remote, 8 bit addresses
+
+red = LED(1)
+
+def callback(data, addr, ctrl):
+    if data < 0:  # NEC protocol sends repeat codes.
+        print('Repeat code.')
+    else:
+        print('Data {:02x} Addr {:04x}'.format(data, addr))
+
+ir = NEC_8(Pin('X3', Pin.IN), callback)
+while True:
+    time.sleep_ms(500)
+    red.toggle()
+```
 
 #### Common to all classes
 
 
 #### Common to all classes
 
@@ -125,24 +147,46 @@ Method:
  it will be called if an error occurs. The value corresponds to the error code
  (see below).
 
  it will be called if an error occurs. The value corresponds to the error code
  (see below).
 
-#### Properties specific to a class
-
-`NEC_IR`:  
-`extended` `bool`. Remotes using the NEC protocol can send 8 or 16 bit
-addresses. If `True` 16 bit addresses are assumed. If an 8 bit address is sent
-it will be received as a 16 bit value comprising the address and (in bits 8-15)
-its ones complement. Set `False` to enable error checking for remotes that
-return an 8 bit address: the complement will be checked and the address will be
-returned as an 8-bit value. The default is `True`.
-
-`SONY_IR`:  
-`bits` `int`. The SIRC protocol comes in 3 variants: 12, 15 and 20 bits. The
-default will handle bitstreams from all three types of remote. A value matching
-your remote improves the timing reducing the likelihood of errors when handling
-repeats: in 20-bit mode SIRC timing when a button is held down is tight. A
-worst-case 20-bit block takes 39ms nominal, yet the repeat time is 45ms nominal.  
-The Sony remote tested issues both 12 bit and 15 bit streams. The default is
-20.
+#### NEC classes
+
+`NEC_8`, `NEC_16`
+
+```python
+from ir_rx.nec import NEC_8
+```
+
+Remotes using the NEC protocol can send 8 or 16 bit addresses. If the `NEC_16`
+class receives an 8 bit address it will get a 16 bit value comprising the
+address in bits 0-7 and its one's complement in bits 8-15.  
+The `NEC_8` class enables error checking for remotes that return an 8 bit
+address: the complement is checked and the address returned as an 8-bit value.
+A 16-bit address will result in an error.
+
+#### Sony classes
+
+`SONY_12`, `SONY_15`, `SONY_20`
+
+```python
+from ir_rx.sony import SONY_15
+```
+
+The SIRC protocol comes in 3 variants: 12, 15 and 20 bits. `SONY_20` handles
+bitstreams from all three types of remote. Choosing a class matching the remote
+improves the timing reducing the likelihood of errors when handling repeats: in
+20-bit mode SIRC timing when a button is held down is tight. A worst-case 20
+bit block takes 39ms nominal, yet the repeat time is 45ms nominal.  
+A single physical remote can issue more than one type of bitstream. The Sony
+remote tested issued both 12 bit and 15 bit streams.
+
+#### Philips classes
+
+`RC5_IR`, `RC6_M0`
+
+```python
+from ir_rx.philips import RC5_IR
+```
+
+These support the RC-5 and RC-6 mode 0 protocols respectively.
 
 # 4.1 Errors
 
 
 # 4.1 Errors
 
diff --git a/ir_rx.py b/ir_rx.py
deleted file mode 100644 (file)
index 210d617..0000000
--- a/ir_rx.py
+++ /dev/null
@@ -1,324 +0,0 @@
-# ir_rx.py Decoder for IR remote control using synchronous code
-# Supports RC-5 RC-6 mode 0 and NEC protocols.
-# For a remote using NEC see https://www.adafruit.com/products/389
-
-# Author: Peter Hinch
-# Copyright Peter Hinch 2020 Released under the MIT license
-
-from sys import platform
-from micropython import const
-from machine import Timer
-from array import array
-from utime import ticks_us, ticks_diff
-
-if platform == 'pyboard':
-    from pyb import Pin, ExtInt
-else:
-    from machine import Pin
-
-ESP32 = platform == 'esp32' or platform == 'esp32_LoBo'
-
-# Save RAM
-# from micropython import alloc_emergency_exception_buf
-# alloc_emergency_exception_buf(100)
-
-# Result codes (accessible to application)
-# Repeat button code
-REPEAT = -1
-# Error codes
-BADSTART = -2
-BADBLOCK = -3
-BADREP = -4
-OVERRUN = -5
-BADDATA = -6
-BADADDR = -7
-
-
-# On 1st edge start a block timer. While the timer is running, record the time
-# of each edge. When the timer times out decode the data. Duration must exceed
-# the worst case block transmission time, but be less than the interval between
-# a block start and a repeat code start (~108ms depending on protocol)
-
-class IR_RX():
-    _erstr = "'{}' object has no attribute '{}'"
-
-    def __init__(self, pin, nedges, tblock, callback, *args):  # Optional args for callback
-        self._nedges = nedges
-        self._tblock = tblock
-        self.callback = callback
-        self.args = args
-        self._errf = lambda _ : None
-        self.verbose = False
-
-        self._times = array('i',  (0 for _ in range(nedges + 1)))  # +1 for overrun
-        if platform == 'pyboard':
-            ei = ExtInt(pin, ExtInt.IRQ_RISING_FALLING, Pin.PULL_NONE, self._cb_pin)
-        elif ESP32:
-            ei = pin.irq(handler = self._cb_pin, trigger = (Pin.IRQ_FALLING | Pin.IRQ_RISING))
-        else:
-            ei = pin.irq(handler = self._cb_pin, trigger = (Pin.IRQ_FALLING | Pin.IRQ_RISING), hard = True)
-        self._eint = ei  # Keep reference??
-        self.edge = 0
-        self.tim = Timer(-1)  # Sofware timer
-        self.cb = self.decode
-
-
-    # Pin interrupt. Save time of each edge for later decode.
-    def _cb_pin(self, line):
-        t = ticks_us()
-        # On overrun ignore pulses until software timer times out
-        if self.edge <= self._nedges:  # Allow 1 extra pulse to record overrun
-            if not self.edge:  # First edge received
-                self.tim.init(period=self._tblock , mode=Timer.ONE_SHOT, callback=self.cb)
-            self._times[self.edge] = t
-            self.edge += 1
-
-    @property
-    def extended(self):
-        raise AttributeError(self._erstr.format(self.__qualname__, 'extended'))
-
-    @property
-    def bits(self):
-        raise AttributeError(self._erstr.format(self.__qualname__, 'bits'))
-
-    def do_callback(self, cmd, addr, ext, thresh=0):
-        self.edge = 0
-        if cmd >= thresh:
-            self.callback(cmd, addr, ext, *self.args)
-        else:
-            self._errf(cmd)
-
-    def error_function(self, func):
-        self._errf = func
-
-class NEC_IR(IR_RX):
-    def __init__(self, pin, callback, *args):
-        # Block lasts <= 80ms (extended mode) and has 68 edges
-        super().__init__(pin, 68, 80, callback, *args)
-        self._extended = True
-        self._addr = 0
-
-    def decode(self, _):
-        try:
-            if self.edge > 68:
-                raise RuntimeError(OVERRUN)
-            width = ticks_diff(self._times[1], self._times[0])
-            if width < 4000:  # 9ms leading mark for all valid data
-                raise RuntimeError(BADSTART)
-            width = ticks_diff(self._times[2], self._times[1])
-            if width > 3000:  # 4.5ms space for normal data
-                if self.edge < 68:  # Haven't received the correct number of edges
-                    raise RuntimeError(BADBLOCK)
-                # Time spaces only (marks are always 562.5µs)
-                # Space is 1.6875ms (1) or 562.5µs (0)
-                # Skip last bit which is always 1
-                val = 0
-                for edge in range(3, 68 - 2, 2):
-                    val >>= 1
-                    if ticks_diff(self._times[edge + 1], self._times[edge]) > 1120:
-                        val |= 0x80000000
-            elif width > 1700: # 2.5ms space for a repeat code. Should have exactly 4 edges.
-                raise RuntimeError(REPEAT if self.edge == 4 else BADREP)  # Treat REPEAT as error.
-            else:
-                raise RuntimeError(BADSTART)
-            addr = val & 0xff  # 8 bit addr
-            cmd = (val >> 16) & 0xff
-            if cmd != (val >> 24) ^ 0xff:
-                raise RuntimeError(BADDATA)
-            if addr != ((val >> 8) ^ 0xff) & 0xff:  # 8 bit addr doesn't match check
-                if not self._extended:
-                    raise RuntimeError(BADADDR)
-                addr |= val & 0xff00  # pass assumed 16 bit address to callback
-            self._addr = addr
-        except RuntimeError as e:
-            cmd = e.args[0]
-            addr = self._addr if cmd == REPEAT else 0  # REPEAT uses last address
-        # Set up for new data burst and run user callback
-        self.do_callback(cmd, addr, 0, REPEAT)
-
-    @property
-    def extended(self):
-        return self._extended
-
-    @extended.setter
-    def extended(self, value):
-        self._extended = bool(value)
-
-class SONY_IR(IR_RX):
-    def __init__(self, pin, callback, *args):
-        # 20 bit block has 42 edges and lasts <= 39ms nominal. Add 4ms to time
-        # for tolerances except in 20 bit case where timing is tight with a
-        # repeat period of 45ms.
-        t = int(3 + bits * 1.8) + (1 if bits == 20 else 4)
-        super().__init__(pin, 2 + bits * 2, t, callback, *args)
-        self._addr = 0
-        self._bits = 20
-
-    def decode(self, _):
-        try:
-            nedges = self.edge  # No. of edges detected
-            self.verbose and print('nedges', nedges)
-            if nedges > 42:
-                raise RuntimeError(OVERRUN)
-            bits = (nedges - 2) // 2
-            if nedges not in (26, 32, 42) or bits > self._bits:
-                raise RuntimeError(BADBLOCK)
-            self.verbose and print('SIRC {}bit'.format(bits))
-            width = ticks_diff(self._times[1], self._times[0])
-            if not 1800 < width < 3000:  # 2.4ms leading mark for all valid data
-                raise RuntimeError(BADSTART)
-            width = ticks_diff(self._times[2], self._times[1])
-            if not 350 < width < 1000:  # 600μs space
-                raise RuntimeError(BADSTART)
-
-            val = 0  # Data received, LSB 1st
-            x = 2
-            bit = 1
-            while x < nedges - 2:
-                if ticks_diff(self._times[x + 1], self._times[x]) > 900:
-                    val |= bit
-                bit <<= 1
-                x += 2
-
-            cmd = val & 0x7f  # 7 bit command
-            val >>= 7
-            if nedges < 42:
-                addr = val & 0xff  # 5 or 8 bit addr
-                val = 0
-            else:
-                addr = val & 0x1f  # 5 bit addr
-                val >>= 5  # 8 bit extended
-        except RuntimeError as e:
-            cmd = e.args[0]
-            addr = 0
-            val = 0
-        self.do_callback(cmd, addr, val)
-
-    @property
-    def bits(self):
-        return self._bits
-
-    @bits.setter
-    def bits(self, value):
-        if value not in (12, 15, 20):
-            raise ValueError('bits must be 12, 15 or 20')
-        self._bits = value
-
-class RC5_IR(IR_RX):
-    def __init__(self, pin, callback, *args):
-        # Block lasts <= 30ms and has <= 28 edges
-        super().__init__(pin, 28, 30, callback, *args)
-
-    def decode(self, _):
-        try:
-            nedges = self.edge  # No. of edges detected
-            if not 14 <= nedges <= 28:
-                raise RuntimeError(OVERRUN if nedges > 28 else BADSTART)
-            # Regenerate bitstream
-            bits = 0
-            bit = 1
-            for x in range(1, nedges):
-                width = ticks_diff(self._times[x], self._times[x - 1])
-                if not 500 < width < 2000:
-                    raise RuntimeError(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:
-                    raise RuntimeError(BADBLOCK)
-                v |= b0
-                m0 >>= 2
-                m1 >>= 2
-            # Split into fields (val, addr, ctrl)
-            val = (v & 0x3f) | (0x40 if ((v >> 12) & 1) else 0)
-            addr = (v >> 6) & 0x1f
-            ctrl = (v >> 11) & 1
-
-        except RuntimeError as e:
-            val, addr, ctrl = e.args[0], 0, 0
-        # 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)
-    # Header nominal 2666, 889, 444, 889, 444, 444, 444, 444 carrier ON at end
-    hdr = ((1800, 4000), (593, 1333), (222, 750), (593, 1333), (222, 750), (222, 750), (222, 750), (222, 750))
-    def __init__(self, pin, callback, *args):
-        # Block lasts 23ms nominal and has <=44 edges
-        super().__init__(pin, 44, 30, callback, *args)
-
-    def decode(self, _):
-        try:
-            nedges = self.edge  # No. of edges detected
-            if not 22 <= nedges <= 44:
-                raise RuntimeError(OVERRUN if nedges > 28 else BADSTART)
-            for x, lims in enumerate(self.hdr):
-                width = ticks_diff(self._times[x + 1], self._times[x])
-                if not (lims[0] < width < lims[1]):
-                    self.verbose and print('Bad start', x, width, lims)
-                    raise RuntimeError(BADSTART)
-            x += 1
-            width = ticks_diff(self._times[x + 1], self._times[x])
-            # 2nd bit of last 0 is 444μs (0) or 1333μs (1)
-            if not 222 < width < 1555:
-                self.verbose and print('Bad block 1 Width', width, 'x', x)
-                raise RuntimeError(BADBLOCK)
-            short = width < 889
-            v = int(not short)
-            bit = v
-            bits = 1  # Bits decoded
-            x += 1 + int(short)
-            width = ticks_diff(self._times[x + 1], self._times[x])
-            if not 222 < width < 1555:
-                self.verbose and print('Bad block 2 Width', width, 'x', x)
-                raise RuntimeError(BADBLOCK)
-            short = width < 1111
-            if not short:
-                bit ^= 1
-            x += 1 + int(short)  # If it's short, we know width of next
-            v <<= 1
-            v |= bit  # MSB of result
-            bits += 1
-            # Decode bitstream
-            while bits < 17:
-                # -1 convert count to index, -1 because we look ahead
-                if x > nedges - 2:
-                    raise RuntimeError(BADBLOCK)
-                # width is 444/889 nominal
-                width = ticks_diff(self._times[x + 1], self._times[x])
-                if not 222 < width < 1111:
-                    self.verbose and print('Bad block 3 Width', width, 'x', x)
-                    raise RuntimeError(BADBLOCK)
-                short = width < 666
-                if not short:
-                    bit ^= 1
-                v <<= 1
-                v |= bit
-                bits += 1
-                x += 1 + int(short)
-
-            if self.verbose:
-                 ss = '20-bit format {:020b} x={} nedges={} bits={}'
-                 print(ss.format(v, x, nedges, bits))
-
-            val = v & 0xff
-            addr = (v >> 8) & 0xff
-            ctrl = (v >> 16) & 1
-        except RuntimeError as e:
-            val, addr, ctrl = e.args[0], 0, 0
-        # Set up for new data burst and run user callback
-        self.do_callback(val, addr, ctrl)
diff --git a/ir_rx/__init__.py b/ir_rx/__init__.py
new file mode 100644 (file)
index 0000000..4b96524
--- /dev/null
@@ -0,0 +1,70 @@
+# ir_rx __init__.py Decoder for IR remote control using synchronous code
+# IR_RX abstract base class for IR receivers.
+
+# Author: Peter Hinch
+# Copyright Peter Hinch 2020 Released under the MIT license
+
+from sys import platform
+from machine import Timer, Pin
+from array import array
+from utime import ticks_us
+
+# Save RAM
+# from micropython import alloc_emergency_exception_buf
+# alloc_emergency_exception_buf(100)
+
+
+# On 1st edge start a block timer. While the timer is running, record the time
+# of each edge. When the timer times out decode the data. Duration must exceed
+# the worst case block transmission time, but be less than the interval between
+# a block start and a repeat code start (~108ms depending on protocol)
+
+class IR_RX():
+    # Result/error codes
+    # Repeat button code
+    REPEAT = -1
+    # Error codes
+    BADSTART = -2
+    BADBLOCK = -3
+    BADREP = -4
+    OVERRUN = -5
+    BADDATA = -6
+    BADADDR = -7
+
+    def __init__(self, pin, nedges, tblock, callback, *args):  # Optional args for callback
+        self._nedges = nedges
+        self._tblock = tblock
+        self.callback = callback
+        self.args = args
+        self._errf = lambda _ : None
+        self.verbose = False
+
+        self._times = array('i',  (0 for _ in range(nedges + 1)))  # +1 for overrun
+        if platform == 'esp32' or platform == 'esp32_LoBo':  # ESP32 Doesn't support hard IRQ
+            ei = pin.irq(handler = self._cb_pin, trigger = (Pin.IRQ_FALLING | Pin.IRQ_RISING))
+        else:
+            ei = pin.irq(handler = self._cb_pin, trigger = (Pin.IRQ_FALLING | Pin.IRQ_RISING), hard = True)
+        self._eint = ei  # Keep reference??
+        self.edge = 0
+        self.tim = Timer(-1)  # Sofware timer
+        self.cb = self.decode
+
+    # Pin interrupt. Save time of each edge for later decode.
+    def _cb_pin(self, line):
+        t = ticks_us()
+        # On overrun ignore pulses until software timer times out
+        if self.edge <= self._nedges:  # Allow 1 extra pulse to record overrun
+            if not self.edge:  # First edge received
+                self.tim.init(period=self._tblock , mode=Timer.ONE_SHOT, callback=self.cb)
+            self._times[self.edge] = t
+            self.edge += 1
+
+    def do_callback(self, cmd, addr, ext, thresh=0):
+        self.edge = 0
+        if cmd >= thresh:
+            self.callback(cmd, addr, ext, *self.args)
+        else:
+            self._errf(cmd)
+
+    def error_function(self, func):
+        self._errf = func
diff --git a/ir_rx/nec.py b/ir_rx/nec.py
new file mode 100644 (file)
index 0000000..e516924
--- /dev/null
@@ -0,0 +1,62 @@
+# nec.py Decoder for IR remote control using synchronous code
+# Supports NEC protocol.
+# For a remote using NEC see https://www.adafruit.com/products/389
+
+# Author: Peter Hinch
+# Copyright Peter Hinch 2020 Released under the MIT license
+
+from utime import ticks_us, ticks_diff
+from ir_rx import IR_RX
+
+class NEC_ABC(IR_RX):
+    def __init__(self, pin, extended, callback, *args):
+        # Block lasts <= 80ms (extended mode) and has 68 edges
+        super().__init__(pin, 68, 80, callback, *args)
+        self._extended = extended
+        self._addr = 0
+
+    def decode(self, _):
+        try:
+            if self.edge > 68:
+                raise RuntimeError(self.OVERRUN)
+            width = ticks_diff(self._times[1], self._times[0])
+            if width < 4000:  # 9ms leading mark for all valid data
+                raise RuntimeError(self.BADSTART)
+            width = ticks_diff(self._times[2], self._times[1])
+            if width > 3000:  # 4.5ms space for normal data
+                if self.edge < 68:  # Haven't received the correct number of edges
+                    raise RuntimeError(self.BADBLOCK)
+                # Time spaces only (marks are always 562.5µs)
+                # Space is 1.6875ms (1) or 562.5µs (0)
+                # Skip last bit which is always 1
+                val = 0
+                for edge in range(3, 68 - 2, 2):
+                    val >>= 1
+                    if ticks_diff(self._times[edge + 1], self._times[edge]) > 1120:
+                        val |= 0x80000000
+            elif width > 1700: # 2.5ms space for a repeat code. Should have exactly 4 edges.
+                raise RuntimeError(self.REPEAT if self.edge == 4 else self.BADREP)  # Treat REPEAT as error.
+            else:
+                raise RuntimeError(self.BADSTART)
+            addr = val & 0xff  # 8 bit addr
+            cmd = (val >> 16) & 0xff
+            if cmd != (val >> 24) ^ 0xff:
+                raise RuntimeError(self.BADDATA)
+            if addr != ((val >> 8) ^ 0xff) & 0xff:  # 8 bit addr doesn't match check
+                if not self._extended:
+                    raise RuntimeError(self.BADADDR)
+                addr |= val & 0xff00  # pass assumed 16 bit address to callback
+            self._addr = addr
+        except RuntimeError as e:
+            cmd = e.args[0]
+            addr = self._addr if cmd == self.REPEAT else 0  # REPEAT uses last address
+        # Set up for new data burst and run user callback
+        self.do_callback(cmd, addr, 0, self.REPEAT)
+
+class NEC_8(NEC_ABC):
+    def __init__(self, pin, callback, *args):
+        super().__init__(pin, False, callback, *args)
+
+class NEC_16(NEC_ABC):
+    def __init__(self, pin, callback, *args):
+        super().__init__(pin, True, callback, *args)
diff --git a/ir_rx/philips.py b/ir_rx/philips.py
new file mode 100644 (file)
index 0000000..cbd96f8
--- /dev/null
@@ -0,0 +1,127 @@
+# philips.py Decoder for IR remote control using synchronous code
+# Supports Philips RC-5 RC-6 mode 0 protocols.
+
+# Author: Peter Hinch
+# Copyright Peter Hinch 2020 Released under the MIT license
+
+from utime import ticks_us, ticks_diff
+from ir_rx import IR_RX
+
+class RC5_IR(IR_RX):
+    def __init__(self, pin, callback, *args):
+        # Block lasts <= 30ms and has <= 28 edges
+        super().__init__(pin, 28, 30, callback, *args)
+
+    def decode(self, _):
+        try:
+            nedges = self.edge  # No. of edges detected
+            if not 14 <= nedges <= 28:
+                raise RuntimeError(self.OVERRUN if nedges > 28 else self.BADSTART)
+            # Regenerate bitstream
+            bits = 0
+            bit = 1
+            for x in range(1, nedges):
+                width = ticks_diff(self._times[x], self._times[x - 1])
+                if not 500 < width < 2000:
+                    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:
+                    raise RuntimeError(self.BADBLOCK)
+                v |= b0
+                m0 >>= 2
+                m1 >>= 2
+            # Split into fields (val, addr, ctrl)
+            val = (v & 0x3f) | (0x40 if ((v >> 12) & 1) else 0)
+            addr = (v >> 6) & 0x1f
+            ctrl = (v >> 11) & 1
+
+        except RuntimeError as e:
+            val, addr, ctrl = e.args[0], 0, 0
+        # 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)
+    # Header nominal 2666, 889, 444, 889, 444, 444, 444, 444 carrier ON at end
+    hdr = ((1800, 4000), (593, 1333), (222, 750), (593, 1333), (222, 750), (222, 750), (222, 750), (222, 750))
+    def __init__(self, pin, callback, *args):
+        # Block lasts 23ms nominal and has <=44 edges
+        super().__init__(pin, 44, 30, callback, *args)
+
+    def decode(self, _):
+        try:
+            nedges = self.edge  # No. of edges detected
+            if not 22 <= nedges <= 44:
+                raise RuntimeError(self.OVERRUN if nedges > 28 else self.BADSTART)
+            for x, lims in enumerate(self.hdr):
+                width = ticks_diff(self._times[x + 1], self._times[x])
+                if not (lims[0] < width < lims[1]):
+                    self.verbose and print('Bad start', x, width, lims)
+                    raise RuntimeError(self.BADSTART)
+            x += 1
+            width = ticks_diff(self._times[x + 1], self._times[x])
+            # 2nd bit of last 0 is 444μs (0) or 1333μs (1)
+            if not 222 < width < 1555:
+                self.verbose and print('Bad block 1 Width', width, 'x', x)
+                raise RuntimeError(self.BADBLOCK)
+            short = width < 889
+            v = int(not short)
+            bit = v
+            bits = 1  # Bits decoded
+            x += 1 + int(short)
+            width = ticks_diff(self._times[x + 1], self._times[x])
+            if not 222 < width < 1555:
+                self.verbose and print('Bad block 2 Width', width, 'x', x)
+                raise RuntimeError(self.BADBLOCK)
+            short = width < 1111
+            if not short:
+                bit ^= 1
+            x += 1 + int(short)  # If it's short, we know width of next
+            v <<= 1
+            v |= bit  # MSB of result
+            bits += 1
+            # Decode bitstream
+            while bits < 17:
+                # -1 convert count to index, -1 because we look ahead
+                if x > nedges - 2:
+                    raise RuntimeError(self.BADBLOCK)
+                # width is 444/889 nominal
+                width = ticks_diff(self._times[x + 1], self._times[x])
+                if not 222 < width < 1111:
+                    self.verbose and print('Bad block 3 Width', width, 'x', x)
+                    raise RuntimeError(self.BADBLOCK)
+                short = width < 666
+                if not short:
+                    bit ^= 1
+                v <<= 1
+                v |= bit
+                bits += 1
+                x += 1 + int(short)
+
+            if self.verbose:
+                 ss = '20-bit format {:020b} x={} nedges={} bits={}'
+                 print(ss.format(v, x, nedges, bits))
+
+            val = v & 0xff
+            addr = (v >> 8) & 0xff
+            ctrl = (v >> 16) & 1
+        except RuntimeError as e:
+            val, addr, ctrl = e.args[0], 0, 0
+        # Set up for new data burst and run user callback
+        self.do_callback(val, addr, ctrl)
diff --git a/ir_rx/print_error.py b/ir_rx/print_error.py
new file mode 100644 (file)
index 0000000..31ce51e
--- /dev/null
@@ -0,0 +1,19 @@
+# print_error.py Error print for IR receiver
+
+# Author: Peter Hinch
+# Copyright Peter Hinch 2020 Released under the MIT license
+
+from ir_rx import IR_RX
+
+_errors = {IR_RX.BADSTART : 'Invalid start pulse',
+           IR_RX.BADBLOCK : 'Error: bad block',
+           IR_RX.BADREP : 'Error: repeat',
+           IR_RX.OVERRUN : 'Error: overrun',
+           IR_RX.BADDATA : 'Error: invalid data',
+           IR_RX.BADADDR : 'Error: invalid address'}
+
+def print_error(data):
+    if data in _errors:
+        print(_errors[data])
+    else:
+        print('Unknown error code:', data)
diff --git a/ir_rx/sony.py b/ir_rx/sony.py
new file mode 100644 (file)
index 0000000..9c9147a
--- /dev/null
@@ -0,0 +1,71 @@
+# sony.py Decoder for IR remote control using synchronous code
+# Sony SIRC protocol.
+
+# Author: Peter Hinch
+# Copyright Peter Hinch 2020 Released under the MIT license
+
+from utime import ticks_us, ticks_diff
+from ir_rx import IR_RX
+
+class SONY_ABC(IR_RX):  # Abstract base class
+    def __init__(self, pin, bits, callback, *args):
+        # 20 bit block has 42 edges and lasts <= 39ms nominal. Add 4ms to time
+        # for tolerances except in 20 bit case where timing is tight with a
+        # repeat period of 45ms.
+        t = int(3 + bits * 1.8) + (1 if bits == 20 else 4)
+        super().__init__(pin, 2 + bits * 2, t, callback, *args)
+        self._addr = 0
+        self._bits = 20
+
+    def decode(self, _):
+        try:
+            nedges = self.edge  # No. of edges detected
+            self.verbose and print('nedges', nedges)
+            if nedges > 42:
+                raise RuntimeError(self.OVERRUN)
+            bits = (nedges - 2) // 2
+            if nedges not in (26, 32, 42) or bits > self._bits:
+                raise RuntimeError(self.BADBLOCK)
+            self.verbose and print('SIRC {}bit'.format(bits))
+            width = ticks_diff(self._times[1], self._times[0])
+            if not 1800 < width < 3000:  # 2.4ms leading mark for all valid data
+                raise RuntimeError(self.BADSTART)
+            width = ticks_diff(self._times[2], self._times[1])
+            if not 350 < width < 1000:  # 600μs space
+                raise RuntimeError(self.BADSTART)
+
+            val = 0  # Data received, LSB 1st
+            x = 2
+            bit = 1
+            while x < nedges - 2:
+                if ticks_diff(self._times[x + 1], self._times[x]) > 900:
+                    val |= bit
+                bit <<= 1
+                x += 2
+
+            cmd = val & 0x7f  # 7 bit command
+            val >>= 7
+            if nedges < 42:
+                addr = val & 0xff  # 5 or 8 bit addr
+                val = 0
+            else:
+                addr = val & 0x1f  # 5 bit addr
+                val >>= 5  # 8 bit extended
+        except RuntimeError as e:
+            cmd = e.args[0]
+            addr = 0
+            val = 0
+        self.do_callback(cmd, addr, val)
+
+class SONY_12(SONY_ABC):
+    def __init__(self, pin, callback, *args):
+        super().__init__(pin, 12, callback, *args)
+
+class SONY_15(SONY_ABC):
+    def __init__(self, pin, callback, *args):
+        super().__init__(pin, 15, callback, *args)
+
+class SONY_20(SONY_ABC):
+    def __init__(self, pin, callback, *args):
+        super().__init__(pin, 20, callback, *args)
+
index a436082b411779f7f87c12693ac6bec533065ba1..badd2b559b76117a49f9bee3f2b9b3a87728af00 100644 (file)
@@ -10,16 +10,19 @@ from sys import platform
 import time
 import gc
 from machine import Pin, freq
 import time
 import gc
 from machine import Pin, freq
-from ir_rx import *
-
-ESP32 = platform == 'esp32' or platform == 'esp32_LoBo'
+from ir_rx.print_error import print_error  # Optional print of error codes
+# Import all implemented classes
+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
 
 
+# Define pin according to platform
 if platform == 'pyboard':
     p = Pin('X3', Pin.IN)
 elif platform == 'esp8266':
     freq(160000000)
     p = Pin(13, Pin.IN)
 if platform == 'pyboard':
     p = Pin('X3', Pin.IN)
 elif platform == 'esp8266':
     freq(160000000)
     p = Pin(13, Pin.IN)
-elif ESP32:
+elif platform == 'esp32' or platform == 'esp32_LoBo':
     p = Pin(23, Pin.IN)  # was 27
 
 # User callback
     p = Pin(23, Pin.IN)  # was 27
 
 # User callback
@@ -27,42 +30,30 @@ def cb(data, addr, ctrl):
     if data < 0:  # NEC protocol sends repeat codes.
         print('Repeat code.')
     else:
     if data < 0:  # NEC protocol sends repeat codes.
         print('Repeat code.')
     else:
-        print('Data {:03x} Addr {:03x} Ctrl {:01x}'.format(data, addr, ctrl))
+        print('Data {:02x} Addr {:04x} Ctrl {:02x}'.format(data, addr, ctrl))
 
 
-# Optionally print debug information
-def errf(data):
-    errors = {BADSTART : 'Invalid start pulse', BADBLOCK : 'Error: bad block',
-            BADREP : 'Error: repeat', OVERRUN : 'Error: overrun',
-            BADDATA : 'Error: invalid data', BADADDR : 'Error: invalid address'}
-    print(errors[data])
+def test(proto=0):
+    classes = (NEC_8, NEC_16, SONY_12, SONY_15, SONY_20, RC5_IR, RC6_M0)
+    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()
 
 
+# **** DISPLAY GREETING ****
 s = '''Test for IR receiver. Run:
 from ir_rx_test import test
 s = '''Test for IR receiver. Run:
 from ir_rx_test import test
-test() for NEC protocol,
-test(1) for Sony SIRC 12 bit,
-test(2) for Sony SIRC 15 bit,
-test(3) for Sony SIRC 20 bit,
+test() for NEC 8 bit protocol,
+test(1) for NEC 16 bit,
+test(2) for Sony SIRC 12 bit,
+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.
 
 Hit ctrl-c to stop, then ctrl-d to soft reset.'''
 
 print(s)
 test(5) for Philips RC-5 protocol,
 test(6) for RC6 mode 0.
 
 Hit ctrl-c to stop, then ctrl-d to soft reset.'''
 
 print(s)
-
-def test(proto=0):
-    if proto == 0:
-        ir = NEC_IR(p, cb)  # Extended mode
-    elif proto < 4:
-        ir = SONY_IR(p, cb)
-        ir.bits = (12, 15, 20)[proto - 1]
-        #ir.verbose = True
-    elif proto == 5:
-        ir = RC5_IR(p, cb)
-    elif proto == 6:
-        ir = RC6_M0(p, cb)
-    ir.error_function(errf)  # Show debug information
-    # A real application would do something here...
-    while True:
-        print('running')
-        time.sleep(5)
-        gc.collect()