]> vault307.fbx.one Git - micorpython_ir.git/commitdiff
Release 0.1 Various bugfixes and improvements.
authorPeter Hinch <peter@hinch.me.uk>
Sun, 22 Mar 2020 14:36:44 +0000 (14:36 +0000)
committerPeter Hinch <peter@hinch.me.uk>
Sun, 22 Mar 2020 14:36:44 +0000 (14:36 +0000)
RECEIVER.md
TRANSMITTER.md
ir_rx/acquire.py
ir_rx/sony.py
ir_tx/__init__.py
ir_tx/mce.py
ir_tx/mcetest.py
ir_tx/nec.py
ir_tx/philips.py
ir_tx/sony.py
ir_tx/test.py

index ac20e18053898572badb6fe7eae1d0865ef06767..d6998f1cb25c1acd077f369576148ef12d2237d3 100644 (file)
@@ -55,7 +55,8 @@ test()
 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
 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.
+report produced by the script exposed to an unknown protocol is unpredictable.
+The `test()` function returns a list of the mark and space periods (in μs).
 
 # 3. The driver
 
 
 # 3. The driver
 
@@ -280,6 +281,12 @@ 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. Unsupported protocols
+
+It is possible to capture an IR burst from a remote and to re-create it using
+the transmitter. This has limitations and is discussed in detail in
+[the transmitter doc](./TRANSMITTER.md#5-unsupported-protocols).
+
 # 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
index 4f15c656e190e56494ff0a6f3f76593f7a3bf445..2cf169aabd4c879b601b7f2ce56f88a158298f98 100644 (file)
@@ -97,19 +97,40 @@ Constructor args:
  3. `verbose=False` If `True` emits (a lot of) debug output.
 
 Method:
  3. `verbose=False` If `True` emits (a lot of) debug output.
 
 Method:
- 1. `transmit(addr, data, toggle=0)` Integer args. `addr` and `data` are
- normally 8-bit values and `toggle` is normally 0 or 1; details are protocol
- dependent and are described below.
+ 1. `transmit(addr, data, toggle=0, validate=False)` Args `addr`, `data` and
+ `toggle` are positive integers. The maximum vaues are protocol dependent. If
+ `validate` is `True` passed values are checked and a `ValueError` raised if
+ they are out of range. If `validate` is false invalid bits are silently
+ discarded. For example if an address of 0x11 is passed to `MCE.transmit`, the
+ address sent will be 1 because that protocol supports only a four bit address
+ field. The `toggle` field is unused by some protocols when 0 should be passed.
+
+Class method:
+ 1. `active_low` No args. Pyboard only. A `ValueError` will be thrown on ESP32.
+ The IR LED drive circuit is usually designed to turn the LED on if the driver
+ pin is high. If it has opposite polarity the method must be called before
+ instantiating the class - it will be ineffective if called later.
+
+Class varaible:
+ 1. `timeit=False` If `True` the `.transmit` method times itself and prints the
+ result in μs.
 
 The `transmit` method is synchronous with rapid return. Actual transmission
 
 The `transmit` method is synchronous with rapid return. Actual transmission
-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.
+occurs as a background process, on the Pyboard controlled by timers 2 and 5. On
+ESP32 the RMT class is used. Execution times were measured on a Pyboard 1.1 and
+the ESP32 reference board  without SPIRAM. Tests were done at stock frequency and
+with `validate=True`, `verbose=False`. A small saving could be achieved by
+skipping validation.
+
+| Protocol | ESP32 | Pyboard |
+|:--------:|:-----:|:-------:|
+| NEC      | 7.8ms | 3.2ms   |
+| SONY12   | 3.2ms | 1.3ms   |
+| SONY15   | 3.6ms | 1.5ms   |
+| SONY20   | 4.5ms | 1.9ms   |
+| RC5      | 4.9ms | 1.5ms   |
+| RC6_M0   | 6.0ms | 2.0ms   |
+| MCE      | 6.7ms | 2.0ms   |
 
 #### NEC class
 
 
 #### NEC class
 
@@ -199,8 +220,6 @@ require 3.
 
 # 4. Principle of operation
 
 
 # 4. Principle of operation
 
-## 4.1 Pyboard
-
 The classes inherit from the abstract base class `IR`. This has an array `.arr`
 to contain the duration (in μs) of each carrier on or off period. The
 `transmit` method calls a `tx` method of the subclass which populates this
 The classes inherit from the abstract base class `IR`. This has an array `.arr`
 to contain the duration (in μs) of each carrier on or off period. The
 `transmit` method calls a `tx` method of the subclass which populates this
@@ -212,18 +231,25 @@ this is required for bi-phase (manchester) codings.
 The `.add` method takes a single μs time value and adds it to the last value
 in the array: this pulse lengthening is used in bi-phase encodings.
 
 The `.add` method takes a single μs time value and adds it to the last value
 in the array: this pulse lengthening is used in bi-phase encodings.
 
-On completion of the subclass `.tx`, `.transmit` appends a special `STOP` value
-and initiates physical transmission which occurs in an interrupt context.
+On completion of the subclass `.tx`, `.transmit` calls `.trigger` which
+initiates transmission as a background process. Its behaviour is platform
+dependent.
+
+## 4.1 Pyboard
 
 
-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.
+Tramsmission 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 the 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
-and re-initialises T5 for the new duration.
+and re-initialises T5 for the new duration. If it is `STOP` it ensures that the
+duty ratio is set to the `_SPACE`
+
+Here `.trigger` appends a special `STOP` value and initiates physical
+transmission by calling the Timer5 callback.
 
 ## 4.2 ESP32
 
 
 ## 4.2 ESP32
 
@@ -235,7 +261,59 @@ 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.
 
+The `.trigger` method calls `RMT.write_pulses` and returns with `RMT` operating
+in the background.
+
 ## 4.3 Duty ratio
 
 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.
 ## 4.3 Duty ratio
 
 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.
+
+# 5. Unsupported protocols
+
+You can use the receiver module to capture an IR burst and replay it with the
+transmitter. This enables limited support for unknown protocols. This is
+strictly for experimenters and I haven't documented it in detail.
+
+There are two limitations. The first is timing accuracy: both receiving and
+transmitting processes introduce some timing uncertainty. This is only likely
+to be a practical problem with fast protocols. In brief testing with a known
+protocol the scripts below worked.
+
+The more tricky problem is handling repeat keys: different protocols use widely
+varying approaches. If repeat keys are to be supported some experimentation and
+coding is likely to be required.
+
+The following captures a single burst and saves it to a file:  
+```python
+from ir_rx.acquire import test
+import ujson
+
+lst = test()  # May report unsupported or unknown protocol
+with open('burst.py', 'w') as f:
+    ujson.dump(lst, f)
+```
+This replays it:  
+```python
+from ir_tx import Player
+from sys import platform
+import ujson
+
+if platform == 'esp32':
+    from machine import Pin
+    pin = (Pin(23, Pin.OUT, value = 0), Pin(21, Pin.OUT, value = 0))
+else:
+    from pyb import Pin, LED
+    pin = Pin('X1')
+with open('burst.py', 'r') as f:
+    lst = ujson.load(f)
+ir = Player(pin)
+ir.play(lst)
+```
+The `ir_tx.Player` class is a minimal subclass supporting only the `.play`
+method. This takes as an arg an iterable comprising time values of successive
+mark and space periods (in μs).
+
+The `ir_rx.acquire.test` function makes assumptions about the likely maximum
+length and maximum duration of a burst. In some cases this may require some
+modification e.g. to instantiate `IR_GET` with different args.
index e8c6a1243b3174db935296a4f9d46b291d2546fa..e5162b21250b0aae398dc5fc52c81237f3774337 100644 (file)
@@ -103,4 +103,4 @@ def test():
         pin = Pin(23, Pin.IN)
     irg = IR_GET(pin)
     print('Waiting for IR data...')
         pin = Pin(23, Pin.IN)
     irg = IR_GET(pin)
     print('Waiting for IR data...')
-    irg.acquire()
+    return irg.acquire()
index 9c9147a709d1fcb2a4419e319d5eddbfdd064d7b..1050356d710db12bb1394c067f7a13e86adc58b2 100644 (file)
@@ -37,12 +37,11 @@ class SONY_ABC(IR_RX):  # Abstract base class
             val = 0  # Data received, LSB 1st
             x = 2
             bit = 1
             val = 0  # Data received, LSB 1st
             x = 2
             bit = 1
-            while x < nedges - 2:
+            while x <= nedges - 2:
                 if ticks_diff(self._times[x + 1], self._times[x]) > 900:
                     val |= bit
                 bit <<= 1
                 x += 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:
             cmd = val & 0x7f  # 7 bit command
             val >>= 7
             if nedges < 42:
index 5f0071871abfab5e7c6b3efa1393a24cf204ef04..ab166277ef59f8e2c2939b60671d8cd329ea9ddc 100644 (file)
@@ -14,12 +14,10 @@ else:
 
 from micropython import const
 from array import array
 
 from micropython import const
 from array import array
-import micropython
-
+from time import ticks_us, ticks_diff
+# import micropython
 # micropython.alloc_emergency_exception_buf(100)
 
 # micropython.alloc_emergency_exception_buf(100)
 
-# Duty ratio in carrier off state.
-_SPACE = const(0)
 # On ESP32 gate hardware design is led_on = rmt and carrier
 
 # Shared by NEC
 # On ESP32 gate hardware design is led_on = rmt and carrier
 
 # Shared by NEC
@@ -27,13 +25,20 @@ STOP = const(0)  # End of data
 
 # IR abstract base class. Array holds periods in μs between toggling 36/38KHz
 # carrier on or off. Physical transmission occurs in an ISR context controlled
 
 # IR abstract base class. Array holds periods in μs between toggling 36/38KHz
 # 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.
+# by timer 2 and timer 5. See TRANSMITTER.md for details of operation.
 class IR:
 class IR:
-    active_high = True  # Hardware turns IRLED on if pin goes high.
+    _active_high = True  # Hardware turns IRLED on if pin goes high.
+    _space = 0  # Duty ratio that causes IRLED to be off
+    timeit = False  # Print timing info
+
+    @classmethod
+    def active_low(cls):
+        if ESP32:
+            raise ValueError('Cannot set active low on ESP32')
+        cls._active_high = False
+        cls._space = 100
 
     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()
@@ -41,9 +46,11 @@ class IR:
             self._pwm.init(freq=cfreq, duty=round(duty * 10.23))
             self._rmt = RMT(0, pin=pin[1], clock_div=80)  # 1μs resolution
         else:  # Pyboard
             self._pwm.init(freq=cfreq, duty=round(duty * 10.23))
             self._rmt = RMT(0, pin=pin[1], clock_div=80)  # 1μs resolution
         else:  # Pyboard
+            if not IR._active_high:
+                duty = 100 - duty
             tim = Timer(2, freq=cfreq)  # Timer 2/pin produces 36/38/40KHz carrier
             self._ch = tim.channel(1, Timer.PWM, pin=pin)
             tim = Timer(2, freq=cfreq)  # Timer 2/pin produces 36/38/40KHz carrier
             self._ch = tim.channel(1, Timer.PWM, pin=pin)
-            self._ch.pulse_width_percent(_SPACE)  # Turn off IR LED
+            self._ch.pulse_width_percent(self._space)  # Turn off IR LED
             # Pyboard: 0 <= pulse_width_percent <= 100
             self._duty = duty
             self._tim = Timer(5)  # Timer 5 controls carrier on/off times
             # Pyboard: 0 <= pulse_width_percent <= 100
             self._duty = duty
             self._tim = Timer(5)  # Timer 5 controls carrier on/off times
@@ -60,19 +67,30 @@ class IR:
         p = self.aptr
         v = self._arr[p]
         if v == STOP:
         p = self.aptr
         v = self._arr[p]
         if v == STOP:
-            self._ch.pulse_width_percent(_SPACE)  # Turn off IR LED.
+            self._ch.pulse_width_percent(self._space)  # Turn off IR LED.
             return
             return
-        self._ch.pulse_width_percent(_SPACE if p & 1 else self._duty)
+        self._ch.pulse_width_percent(self._space if p & 1 else self._duty)
         self._tim.init(prescaler=84, period=v, callback=self._tcb)
         self.aptr += 1
 
     # Public interface
     # Before populating array, zero pointer, set notional carrier state (off).
         self._tim.init(prescaler=84, period=v, callback=self._tcb)
         self.aptr += 1
 
     # Public interface
     # Before populating array, zero pointer, set notional carrier state (off).
-    def transmit(self, addr, data, toggle=0):  # NEC: toggle is unused
+    def transmit(self, addr, data, toggle=0, validate=False):  # NEC: toggle is unused
+        t = ticks_us()
+        if validate:
+            if addr > self.valid[0] or addr < 0:
+                raise ValueError('Address out of range', addr)
+            if data > self.valid[1] or data < 0:
+                raise ValueError('Data out of range', data)
+            if toggle > self.valid[2] or toggle < 0:
+                raise ValueError('Toggle out of range', toggle)
         self.aptr = 0  # Inital conditions for tx: index into array
         self.carrier = False
         self.tx(addr, data, toggle)  # Subclass populates ._arr
         self.trigger()  # Initiate transmission
         self.aptr = 0  # Inital conditions for tx: index into array
         self.carrier = False
         self.tx(addr, data, toggle)  # Subclass populates ._arr
         self.trigger()  # Initiate transmission
+        if self.timeit:
+            dt = ticks_diff(ticks_us(), t)
+            print('Time = {}μs'.format(dt))
 
     # Subclass interface
     def trigger(self):  # Used by NEC to initiate a repeat frame
 
     # Subclass interface
     def trigger(self):  # Used by NEC to initiate a repeat frame
@@ -95,3 +113,16 @@ class IR:
         self.verbose and print('add', t)
         # .carrier unaffected
         self._arr[self.aptr - 1] += t
         self.verbose and print('add', t)
         # .carrier unaffected
         self._arr[self.aptr - 1] += t
+
+
+# Given an iterable (e.g. list or tuple) of times, emit it as an IR stream.
+class Player(IR):
+
+    def __init__(self, pin, freq=38000, verbose=False):  # NEC specifies 38KHz
+        super().__init__(pin, freq, 68, 33, verbose)  # Measured duty ratio 33%
+
+    def play(self, lst):
+        for x, t in enumerate(lst):
+            self._arr[x] = t
+        self.aptr = x + 1
+        self.trigger()
index 24f1aef53d75c229c77e647278ea7006d90c0f24..b2b7267f7d9a45f49f1a6ddef68198a42d6f3261 100644 (file)
@@ -13,6 +13,7 @@ _TBIT = const(500)  # Time (μs) for pulse of carrier
 
 
 class MCE(IR):
 
 
 class MCE(IR):
+    valid = (0xf, 0x3f, 3)  # Max addr, data, toggle
     init_cs = 4  # http://www.hifi-remote.com/johnsfine/DecodeIR.html#OrtekMCE says 3
 
     def __init__(self, pin, freq=38000, verbose=False):
     init_cs = 4  # http://www.hifi-remote.com/johnsfine/DecodeIR.html#OrtekMCE says 3
 
     def __init__(self, pin, freq=38000, verbose=False):
index 2b4af96671e84d7a669bb4d3b031bf4973b4baa3..250c693c260b5bcd62633c991b0e8ad4bf4f4f7a 100644 (file)
@@ -36,8 +36,7 @@ class Rbutton:
         self.stop = False
 
     def cfunc(self):  # Button push: send data and set up for repeats
         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.irb.transmit(self.addr, self.data, _FIRST, True)
         self.tim.trigger(_REP_DELAY)
 
     def ofunc(self):  # Button release: cancel repeat timer
         self.tim.trigger(_REP_DELAY)
 
     def ofunc(self):  # Button release: cancel repeat timer
@@ -48,19 +47,19 @@ class Rbutton:
         if self.stop:  # Button has been released: send last message
             self.stop = False
             self.tim.stop()  # Not strictly necessary
         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')
+            self.irb.transmit(self.addr, self.data, _END, True)
         else:
         else:
-            print('rep')
             self.tim.trigger(_REP_DELAY)
             self.tim.trigger(_REP_DELAY)
-            self.irb.transmit(self.addr, self.data, _REP)
+            self.irb.transmit(self.addr, self.data, _REP, True)
 
 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')
 
 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)
