Remote is a standard NEC remote with 16 bit address, so I have commented out the 8 bit code
The code for the IR remote is based from pythoncoder - https://github.com/peterhinch/micropyth ... ers/nec_ir
I changed it slightly, so the decoded message is printed for deugging and it places the commands in a Queue for later processing.
If I disable the webserver and just use the remote, everything works fine.
Below is output from pressing off and on button.
_run: starting NEC_IR
_decode: Val: hex - da25fbe2
Button press: data:25 addr:fbe2 - LED off
_decode: Val: hex - d926fbe2
Button press: data:26 addr:fbe2 - LED on
If I add the tcp server using server = await asyncio.start_server(client_server, '0.0.0.0', 80) , then the IR decode message is garbled.
main: started http server
_run: starting NEC_IR
_decode: Val: hex - 22222223
Button press: data:-6 addr:2223 - Incorrect remote
_decode: Val: hex - 0000000a
Button press: data:-6 addr:000a - Incorrect remote
_decode: Val: hex - 72236900
This happens with no connections active on the tcp server. I'm assuming the IRQ interrupts are being delayed by the tcp server which is waiting for connections. The tcp server works normally.
Does anyone know if there is a solution for this?
I can think of two, but don't like either:
- rewrite the aremote to by synchronous and disable the IRQ for the duration - this appears to be ~80ms; which doesn't sound too bad.
- Add a 2nd microcontroller to handle the IR and pass it through UART to the esp32 - but it seems a waste of hardware.
Code: Select all
# aremote.py Decoder for NEC protocol IR remote control
# e.g.https://www.adafruit.com/products/389
# Hacked to add a http server - but doesn't work
# Author: Peter Hinch
# Copyright Peter Hinch 2017 Released under the MIT license
from sys import platform
import uasyncio as asyncio
from primitives.message import Message
from primitives.queue import Queue
from micropython import const
from array import array
from utime import ticks_ms, ticks_us, ticks_diff
from machine import Pin
import gc
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
_EDGECOUNT = const(68) # No. of edges in data block
# On 1st edge start a block timer. When it times out decode the data. Time must
# exceed the worst case block transmission time, but (with asyncio latency) be
# less than the interval between a block start and a repeat code start (108ms)
# Value of 73 allows for up to 35ms latency.
class NEC_IR():
def __init__(self, pin, callback, extended, msg_queue, *args): # Optional args for callback
self._ev_start = Message()
self._callback = callback
self._extended = extended
self._addr = 0
self._args = args
self.block_time = 80 if extended else 73 # Allow for some tx tolerance (?)
self._times = array('i', (0 for _ in range(_EDGECOUNT + 1))) # +1 for overrun
pin.irq(handler = self._cb_pin, trigger = (Pin.IRQ_FALLING | Pin.IRQ_RISING))
self._edge = 0
self._ev_start.clear()
self._msg_queue = msg_queue
async def _run(self):
print("_run: starting NEC_IR")
try:
while True:
await self._ev_start # Wait until data collection has started
# Compensate for asyncio latency
latency = ticks_diff(ticks_ms(), self._ev_start.value())
await asyncio.sleep_ms(self.block_time - latency) # Data block should have ended
self._decode() # decode, clear event, prepare for new rx, call cb
except Exception as err:
print("_run: Exception: {}".format(err))
finally:
print("_run: finshed")
# 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 <= _EDGECOUNT: # Allow 1 extra pulse to record overrun
if not self._ev_start.is_set(): # First edge received
self._ev_start.set(ticks_ms()) # asyncio latency compensation
self._times[self._edge] = t
self._edge += 1
def _decode(self):
try:
overrun = self._edge > _EDGECOUNT
val = OVERRUN if overrun else BADSTART
if not overrun:
width = ticks_diff(self._times[1], self._times[0])
if width > 4000: # 9ms leading mark for all valid data
width = ticks_diff(self._times[2], self._times[1])
if width > 3000: # 4.5ms space for normal data
if self._edge < _EDGECOUNT:
# Haven't received the correct number of edges
val = BADBLOCK
else:
# 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, _EDGECOUNT - 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.
val = REPEAT if self._edge == 4 else BADREP
addr = 0
if val >= 0: # validate. Byte layout of val ~cmd cmd ~addr addr
print("_decode: Val: hex - {:08x}".format(val))
addr = val & 0xff
cmd = (val >> 16) & 0xff
# if addr == ((val >> 8) ^ 0xff) & 0xff: # 8 bit address OK
# val = cmd if cmd == (val >> 24) ^ 0xff else BADDATA
# self._addr = addr
# else:
addr |= val & 0xff00 # pass assumed 16 bit address to callback
# if self._extended:
val = cmd if cmd == (val >> 24) ^ 0xff else BADDATA
self._addr = addr
# else:
# val = BADADDR
if val == REPEAT:
addr = self._addr # Last valid addresss
self._edge = 0 # Set up for new data burst and run user callback
self._ev_start.clear()
# self._callback(val, addr, *self._args)
self._msg_queue.put_nowait((val, addr))
except Exception as e:
print("_decode: exception: {}".format(e))
async def ir_cb(led, ir_rx_queue):
while True:
try:
# await asyncio.sleep_ms(1)
(data, addr) = await ir_rx_queue.get()
print("Button press: data:{:02x} addr:{:04x}".format(data, addr), end=' - ')
if addr == 0xfbe2: # Adapt for your remote
if data == 0x26: # Button 1. Adapt for your remote/buttons
print('LED on')
led(1)
elif data == 0x25:
print('LED off')
led(0)
elif data == REPEAT:
print('Repeat')
elif data < REPEAT:
print('Bad IR data')
else:
print("Unnown data")
else:
print('Incorrect remote')
except Exception as e:
print("ir_cb: exception: {}".format(e))
async def client_server(reader, writer):
cid = ticks_ms()
print("client({}) - connection established".format(cid))
req = await reader.readline()
print(req)
method, uri, proto = req.split(b" ")
while True:
h = await reader.readline()
if h == b"" or h == b"\r\n":
break
print("client({}) - Received {}".format(cid,h))
writer.write(b'HTTP/1.0 200 OK\r\n')
writer.write("Content-Type: application/json\r\n\r\n")
writer.write(b'{"msg":"HELLO"}\r\n')
writer.write(b'\r\n')
await writer.drain()
writer.close()
await writer.wait_closed()
print("client({}) - connection closed".format(cid))
gc.collect()
async def main(ir, ir_rx_queue):
asyncio.create_task(ir._run())
asyncio.create_task(ir_cb(led, ir_rx_queue))
try:
print("main: started http server")
server = await asyncio.start_server(client_server, '0.0.0.0', 80)
while True:
await asyncio.sleep(60)
except Exception as err:
print("main: Exception: ",err)
finally:
print("main: closing server")
server.close()
await server.wait_closed()
print("main: server closed")
p = Pin(23, Pin.IN)
led = Pin(16, Pin.OUT) # LED with 220Ω series resistor between 3.3V and pin 21
led(1)
ir_rx_queue = Queue()
ir = NEC_IR(p, ir_cb, True, ir_rx_queue, False) # Assume extended address mode r/c
try:
asyncio.run(main(ir, ir_rx_queue))
except KeyboardInterrupt:
print('Interrupted') # This mechanism doesn't work on Unix build.
except Exception as err:
print("Exception: ",err)
finally:
_ = asyncio.new_event_loop()