kvc0 / circuitpython_async Goto Github PK
View Code? Open in Web Editor NEWCircuitpython async event loop library
Circuitpython async event loop library
I've just started using Tasko. Looks pretty cool and looking forward to trying it out.
I have a situation where I want something to run every 60 seconds.
Or maybe every five minutes.
It would be nice to be able to schedule these functions in some manner like "min" or "sec" instead of "hz".
FYI, following up on the last issue i raised (since closed). Apologies to not respond to your reply until now.
The following is a test of time.monotonic() vs time.montonic_ns() module on CP on nRF:
import time
def sleep_ns(delta_ns):
then = time.monotonic_ns()
while time.monotonic_ns() - then <= delta_ns:
pass
def sleep(delta_s):
delta_ns = int(delta_s * 1.0e9)
then = time.monotonic_ns()
while time.monotonic_ns() - then <= delta_ns:
pass
then = time.monotonic()
then_ns = time.monotonic_ns()
for j in range(100):
# val = j * 0.001
# val = j * 100000
val = j * 0.0001
for i in range(3):
now = time.monotonic()
now_ns = time.monotonic_ns()
print(f"j: {j}; n: {now:6.6f}; n_ns: {now_ns}; d: {now - then:.5f}; d_ns: {now_ns - then_ns}; d_s: {(now_ns - then_ns) * 1.0e-9:.10f}; d_st: {(now_ns - then_ns) * 1.0e-9:.4f}")
then = time.monotonic()
then_ns = time.monotonic_ns()
# time.sleep(val)
# sleep_ns(val)
sleep(val)
Sample output:
j: 0; n: 164261.684418; n_ns: 164261680511486; d: 0.00000; d_ns: 213626; d_s: 0.0002136259; d_st: 0.0002
j: 0; n: 164261.684418; n_ns: 164261683654786; d: 0.00000; d_ns: 335685; d_s: 0.0003356849; d_st: 0.0003
j: 0; n: 164261.684418; n_ns: 164261686676027; d: 0.00000; d_ns: 305166; d_s: 0.0003051659; d_st: 0.0003
j: 1; n: 164261.684418; n_ns: 164261689788823; d: 0.00000; d_ns: 366203; d_s: 0.0003662028; d_st: 0.0004
j: 1; n: 164261.684418; n_ns: 164261692901618; d: 0.00000; d_ns: 396734; d_s: 0.0003967339; d_st: 0.0004
j: 1; n: 164261.684418; n_ns: 164261696014414; d: 0.00000; d_ns: 396734; d_s: 0.0003967339; d_st: 0.0004
j: 2; n: 164261.684418; n_ns: 164261699157727; d: 0.00000; d_ns: 396734; d_s: 0.0003967339; d_st: 0.0004
j: 2; n: 164261.684418; n_ns: 164261702331545; d: 0.00000; d_ns: 396720; d_s: 0.0003967198; d_st: 0.0004
j: 2; n: 164261.684418; n_ns: 164261705444341; d: 0.00000; d_ns: 396721; d_s: 0.0003967208; d_st: 0.0004
j: 3; n: 164261.684418; n_ns: 164261708587654; d: 0.00000; d_ns: 427252; d_s: 0.0004272518; d_st: 0.0004
j: 3; n: 164261.684418; n_ns: 164261711853040; d: 0.00000; d_ns: 549324; d_s: 0.0005493236; d_st: 0.0005
j: 3; n: 164261.684418; n_ns: 164261715087894; d: 0.00000; d_ns: 518793; d_s: 0.0005187928; d_st: 0.0005
j: 4; n: 164261.684418; n_ns: 164261718353279; d: 0.00000; d_ns: 549324; d_s: 0.0005493236; d_st: 0.0005
j: 4; n: 164261.684418; n_ns: 164261721740723; d: 0.00000; d_ns: 640864; d_s: 0.0006408638; d_st: 0.0006
j: 4; n: 164261.684418; n_ns: 164261725067145; d: 0.00000; d_ns: 610347; d_s: 0.0006103467; d_st: 0.0006
j: 5; n: 164261.684418; n_ns: 164261728424084; d: 0.00000; d_ns: 640878; d_s: 0.0006408778; d_st: 0.0006
j: 5; n: 164261.684418; n_ns: 164261731872564; d: 0.00000; d_ns: 701900; d_s: 0.0007018996; d_st: 0.0007
j: 5; n: 164261.684418; n_ns: 164261735290540; d: 0.00000; d_ns: 732432; d_s: 0.0007324317; d_st: 0.0007
j: 6; n: 164261.746407; n_ns: 164261738739020; d: 0.00000; d_ns: 732419; d_s: 0.0007324186; d_st: 0.0007
j: 6; n: 164261.746407; n_ns: 164261742340090; d: 0.00000; d_ns: 854491; d_s: 0.0008544905; d_st: 0.0009
j: 6; n: 164261.746407; n_ns: 164261745880137; d: 0.00000; d_ns: 823972; d_s: 0.0008239716; d_st: 0.0008
j: 7; n: 164261.746407; n_ns: 164261749511725; d: 0.00000; d_ns: 915526; d_s: 0.0009155255; d_st: 0.0009
j: 7; n: 164261.746407; n_ns: 164261753143313; d: 0.00000; d_ns: 915526; d_s: 0.0009155255; d_st: 0.0009
j: 7; n: 164261.746407; n_ns: 164261756774915; d: 0.00000; d_ns: 885022; d_s: 0.0008850216; d_st: 0.0009
j: 8; n: 164261.746407; n_ns: 164261760467539; d: 0.00000; d_ns: 946045; d_s: 0.0009460444; d_st: 0.0009
j: 8; n: 164261.746407; n_ns: 164261764251717; d: 0.00000; d_ns: 1037599; d_s: 0.0010375986; d_st: 0.0010
j: 8; n: 164261.746407; n_ns: 164261768035895; d: 0.00000; d_ns: 1037599; d_s: 0.0010375986; d_st: 0.0010
j: 9; n: 164261.746407; n_ns: 164261771911627; d: 0.00000; d_ns: 1129153; d_s: 0.0011291523; d_st: 0.0011
j: 9; n: 164261.746407; n_ns: 164261775787359; d: 0.00000; d_ns: 1159671; d_s: 0.0011596702; d_st: 0.0012
j: 9; n: 164261.746407; n_ns: 164261779663091; d: 0.00000; d_ns: 1159671; d_s: 0.0011596702; d_st: 0.0012
j: 10; n: 164261.746407; n_ns: 164261783569341; d: 0.00000; d_ns: 1159671; d_s: 0.0011596702; d_st: 0.0012
Floating point resolution seems to be the cause of the issue that I had before. The time difference calculation using time.monotonic() produces zero unless delta time gets big enough.
I ended up replacing time in tasko with the following code (not at all optimized) and started to see what I expected.
import time
class Time_ns:
def __init__(self):
pass
def monotonic_ns(self):
return time.monotonic_ns()
def monotonic(self):
ns, lhs, rhs = self._monotonic_float()
lhs = lhs - int(lhs * 1.0e-4) * 1.0e4
return lhs + rhs
def _monotonic_float(self):
tm_ns = time.monotonic_ns()
tm_lhs = int(tm_ns * 1.0e-9) * 1000000000
tm_rhs = tm_ns - tm_lhs
return tm_ns ,tm_lhs * 1.0e-9, (tm_rhs * 1.0e-4) * 0.00001
def _monotonic_binary(self):
# this is not accurate
tm_ns = time.monotonic_ns()
tm_lhs = (tm_ns >> 29) << 29
tm_rhs = tm_ns - tm_lhs
return tm_ns, tm_lhs * 1.0e-9, (tm_rhs * 1.0e-4) * 0.00001
def sleep_ns(self, delta_t_ns):
then = time.monotonic_ns()
while time.monotonic_ns() - then <= delta_ns:
pass
def sleep(self, delta_s):
delta_ns = int(delta_s * 1.0e9)
then = time.monotonic_ns()
while time.monotonic_ns() - then <= delta_ns:
pass
In loop.py in tasko replaced time module as follows:
from Time_ns import Time_ns
time = Time_ns()
# import time
Results:
task1: 10Hz; index: 10; spot_per: 0.12500; avg_per 0.10012, avg_var: +0.024880
task2: 10Hz; index: 10; spot_per: 0.12500; avg_per 0.10012, avg_var: +0.024880
task3: 10Hz; index: 10; spot_per: 0.12500; avg_per 0.10012, avg_var: +0.024880
task4: 50Hz; index: 51; spot_per: 0.00000; avg_per 0.01987, avg_var: -0.019867
task1: 10Hz; index: 10; spot_per: 0.12500; avg_per 0.10012, avg_var: +0.024881
task2: 10Hz; index: 10; spot_per: 0.12500; avg_per 0.10012, avg_var: +0.024881
task3: 10Hz; index: 10; spot_per: 0.12500; avg_per 0.10012, avg_var: +0.024881
task4: 50Hz; index: 51; spot_per: 0.06250; avg_per 0.01987, avg_var: +0.042634
task1: 10Hz; index: 10; spot_per: 0.12500; avg_per 0.10015, avg_var: +0.024854
task2: 10Hz; index: 10; spot_per: 0.12500; avg_per 0.10015, avg_var: +0.024854
task3: 10Hz; index: 11; spot_per: 0.06250; avg_per 0.10013, avg_var: -0.037629
task4: 50Hz; index: 51; spot_per: 0.06250; avg_per 0.01987, avg_var: +0.042630
task1: 10Hz; index: 10; spot_per: 0.12500; avg_per 0.10012, avg_var: +0.024882
task2: 10Hz; index: 10; spot_per: 0.12500; avg_per 0.10012, avg_var: +0.024882
task3: 10Hz; index: 9; spot_per: 0.12500; avg_per 0.10012, avg_var: +0.024882
task4: 50Hz; index: 48; spot_per: 0.06250; avg_per 0.01987, avg_var: +0.042632
task1: 10Hz; index: 10; spot_per: 0.12500; avg_per 0.10012, avg_var: +0.024882
task2: 10Hz; index: 10; spot_per: 0.12500; avg_per 0.10012, avg_var: +0.024882
task3: 10Hz; index: 10; spot_per: 0.12500; avg_per 0.10012, avg_var: +0.024882
task4: 50Hz; index: 52; spot_per: 0.00000; avg_per 0.01987, avg_var: -0.019866
task1: 10Hz; index: 10; spot_per: 0.06250; avg_per 0.10009, avg_var: -0.037591
task2: 10Hz; index: 10; spot_per: 0.06250; avg_per 0.10009, avg_var: -0.037591
task3: 10Hz; index: 10; spot_per: 0.12500; avg_per 0.10012, avg_var: +0.024883
task4: 50Hz; index: 51; spot_per: 0.06250; avg_per 0.01986, avg_var: +0.042635
I am sure there are better ways to deal with this, but thought this might be of interest to anyone traveling through here.
First of all thanks for this great library, is very helpful.
Coming from a past python/twisted tasks experience, I'm missing the possibility to start tasks "later" (at the end of the specified interval)
Right now tasko immediately run all defined tasks when tasko.run
is called. Why not add a flag like immediate=False
in the schedule
fun in order to delay first task run at the end of the defined hz ?
An use case is to periodically publish acquired data, which makes no sense doing immediately. Right now I've just added a "first_run" flag in my publisher helper that just skips on first invocation, but if tasko can support it can be very helpful :)
I keep finding myself in situations where I want to stop/remove a scheduled task, but it's quite tedious to remove a task from tasko at the moment. Maybe you can set me straight?
For lack of a better example...
class Widget:
lowbatt=False
async def power_hungry(self):
# something that takes a lot of power
pass
async def check_battery(self):
if battery_percent() < 10
if not self.lowbatt:
self.lowbatt=True
# remove battery-intensive tasks from scheduler
# tasko.remove(coroutine_function)
elif self.lowbatt:
self.lowbatt=False
# add the battery-intensive task back
tasko.schedule(hz=1,coroutine_function=self.power_hungry)
widget=Widget()
tasko.schedule(hz=1,coroutine_function=widget.power_hungry)
tasko.schedule(hz=0.03,coroutine_function=widget.check_battery)
tasko.run()
The intent here is that our widget will stop doing its power_hungry
task every second if the periodic check_battery
task determines the widget has a low battery voltage.
ISSUE: At above 16Hz the system freezes. If i set the task frequency above 8hz the measured freq gets pegged to 16hz no matter what the setting. However the iteration count seems to match the report interval for the task that is doing the reporting. i put some time.monotonic_ns timing around all of these. The tasks are all completing in under 0.5 ms on average.
Background:
Thank you for this interesting and helpful library i have been building an application around it.
Things have generally been working as described in the documentation but at some point when my application grew to have multiple modules and objects i started to see the scheduler freeze.
i have an event loop and usb, hardware uart and ble uart buffer parsing and and event loop for a total of 4x tasks. orginally the slower usb, ble and event tasks were at 10hz and the uart was at 100hz, because of a lot of serial data streaming. I had all of this working great balanced the buffers etc.
Hello! I wanted to request that you add this library to the CircuitPython Community Bundle, if you are interested in doing so.
Please do not feel obligated, and feel free to close this regardless.
Thank you!
Hello, I'm trying to make a midi arpeggiator. I tried other methods but your library is really precise in terms of midi latency. Thanks for your efforts! Is there a way to change the duration after the run starts? I've added a screenshot; the bpm value will be changing here. I'm very new on GitHub so I'm not sure if this is the right place to ask this, but I couldn't find any other contact info.
def run():
duR = (1000/PPQN_MAX)/(get_bpm()/60) ## = 60000/(24*bpmM) = 2500/bpm
asynccp.schedule(frequency=Duration.of_milliseconds(duR), coroutine_function=count_PPQN)
asynccp.schedule(frequency=80, coroutine_function=updates_UE)
# asynccp.add_task(main())
asynccp.run()
if __name__ == '__main__':
run()
Hi, great library!
I'm attempting to handle a socket connection and pass that connection between two async functions to pose as a reader and writer. my reader (GetCommand
) is receiving data but my writer (DoTask
) doesn't seem to be calling. Is there a problem with me sharing sockets across async functions?
Here's an example of what i'm trying in case there's any glaringly obvious errors or protocols - CircuitPython code on ESP32-S2-WROVER-I:
import wifi
import asynccp
import socketpool
import time
bufSize = 40
buf = bytearray(bufSize)
mv_buf = memoryview(buf)
count = 0
async def DoTask(conn):
# originally had custom functions for spi to attached device,
# would poll for new data before incrementing through buffer
global count
mv_buf[count] = count
count += 1
time.sleep(0.01)
if count == 40:
conn.send(mv_buf)
count = 0
print("sent buffer")
async def GetCommand(conn):
buffer = bytearray(256)
size = conn.recv_into(buffer, 256)
print(buffer[:size])
async def loop(conn):
await GetCommand(conn)
await DoTask(conn)
def StartWiFiServer():
ssid = "esp32-s2"
password = "password123"
wifi.radio.enabled = True
wifi.radio.start_ap(ssid, password)
if __name__ == "__main__":
print("Running main.py")
StartWiFiServer()
pool = socketpool.SocketPool(wifi.radio)
s = pool.socket(pool.AF_INET, pool.SOCK_STREAM)
s.setblocking(1)
HOST = "192.168.4.1"
s.bind((HOST, 99))
s.listen(1)
while True:
conn, addr = s.accept()
with conn:
print("Connected by", addr)
asynccp.add_task(loop(conn))
asynccp.run()
PC code:
import socket
HOST = "192.168.4.1" # The server's hostname or IP address
PORT = 99 # The port used by the server
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(None)
print("Connecting")
s.connect((HOST, PORT))
array = [1,2,3,5,4]
s.send(bytes(array))
#s.rea
res = s.recv(40, socket.MSG_WAITALL)
print(res)
First off @WarriorOfWire, absolutely AWESOME work with tasko!! ⭐⭐
I wanted to report the performances I'm seeing on the few microcontroller boards I have around.
test_schedule_rate
from test_loop.py
(9811f53) and this unittest for CP. All boards running adafruit/circuitpython@7df5d74Board | Microcontroller | Task Count | Throughput |
---|---|---|---|
Feather M4 Express | SAMD51J19 (120 MHz, 192KB SRAM) | 17 | 544 Hz |
PyCubed v05 | SAMD51J19 (120 MHz, 192KB SRAM) | 19 | 665 Hz |
Feather S2 | ESP32-S2 (240 MHz, 320KB SRAM) | 16 | 488 Hz |
It's very curious to me that the 240 MHz board performs the worst! Any thoughts?
This is my first time dabbling with async/await-style programming in Python, so if I'm simply failing to understand something here, please feel free to tell me to RTFM. :)
With that caveat: From reading the source code for asynccp/loop.py
, I was under the impression I could raise StopIteration
from inside the coroutine_function
passed to a ScheduledTask
to achieve the equivalent of calling my_task.stop()
. However, when I actually raise StopIteration
I get a stack trace:
Traceback (most recent call last):
File "code.py", line 307, in <module>
File "/lib/asynccp/loop.py", line 268, in run
File "/lib/asynccp/loop.py", line 275, in _step
File "/lib/asynccp/loop.py", line 303, in _run_task
File "/lib/asynccp/loop.py", line 101, in _run_at_fixed_rate
RuntimeError: generator raised StopIteration
I believe that somewhere between raising StopIteration and the exception being handled in _run_task it's being turned into a RuntimeError
.
I'm currently running this on Adafruit CircuitPython 7.0.0 on 2021-09-20; TinyS2 with ESP32S2
Let me know if some sample code (or any other information) would be helpful.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.