+    irb = MCE(pin)  # verbose=True)
+    # Uncomment the following to print transmit timing
+    # irb.timeit = True
 
     b = []  # Rbutton instances
     px3 = Pin(18, Pin.IN, Pin.PULL_UP) if ESP32 else Pin('X3', Pin.IN, Pin.PULL_UP)
 
     b = []  # Rbutton instances
     px3 = Pin(18, Pin.IN, Pin.PULL_UP) if ESP32 else Pin('X3', Pin.IN, Pin.PULL_UP)
index 10cc67bfe164b72bcb5822dbbc02af3aefa3caf9..da1af24a8ae75a0f32735a04aedf56c7c5e61b4c 100644 (file)
@@ -11,6 +11,7 @@ _TBURST = const(563)
 _T_ONE = const(1687)
 
 class NEC(IR):
 _T_ONE = const(1687)
 
 class NEC(IR):
+    valid = (0xffff, 0xff, 0)  # Max addr, data, toggle
 
     def __init__(self, pin, freq=38000, verbose=False):  # NEC specifies 38KHz
         super().__init__(pin, freq, 68, 33, verbose)  # Measured duty ratio 33%
 
     def __init__(self, pin, freq=38000, verbose=False):  # NEC specifies 38KHz
         super().__init__(pin, freq, 68, 33, verbose)  # Measured duty ratio 33%
