ESP32 Debouncing Switches & Relays: IRQ Timer vs Simple Code vs ASYNC
- pythoncoder
- Posts: 5956
- Joined: Fri Jul 18, 2014 8:01 am
- Location: UK
- Contact:
Re: ESP32 Debouncing Switches & Relays: IRQ Timer vs Simple Code vs ASYNC
It's hard to answer that without knowing more. For uasyncio compatibility you should use nonblocking sockets; these require some attention to detail to read and write from them. You'll find some example code in this repo. In particular look at the simple test client and server code in the temporary directory. The server is very minimal and can only connect one client: a full-fat server is in the root directory.
Peter Hinch
Index to my micropython libraries.
Index to my micropython libraries.
Re: ESP32 Debouncing Switches & Relays: IRQ Timer vs Simple Code vs ASYNC
Hi Peter, thanks for your reply and pointers, it is appreciated.
I only use a very minimalist socket, so the temporary folder code looks familiar, and I already use a non-blocking socket, so I'll give that a try.
I think my biggest challenge is to stop thinking about coding sequentially and realize that co-routines require a complete re-structuring of a program. I'm already finding it to be a very useful mechanism that works really well, so I am about to completely revamp my coding to incorporate async.
I have another function where delays exist (posting to an external server), so I'm thinking it will help with that too.
Regards, AB
I only use a very minimalist socket, so the temporary folder code looks familiar, and I already use a non-blocking socket, so I'll give that a try.
I think my biggest challenge is to stop thinking about coding sequentially and realize that co-routines require a complete re-structuring of a program. I'm already finding it to be a very useful mechanism that works really well, so I am about to completely revamp my coding to incorporate async.
I have another function where delays exist (posting to an external server), so I'm thinking it will help with that too.
Regards, AB
Re: ESP32 Debouncing Switches & Relays: IRQ Timer vs Simple Code vs ASYNC
Hi Peter, I have spent quite a few hours trying to make this work with your debounce code, specifically the "Test for the Pushbutton class (coroutines)". I am wanting to provide the ability to register button presses connected to input pins on the ESP32, and that code of yours works perfectly.
The problem arises when I try to combine the button press recognition with a web server that also provides inputs from the user. I am using some pretty basic socket code the run the web server, but I can't seem to combine these 2 things to work together. The web server just waits for input and the button presses don't register, so I presume I have it in the wrong place.
I have tried all kinds of different scenarios with the async coding, but I'll be danged if I can figure out what to do. I'm wondering if you have any suggestions?
I put the shortened version of the socket code below, and the button press code in a separate window below that. I know this should be relatively easy, but as a newbie I'm missing something, I'm sure.
Regards, AB
This is the web socket code:
this is the button press code I am trying to combine it with (it's your code, expanded to accommodate 4 switches):
The problem arises when I try to combine the button press recognition with a web server that also provides inputs from the user. I am using some pretty basic socket code the run the web server, but I can't seem to combine these 2 things to work together. The web server just waits for input and the button presses don't register, so I presume I have it in the wrong place.
I have tried all kinds of different scenarios with the async coding, but I'll be danged if I can figure out what to do. I'm wondering if you have any suggestions?
I put the shortened version of the socket code below, and the button press code in a separate window below that. I know this should be relatively easy, but as a newbie I'm missing something, I'm sure.
Regards, AB
This is the web socket code:
Code: Select all
# define web server socket parameters
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.setblocking(False)
s.bind(('',80))
s.settimeout(None)
# listen for response
while True:
# wait for browser action - either reload or form submission
conn,addr=s.accept()
request=conn.recv(1024)
request=str(request)
# check if it's an actual POST or just a refresh
detect_post = request.find('POST')
# actions
if detect_post > 0:
#do something
else:
# display web form
html_form = form_input(browser_tab_title)
conn.send(html_form)
conn.close()
html_form = ''
Code: Select all
# Quit test by setting var enable_switches
async def kill_switches():
enable_switches = True
while enable_switches:
# this does not affect debounce but must be there
await asyncio.sleep_ms(1)
# actions to perform (coroutine)
async def switch_activated(switch_id, action, delay):
global oled_on
if action == 'pressed' and debug: print('\n---\nSwitch ' + str(switch_id) + ' was pressed')
if action == 'released' and debug: print('Switch ' + str(switch_id) + ' was released')
if action == 'double-click' and debug: print('Switch ' + str(switch_id) + ' was registered')
if action == 'long-press' and debug: print('Switch ' + str(switch_id) + ' was registered')
if action == 'pressed' and oled_on == False and switch_id == 3: # turn on oled display
oled.invert(0) # White text on black background
oled.contrast(255) # Maximum contrast
oled.fill(0)
oled.text("WiFi connected!", 0, 0)
oled.show()
oled_on = True
if debug: print('OLED turned on by user.')
# turn off oled after delay
await asyncio.sleep_ms(10000)
oled.invert(0) # White text on black background
oled.contrast(0) # Minimum contrast
oled.fill(0)
oled.show()
oled_on = False
if debug: print('OLED timed out.')
elif action == 'pressed' and switch_id == 3: # turn off oled display
oled.invert(0) # White text on black background
oled.contrast(0) # Minimum contrast
oled.fill(0)
oled.show()
oled_on = False
if debug: print('OLED turned off by user.')
# this delay has nothing to do with debounce
# await asyncio.sleep_ms(delay)
#if action == 'pressed': print ('pause timer done\n---\n')
# Test for the Pushbutton class (coroutines)
# Pass True to test suppress
def load_switch_async(suppress1, dc1, lp1, suppress2, dc2, lp2,suppress3, dc3, lp3,suppress4, dc4, lp4):
print('Switches are enabled: scheduling coroutines.')
print(helptext)
# CONFIGURE SWITCH 1
pin1 = Pin(36, Pin.IN, None) # pin 36 does not have internal resistor
sw1 = Pushbutton(pin1, suppress1)
# CONFIGURE SWITCH 2
pin2 = Pin(25, Pin.IN, Pin.PULL_DOWN) # pin 25 has internal resistor
sw2 = Pushbutton(pin2, suppress2)
# CONFIGURE SWITCH 3
pin3 = Pin(26, Pin.IN, Pin.PULL_DOWN) # pin 26 has internal resistor
sw3 = Pushbutton(pin3, suppress3)
# CONFIGURE SWITCH 4
pin4 = Pin(39, Pin.IN, None) # pin 39 does not have internal resistor
sw4 = Pushbutton(pin4, suppress4)
delay = 10 #msec
# Switch 1 actions
sw1.press_func(switch_activated, (1, 'pressed', delay))
sw1.release_func(switch_activated, (1, 'released', delay))
if lp1:
sw1.long_func(switch_activated, (1, 'long-press', delay))
if debug: print('Long Press on Switch 1 is enabled')
if dc1:
sw1.double_func(switch_activated, (1, 'double-click', delay))
if debug: print('Double Click on Switch 1 is enabled')
# Switch 2 actions
sw2.press_func(switch_activated, (2, 'pressed', delay))
sw2.release_func(switch_activated, (2, 'released', delay))
if lp2:
sw2.long_func(switch_activated, (2, 'long-press', delay))
if debug: print('Long Press on Switch 2 is enabled')
if dc2:
sw2.double_func(switch_activated, (2, 'double-click', delay))
if debug: print('Double Click on Switch 2 is enabled')
# Switch 3 actions
sw3.press_func(switch_activated, (3, 'pressed', delay))
sw3.release_func(switch_activated, (3, 'released', delay))
if lp3:
sw3.long_func(switch_activated, (3, 'long-press', delay))
if debug: print('Long Press on Switch 3 is enabled')
if dc3:
sw3.double_func(switch_activated, (3, 'double-click', delay))
if debug: print('Double Click on Switch 3 is enabled')
# Switch 4 actions
sw4.press_func(switch_activated, (4, 'pressed', delay))
sw4.release_func(switch_activated, (4, 'released', delay))
if lp4:
sw4.long_func(switch_activated, (4, 'long-press', delay))
if debug: print('Long Press on Switch 4 is enabled')
if dc4:
sw4.double_func(switch_activated, (4, 'double-click', delay))
if debug: print('Double Click on Switch 4 is enabled')
# maintain loop
loop = asyncio.get_event_loop()
#loop.run_forever()
loop.run_until_complete(kill_switches())
# END MODIFIED TEST
# ======================================
# start async switch routine
load_switch_async(suppress1=False, dc1=False, lp1=False, suppress2=False, dc2=False, lp2=False,suppress3=False, dc3=False, lp3=False,suppress4=False, dc4=False, lp4=True)
Last edited by iotman on Sun Feb 10, 2019 2:39 pm, edited 1 time in total.
- pythoncoder
- Posts: 5956
- Joined: Fri Jul 18, 2014 8:01 am
- Location: UK
- Contact:
Re: ESP32 Debouncing Switches & Relays: IRQ Timer vs Simple Code vs ASYNC
You set s to nonblocking, but the new socket conn returned by s.accept is blocking. You will need to adapt the method of reading from it accordingly.
I'm not sure that making s nonblocking is wise. If you look at the server code in the repo I referenced, the run function at the start illustrates the normal way of using a server socket: it is set to blocking mode but polled using the select mechanism. This makes the function nonblocking (well, it blocks for 1ms nominal).
In general if async code (such as my pushbutton driver) becomes unresponsive in an application it is because something is blocking. You need to track down the culprit.
I'm not sure that making s nonblocking is wise. If you look at the server code in the repo I referenced, the run function at the start illustrates the normal way of using a server socket: it is set to blocking mode but polled using the select mechanism. This makes the function nonblocking (well, it blocks for 1ms nominal).
In general if async code (such as my pushbutton driver) becomes unresponsive in an application it is because something is blocking. You need to track down the culprit.
Peter Hinch
Index to my micropython libraries.
Index to my micropython libraries.
Re: ESP32 Debouncing Switches & Relays: IRQ Timer vs Simple Code vs ASYNC
Hi Peter, I did not realize I was using something that was blocking; I had assumed wrongly that setting s as non-blocking would be enough; thanks for pointing that out. I was hoping for a simpler solution, but down the rabbit hole I go ...
So it seems I have to use the polling version of the socket if I want it to work as a co-routine under the asyncio system. I copied the code you had pointed to, starting with the web server. Here is the link, just to be certain we're on the same page:
https://github.com/peterhinch/micropyth ... ry/serv.py
I added a little bit of code to make sure the wifi was active. I was expecting to see the text 'Message from server.' when I load the IP address in my browser (I set it to the default port of 80).
But it won't run, I get an OSError: 112 at line 40, which is:
s_sock.bind(addr)
According to this list of error codes:
http://www.ioplex.com/~miallen/errcmp.html
the error is EHOSTDOWN 112: Host is down
I'm not certain that is the correct error code, though. Do you happen to know where there is a specific list of Micropython error codes? I have had a lot of trouble finding that.
So back to the problem: am I expecting the correct thing with that text appearing in my browser?
I have put the code below in its entirety.
Regards and thanks for the help, AB
So it seems I have to use the polling version of the socket if I want it to work as a co-routine under the asyncio system. I copied the code you had pointed to, starting with the web server. Here is the link, just to be certain we're on the same page:
https://github.com/peterhinch/micropyth ... ry/serv.py
I added a little bit of code to make sure the wifi was active. I was expecting to see the text 'Message from server.' when I load the IP address in my browser (I set it to the default port of 80).
But it won't run, I get an OSError: 112 at line 40, which is:
s_sock.bind(addr)
According to this list of error codes:
http://www.ioplex.com/~miallen/errcmp.html
the error is EHOSTDOWN 112: Host is down
I'm not certain that is the correct error code, though. Do you happen to know where there is a specific list of Micropython error codes? I have had a lot of trouble finding that.
So back to the problem: am I expecting the correct thing with that text appearing in my browser?
I have put the code below in its entirety.
Regards and thanks for the help, AB
Code: Select all
# Run under CPython 3.5+ or MicroPython Unix build
# Aims to detect missed messages on socket where connection is via WiFi
import sys
upython = sys.implementation.name == 'micropython'
if upython:
import usocket as socket
import uasyncio as asyncio
import uselect as select
import uerrno as errno
import ujson as json
import utime as time
else:
import socket
import asyncio
import select
import errno
import json
import time
# get wifi IP address and network name
import network
wlan = network.WLAN(network.STA_IF)
wifi_info_tuple=wlan.ifconfig()
wifi_info_array=list(wifi_info_tuple)
ip_address=wifi_info_array[0]
ssid = wlan.config('essid')
print('IP Address is: ' + ip_address)
PORT = 80
print('Port: ' + str(PORT))
async def run(loop):
addr = socket.getaddrinfo('192.168.1.44', PORT, 0, socket.SOCK_STREAM)[0][-1]
s_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # server socket
s_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s_sock.bind(addr)
s_sock.listen(5)
print('Awaiting connection on', PORT)
poller = select.poll()
poller.register(s_sock, select.POLLIN)
while True:
res = poller.poll(1)
if res:
c_sock, _ = s_sock.accept() # get client socket
c_sock.setblocking(False)
loop.create_task(reader(c_sock))
loop.create_task(writer(c_sock))
await asyncio.sleep(0.2)
async def reader(sock):
print('Reader start')
istr = ''
last = -1
while True:
try:
d = sock.recv(4096) # bytes object
except OSError as e:
err = e.args[0]
if err == errno.EAGAIN: # Would block: try later
await asyncio.sleep(0.05)
else:
if d == b'': # Reset by peer
raise OSError('Client fail.')
istr += d.decode() # Add to any partial message
# Strings from this point
l = istr.split('\n')
istr = l.pop() # '' unless partial line
for line in l:
data = json.loads(line)
print('Got', data)
if last >= 0 and data[0] - last -1:
raise OSError('Missed message')
last = data[0]
async def writer(sock):
print('Writer start')
data = [0, 'Message from server.']
while True:
m = '{}\n'.format(json.dumps(data))
await send(sock, m.encode('utf8'))
data[0] += 1
print('sent', m)
await asyncio.sleep(0.25) # ???
async def send(sock, d):
while d:
try:
ns = sock.send(d) # Raise OSError if client fails
except OSError as e:
err = e.args[0]
if err == errno.EAGAIN: # Would block: try later
await asyncio.sleep(0.1)
else:
d = d[ns:]
if d:
await asyncio.sleep(0.05)
loop = asyncio.get_event_loop()
loop.create_task(run(loop))
loop.run_forever()
Re: ESP32 Debouncing Switches & Relays: IRQ Timer vs Simple Code vs ASYNC
Hi Peter, one other thought I had was to put the button press code into an interrupt timer, and leave the socket code as is. Do you think that might work?
It would foil the attempt at a full async solution, but I'm feeling a bit leery of the polling socket code as so much of my code is involved with the wifi aspect.
Regards, AB
It would foil the attempt at a full async solution, but I'm feeling a bit leery of the polling socket code as so much of my code is involved with the wifi aspect.
Regards, AB
- pythoncoder
- Posts: 5956
- Joined: Fri Jul 18, 2014 8:01 am
- Location: UK
- Contact:
Re: ESP32 Debouncing Switches & Relays: IRQ Timer vs Simple Code vs ASYNC
That link is to a very rudimentary server intended for a specific test. The one I had in mind is this one.
There are two sockets to consider. The server socket performs the connection and accept returns a client socket: one for each client which connects. I use the server socket in blocking mode with a poller object reducing blocking time to 1ms. I set the client sockets to nonblocking mode. However some programmers advocate using blocking sockets and polling throughout. I think it's largely a matter of personal choice, but polling may be more efficient with large numbers of connected clients.
My personal view is that, if you're serious about writing firmware, it's well worth using uasyncio. Cooperative multi tasking greatly simplifies any code which has to handle multiple asynchronous events.
There are two sockets to consider. The server socket performs the connection and accept returns a client socket: one for each client which connects. I use the server socket in blocking mode with a poller object reducing blocking time to 1ms. I set the client sockets to nonblocking mode. However some programmers advocate using blocking sockets and polling throughout. I think it's largely a matter of personal choice, but polling may be more efficient with large numbers of connected clients.
My personal view is that, if you're serious about writing firmware, it's well worth using uasyncio. Cooperative multi tasking greatly simplifies any code which has to handle multiple asynchronous events.
Peter Hinch
Index to my micropython libraries.
Index to my micropython libraries.
Re: ESP32 Debouncing Switches & Relays: IRQ Timer vs Simple Code vs ASYNC
Hi Peter, thanks for your reply.
I agree that I should try to get the async approach working, but I can't seem to get any of the socket code working. I tried that more complex server code, but immediately got an error:
File "a8.py", line 15, in <module>
ValueError: cannot perform relative import
I am only using the web server to set some parameters and control relays, so it definitely does not need to be a fully fledged system. I'm hoping I can get the simpler examples working first, so that I can better understand how the async polling works, and that may also be all I need for his project.
Do you have any idea why I could not run that code in the temporary folder, with regards to the previous post where I get this error:
the error is EHOSTDOWN 112: Host is down
Regards, AB
I agree that I should try to get the async approach working, but I can't seem to get any of the socket code working. I tried that more complex server code, but immediately got an error:
File "a8.py", line 15, in <module>
ValueError: cannot perform relative import
I am only using the web server to set some parameters and control relays, so it definitely does not need to be a fully fledged system. I'm hoping I can get the simpler examples working first, so that I can better understand how the async polling works, and that may also be all I need for his project.
Do you have any idea why I could not run that code in the temporary folder, with regards to the previous post where I get this error:
the error is EHOSTDOWN 112: Host is down
Regards, AB
- pythoncoder
- Posts: 5956
- Joined: Fri Jul 18, 2014 8:01 am
- Location: UK
- Contact:
Re: ESP32 Debouncing Switches & Relays: IRQ Timer vs Simple Code vs ASYNC
I think it's trying to tell you that there is no server on 192.168.1.44 port 80.
Peter Hinch
Index to my micropython libraries.
Index to my micropython libraries.
Re: ESP32 Debouncing Switches & Relays: IRQ Timer vs Simple Code vs ASYNC
Hi Peter, I'm surprised that it seems so difficult to monitor inputs and run a web server at the same time; I would have thought a lot of folks would want to do that.
I'll keep working on it, studying the code to figure out the polling. I'm also going to try and get that fully fledged web server running, although there is a lot of code in there that is waaay above my head, but I will look at it as a learning experience.
Can you possibly explain that initial problem I'm having, with regards to the import on line 15? I have never seen anything like that importing syntax ...
from . import gmid, isnew
Tks, AB
I'll keep working on it, studying the code to figure out the polling. I'm also going to try and get that fully fledged web server running, although there is a lot of code in there that is waaay above my head, but I will look at it as a learning experience.
Can you possibly explain that initial problem I'm having, with regards to the import on line 15? I have never seen anything like that importing syntax ...
from . import gmid, isnew
Tks, AB