From 3bc46448ddca0bfdf3a98d96c3e9315159bf642b Mon Sep 17 00:00:00 2001 From: Peter Hinch Date: Fri, 6 Mar 2020 07:41:27 +0000 Subject: [PATCH] Prior to package. --- README.md | 79 ++++++++++++++++++++++++++------------------------- ir_rx.py | 68 +++++++++++++++++++++++++++++++++----------- ir_rx_test.py | 22 +++++++------- 3 files changed, 104 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index b35bd20..c107a78 100644 --- a/README.md +++ b/README.md @@ -96,37 +96,20 @@ This implements a class for each supported protocol, namely `NEC_IR`, appropriate class with a callback. The callback will run whenever an IR pulse train is received. -Constructor: -`NEC_IR` args: `pin`, `callback`, `extended=True`, `*args` -`SONY_IR` args: `pin`, `callback`, `bits=20`, `*args` -`RC5_IR` and `RC6_M0`: args `pin`, `callback`, `*args` +#### Common to all classes -Args (all protocols): +Constructor: +Args: 1. `pin` is a `machine.Pin` instance configured as an input, connected to the IR decoder chip. - 2. `callback` is the user supplied callback (see below). - 4. `*args` Any further args will be passed to the callback. - -Protocol specific args: - 1. `extended` NEC specific `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. - 2. `bits=20` Sony specific `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 callback takes the following args: - 1. `data` (`int`) Value from the remote. A negative value indicates an error - except for the value of -1 which signifies an NEC repeat code (see below). + 2. `callback` is the user supplied callback. + 3. `*args` Any further args will be passed to the callback. + +The user callback takes the following args: + 1. `data` (`int`) Value from the remote. Normally in range 0-255. A value < 0 + signifies an NEC repeat code. 2. `addr` (`int`) Address from the remote. - 3. `ctrl` (`int`) The meaning of this is protocol dependent. + 3. `ctrl` (`int`) The meaning of this is protocol dependent: NEC: 0 Philips: this is toggled 1/0 on repeat button presses. If the button is held down it is not toggled. The transmitter demo implements this behaviour. @@ -134,15 +117,38 @@ The callback takes the following args: value. 4. Any args passed to the constructor. -Class variable: +Bound variable: 1. `verbose=False` If `True` emits debug output. +Method: + 1. `error_function` Arg: a function taking a single arg. If this is specified + 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. + # 4.1 Errors IR reception is inevitably subject to errors, notably if the remote is operated near the limit of its range, if it is not pointed at the receiver or if its -batteries are low. The user callback should check for, and usually ignore, -errors. These are flagged by data values < `REPEAT` (-1). +batteries are low. The user callback is not called when an error occurs. On ESP8266 and ESP32 there is a further source of errors. This results from the large and variable interrupt latency of the device which can exceed the pulse @@ -152,14 +158,9 @@ On ESP8266 some improvment may be achieved by running the chip at 160MHz. In general applications should provide user feedback of correct reception. Users tend to press the key again if the expected action is absent. -Data values passed to the callback are zero or positive. Negative values -indicate a repeat code or an error. - -`REPEAT` A repeat code was received. - -Any data value < `REPEAT` (-1) denotes an error. In general applications do not -need to decode these, but they may be of use in debugging. For completeness -they are listed below. +In debugging a callback can be specified for reporting errors. The value passed +to the error function are represented by constants indicating the cause of the +error. These are as follows: `BADSTART` A short (<= 4ms) start pulse was received. May occur due to IR interference, e.g. from fluorescent lights. The TSOP4838 is prone to producing @@ -169,7 +170,7 @@ owing to high interrupt latency. `BADREP` A repeat block: an incorrect number of edges were received. `OVERRUN` A normal data block: too many edges received. `BADDATA` Data did not match check byte. -`BADADDR` Where `extended` is `False` the 8-bit address is checked +`BADADDR` (`NEC_IR`) If `extended` is `False` the 8-bit address is checked against the check byte. This code is returned on failure. # 4.2 Receiver platforms diff --git a/ir_rx.py b/ir_rx.py index 69d2da9..210d617 100644 --- a/ir_rx.py +++ b/ir_rx.py @@ -40,12 +40,15 @@ BADADDR = -7 # a block start and a repeat code start (~108ms depending on protocol) class IR_RX(): - verbose = False + _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': @@ -70,12 +73,29 @@ class IR_RX(): 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, extended=True, *args): - # Block lasts <= 80ms and has 68 edges - tblock = 80 if extended else 73 # Allow for some tx tolerance (?) - super().__init__(pin, 68, tblock, callback, *args) - self._extended = extended + 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, _): @@ -113,19 +133,26 @@ class NEC_IR(IR_RX): except RuntimeError as e: cmd = e.args[0] addr = self._addr if cmd == REPEAT else 0 # REPEAT uses last address - self.edge = 0 # Set up for new data burst and run user callback - self.callback(cmd, addr, 0, *self.args) + # 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, bits=20, *args): + 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 = bits + self._bits = 20 def decode(self, _): try: @@ -165,8 +192,17 @@ class SONY_IR(IR_RX): cmd = e.args[0] addr = 0 val = 0 - self.edge = 0 # Set up for new data burst and run user callback - self.callback(cmd, addr, val, *self.args) + 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): @@ -213,8 +249,8 @@ class RC5_IR(IR_RX): except RuntimeError as e: val, addr, ctrl = e.args[0], 0, 0 - self.edge = 0 # Set up for new data burst and run user callback - self.callback(val, addr, ctrl, *self.args) + # 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 @@ -284,5 +320,5 @@ class RC6_M0(IR_RX): ctrl = (v >> 16) & 1 except RuntimeError as e: val, addr, ctrl = e.args[0], 0, 0 - self.edge = 0 # Set up for new data burst and run user callback - self.callback(val, addr, ctrl, *self.args) + # Set up for new data burst and run user callback + self.do_callback(val, addr, ctrl) diff --git a/ir_rx_test.py b/ir_rx_test.py index b4f1cf9..a436082 100644 --- a/ir_rx_test.py +++ b/ir_rx_test.py @@ -22,18 +22,19 @@ elif platform == 'esp8266': elif ESP32: p = Pin(23, Pin.IN) # was 27 -errors = {BADSTART : 'Invalid start pulse', BADBLOCK : 'Error: bad block', - BADREP : 'Error: repeat', OVERRUN : 'Error: overrun', - BADDATA : 'Error: invalid data', BADADDR : 'Error: invalid address'} - +# User callback def cb(data, addr, ctrl): - if data == REPEAT: # NEC protocol sends repeat codes. + if data < 0: # NEC protocol sends repeat codes. print('Repeat code.') - elif data >= 0: - print('Data {:03x} Addr {:03x} Ctrl {:01x}'.format(data, addr, ctrl)) else: - print(errors[data]) # Application would ignore errors + print('Data {:03x} Addr {:03x} Ctrl {:01x}'.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]) s = '''Test for IR receiver. Run: from ir_rx_test import test @@ -52,13 +53,14 @@ def test(proto=0): if proto == 0: ir = NEC_IR(p, cb) # Extended mode elif proto < 4: - bits = (12, 15, 20)[proto - 1] - ir = SONY_IR(p, cb, bits) + 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') -- 2.47.3