index 3ca270ae746fd79734029a4b9b22fb51566dd32b..90b9de941f4d7bc17729fab3e195e9ba1f2a53ef 100644 (file)
@@ -12,6 +12,7 @@ _T_RC5 = const(889)  # Time for pulse of carrier
 
 
 class RC5(IR):
 
 
 class RC5(IR):
+    valid = (0x1f, 0x3f, 1)  # Max addr, data, toggle
 
     def __init__(self, pin, freq=36000, verbose=False):
         super().__init__(pin, freq, 28, 30, verbose)
 
     def __init__(self, pin, freq=36000, verbose=False):
         super().__init__(pin, freq, 28, 30, verbose)
@@ -37,6 +38,7 @@ _T_RC6 = const(444)
 _T2_RC6 = const(889)
 
 class RC6_M0(IR):
 _T2_RC6 = const(889)
 
 class RC6_M0(IR):
+    valid = (0xff, 0xff, 1)  # Max addr, data, toggle
 
     def __init__(self, pin, freq=36000, verbose=False):
         super().__init__(pin, freq, 44, 30, verbose)
 
     def __init__(self, pin, freq=36000, verbose=False):
         super().__init__(pin, freq, 44, 30, verbose)
index 86a23ddfddf17c3d5ec022ed1daf653600122080..7372f5d59256f137a78e30914067144b3cd5ee58 100644 (file)
@@ -32,14 +32,17 @@ class SONY_ABC(IR):
 
 # Sony specifies 40KHz
 class SONY_12(SONY_ABC):
 
 # Sony specifies 40KHz
 class SONY_12(SONY_ABC):
+    valid = (0x1f, 0x7f, 0)  # Max addr, data, toggle
     def __init__(self, pin, freq=40000, verbose=False):
         super().__init__(pin, 12, freq, verbose)
 
 class SONY_15(SONY_ABC):
     def __init__(self, pin, freq=40000, verbose=False):
         super().__init__(pin, 12, freq, verbose)
 
 class SONY_15(SONY_ABC):
+    valid = (0xff, 0x7f, 0)  # Max addr, data, toggle
     def __init__(self, pin, freq=40000, verbose=False):
         super().__init__(pin, 15, freq, verbose)
 
 class SONY_20(SONY_ABC):
     def __init__(self, pin, freq=40000, verbose=False):
         super().__init__(pin, 15, freq, verbose)
 
 class SONY_20(SONY_ABC):
+    valid = (0x1f, 0x7f, 0xff)  # Max addr, data, toggle
     def __init__(self, pin, freq=40000, verbose=False):
         super().__init__(pin, 20, freq, verbose)
 
     def __init__(self, pin, freq=40000, verbose=False):
         super().__init__(pin, 20, freq, verbose)
 
index 9e199dd6a886ed8e86c3ba1ea6bbe22b20a5d923..c3a0ab87c8dc50638134150b907a8b741f3b9662 100644 (file)
@@ -20,20 +20,24 @@ from ir_tx.philips import RC5, RC6_M0
 
 loop = asyncio.get_event_loop()
 
 
 loop = asyncio.get_event_loop()
 
+# If button is held down normal behaviour is to retransmit
+# but most NEC models send a REPEAT code
 class Rbutton:
     toggle = 1  # toggle is ignored in NEC mode
 class Rbutton:
     toggle = 1  # toggle is ignored in NEC mode
-    def __init__(self, irb, pin, addr, data, rep_code=False):
+    def __init__(self, irb, pin, addr, data, proto):
         self.irb = irb
         self.sw = Switch(pin)
         self.addr = addr
         self.data = data
         self.irb = irb
         self.sw = Switch(pin)
         self.addr = addr
         self.data = data
-        self.rep_code = rep_code
+        self.proto = proto
+
         self.sw.close_func(self.cfunc)
         self.sw.open_func(self.ofunc)
         self.tim = Delay_ms(self.repeat)
 
     def cfunc(self):  # Button push: send data
         self.sw.close_func(self.cfunc)
         self.sw.open_func(self.ofunc)
         self.tim = Delay_ms(self.repeat)
 
     def cfunc(self):  # Button push: send data
-        self.irb.transmit(self.addr, self.data, Rbutton.toggle)
+        tog = 0 if self.proto < 3 else Rbutton.toggle  # NEC, sony 12, 15: toggle==0
+        self.irb.transmit(self.addr, self.data, tog, True)  # Test validation
         # Auto repeat. The Sony protocol specifies 45ms but this is tight.
         # In 20 bit mode a data burst can be upto 39ms long.
         self.tim.trigger(108)
         # Auto repeat. The Sony protocol specifies 45ms but this is tight.
         # In 20 bit mode a data burst can be upto 39ms long.
         self.tim.trigger(108)
@@ -46,28 +50,28 @@ class Rbutton:
         await asyncio.sleep(0)  # Let timer stop before retriggering
         if not self.sw():  # Button is still pressed: retrigger
             self.tim.trigger(108)
         await asyncio.sleep(0)  # Let timer stop before retriggering
         if not self.sw():  # Button is still pressed: retrigger
             self.tim.trigger(108)
-            if self.rep_code:
+            if self.proto == 0:
                 self.irb.repeat()  # NEC special case: send REPEAT code
             else:
                 self.irb.repeat()  # NEC special case: send REPEAT code
             else:
-                self.irb.transmit(self.addr, self.data, Rbutton.toggle)
+                tog = 0 if self.proto < 3 else Rbutton.toggle  # NEC, sony 12, 15: toggle==0
+                self.irb.transmit(self.addr, self.data, tog, True)  # Test validation
 
 async def main(proto):
 
 async def main(proto):
-    # Test uses a 38KHz carrier. Some Philips systems use 36KHz.
-    # If button is held down normal behaviour is to retransmit
-    # but most NEC models send a REPEAT code
-    rep_code = proto == 0  # Rbutton constructor requires False for RC-X. NEC protocol only.
+    # Test uses a 38KHz carrier.
     if ESP32:  # Pins for IR LED gate
         pin = (Pin(23, Pin.OUT, value = 0), Pin(21, Pin.OUT, value = 0))
     else:
         pin = Pin('X1')
     classes = (NEC, SONY_12, SONY_15, SONY_20, RC5, RC6_M0)
     irb = classes[proto](pin, 38000)  # My decoder chip is 38KHz
     if ESP32:  # Pins for IR LED gate
         pin = (Pin(23, Pin.OUT, value = 0), Pin(21, Pin.OUT, value = 0))
     else:
         pin = Pin('X1')
     classes = (NEC, SONY_12, SONY_15, SONY_20, RC5, RC6_M0)
     irb = classes[proto](pin, 38000)  # My decoder chip is 38KHz
+    # Uncomment the following to print transmit timing
+    # irb.timeit = 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 = []  # 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, rep_code))
-    b.append(Rbutton(irb, px4, 0x10, 0xb, rep_code))
+    b.append(Rbutton(irb, px3, 0x1, 0x7, proto))
+    b.append(Rbutton(irb, px4, 0x10, 0xb, proto))
     if ESP32:
         while True:
             print('Running')
     if ESP32:
         while True:
             print('Running')