-
Notifications
You must be signed in to change notification settings - Fork 343
thread
Python functions can be run in thread.
It means the function execution runs in a ESP32 FreeRTOS task separate from the main MicroPython thread and apears as running in background.
- Maximum number of threads that can be started is defined via menuconfig. As each thread needs some memory (for stack), creating large amount of threads can couse stack overflow.
- The thread function can be any object of type function (defined with def) or bound_method (defined as class method).
The function can accept arguments, both positional and kw, which must be matched in_thread.start_new_thread
- Thread function is usualy defined as infinite loop. See the thread function template for more details.
- Threads can be created, suspended (paused), resumed and stopped.
- Threads can comunicate with each other or the main thread (repl) using notifications and messages.
- Thread terminates when the thread function is finished, the return is executed or on exception in the thread function.
- If the thread function runs periodically, it is recomended to use
utime.sleep
,utime.sleep__ms
or_thread.wait
functions. While those function are executed, no CPU or MicroPython resources are used, the waiting is handled on FreeRTOS level.
Start a new thread and return its identifier.
The returned thread ID is used with most methods to access the created thread.
Argument | Description |
---|---|
th_name | string, used to describe the thread |
th_func | MicroPython object of type function (defined with def) or bound_method (defined as class method). This is the main thread function which usualy runs in infinite loop. See the thread function template for more details. |
args | the thread positional arguments given as tuple of objects. If the thread function requires no arguments, an empty tuple must be given - () |
kwargs | optional; if used, must be given as dictionary of keyword arguments |
npth =_thread.start_new_thread("Neopixel", thrainbow, ())
bmeth =_thread.start_new_thread("BME280", bmerun, (60,))
testth = _thread.start_new_thread("Test", thread_entry, (10, 20), {'a2': 0, 'a3': 1})
If executed without arguments returns the thread stack size (in bytes) used when creating new threads.
The optional size argument specifies the stack size to be used for subsequently created threads.
The maximum stack size used by the thread can be checked with _thread.list()
_thread.stack_size(10*1024)
testth = _thread.start_new_thread("Test",thread_entry, (10, 20), {'a2': 0, 'a3': 1})
The default behavior of the thread after it is created is to not allow to be suspended.
This method can be used to explicitly allow the thread suspension.
The method must be called from the thread function.
Suspend the execution of the thread function on FreeRTOS level.
Resume the execution of the thread function previously suspended with _thread.suspend() on FreeRTOS level.
Terminate the thread, free all allocated memory.
The thread function must handle _thread.EXIT
notification, see the thread function template.
Get the name of the thread whose id is th_id.
Get the name of the thread executing this method.
The method must be called from the thread function.
Get the thread id of the main (repl) thread.
Can be uset to send notifications/messages to the main thread. |
The method must be called from the thread function.
Suspend the execution of the thread function until some notification is received or timeout expires.
If the optional argument timeout
is not given, the function will wait indefinitely.
Returns integer >0 (the notification value) if the notification was received while waiting or 0 on timeout.
The method must be called from the thread function.
Check if any notification was sent to the thread executing this method.
Returns integer >0 (the notification value) if there was pending notification or 0 if not.
The method must be called from the thread function.
Check if any message was sent to the thread executing this method.
Returns 3-items tuple containing message type, sender thread id and message itself (integer or string).
The method must be called from the thread function. |
Send notification to the thread with id th_id.
**value is integer >0.
Send message to the thread with id th_id.
msg can be integer or string.
Returns True if the main thread (repl) is allowed to accept messages.
If executed from the main thread, optional flag (True | False) argument can be given to allow/dissallow accepting messages in the main thread.
Returns the thread status.
Print the status of all created threads.
If the optional print argument is set to False, returns the tuple with created threads information.br>
Thread info tuple has th_id, type, name, state, stack size and max stack used items
_thread.PAUSE
, _thread.SUSPEND
, _thread.RESUME
, _thread.STOP
, _thread.EXIT
_thread.RUNNING
, _thread.SUSPENDED
, _thread.WAITING
, _thread.TERMINATED
def th_func(arg):
#print("TH_FUNC: started")
# ------------------------------------------------------------
# Allow suspending this thread from other threads using
# _thread.suspend(th_id) / _thread.resume(th_id) functions.
# If not set, the thread connot be suspended.
# Still, "soft" suspend handling via notifications can be used
# ------------------------------------------------------------
_thread.allowsuspend(True)
# ---------------------------------------------
# Thread function usually runs in infinite loop
# ---------------------------------------------
while True:
# ================================================
# It is recommended to handle thread notifications
# ================================================
ntf = _thread.getnotification()
if ntf:
# some notification received
if ntf == _thread.EXIT:
# -------------------------------------------------
# Return from thread function terminates the thread
# -------------------------------------------------
#print("TH_FUNC: terminated")
return
elif ntf == _thread.SUSPEND:
# -------------------------------------------------------------------
# The thread can be suspended using _thread.suspend(th_id) function,
# but sometimes it is more convenient to implement the "soft" suspend
# -------------------------------------------------------------------
#print("TH_FUNC: suspended")
# wait for RESUME notification indefinitely, some other thread must
# send the resume notification: _thread.notify(th_id, _thread.RESUME)
while _thread.wait() != _thread.RESUME:
pass
# ---------------------------------------------
# Handle the application specific notifications
# ---------------------------------------------
elif ntf == 1234:
# Your notification handling code
pass
else:
# -----------------------------
# Default notification handling
# -----------------------------
pass
# ----------------------------------
# Put your thread function code here
# ----------------------------------
x1 = 23.45
# ---------------------------------------------------------------------------
# Thread function execution can be suspended until a notification is received
# Without argument '_thread.wait' will wait for notification indefinitely
# If the timeout argument is given, the function will return even if
# no notification is received after the timeout expires with result 'None'
# ---------------------------------------------------------------------------
ntf = _thread.wait(30000)
if ntf:
# --------------------------------------------
# Check if terminate notification was received
# --------------------------------------------
if ntf == _thread.EXIT:
return
#print("TH_FUNC: Notification received: ", ntf)
# ---------------------------------------------
# Execute some code based on notification value
# ---------------------------------------------
if ntf == 77:
print("Notification 77 received"
elif ntf == 8888:
myvar += 100
else:
# ---------------------------------
# Execute some code on wait timeout
# ---------------------------------
print("TH_FUNC: Wait notification timeout")
# ---------------------------------------------------------------
# - Using sleep in thread function -
# ---------------------------------------------------------------
# 'utime.sleep(sec, True)' & 'utime.sleep_ms(ms, True)' functions returns the
# actual ellapsed sleep time. The sleep will be interrupted if
# a notification is received and the returned value will be less
# than the requested one
# ---------------------------------------------------------------
# Example:
print("TH_FUNC: Loop started")
for i in range(0, 5):
print("TH_FUNC: Loop no:", i)
sleep_time = utime.sleep_ms(10000, True)
if sleep_time < 10000:
# Notification received while sleeping
print("TH_FUNC: Notification while sleeping", st)
# Sleep for the remaining interval if needed
utime.sleep_ms(10000 - sleep_time)
print("TH_FUNC: Loop ended")
# ===================================================================================
# Handle inter thread message
# Sender thread ID, message type (string or integer) and message itself are available
# ===================================================================================
typ, sender, msg = _thread.getmsg()
if msg:
# -------------------------------------
# message received from 'sender' thread
# -------------------------------------
# Reply to sender, we can analyze the message first
_thread.sendmsg(sender, "[%s] Hi %s, received your message." % (_thread.getSelfName(), _thread.getThreadName(sender)))
# We can inform the main MicroPython thread (REPL) about received message
_thread.sendmsg(_thread.getReplID(), "[%s] Received message from '%s'\n'%s'" % (_thread.getSelfName(), _thread.getThreadName(sender), msg))
import machine, _thread, time
import micropython, gc
import bme280
# Setup the LED pins
bled = machine.Pin(4, mode=machine.Pin.OUT)
#rled = machine.Pin(0, mode=machine.Pin.OUT)
#gled = machine.Pin(2, mode=machine.Pin.OUT)
bled.value(0)
#gled.value(0)
#rled.value(0)
# Setup I2C to be used with BME280 sensor
i2c=machine.I2C(scl=machine.Pin(26),sda=machine.Pin(25),speed=400000)
# Initialize BME280
bme=bme280.BME280(i2c=i2c)
# Define LED thread function
#---------------------------
def rgbled(n=200, led=bled):
notif_exit = 4718
notif_replay = 2
notif_count = 3
x = 0
_thread.allowsuspend(True)
while True:
led.value(1)
time.sleep_ms(n)
led.value(0)
x = x + 1
t = 10
while t > 0:
notif = _thread.getnotification()
if notif == notif_exit:
_thread.sendmsg(_thread.getReplID(), "[%s] Exiting" % (_thread.getSelfName()))
return
elif notif == notif_replay:
_thread.sendmsg(_thread.getReplID(), "[%s] I've been notified" % (_thread.getSelfName()))
elif notif == notif_count:
_thread.sendmsg(_thread.getReplID(), "[%s] Run counter = %u" % (_thread.getSelfName(), x))
elif notif == 777:
_thread.sendmsg(_thread.getReplID(), "[%s] Forced EXCEPTION" % (_thread.getSelfName()))
time.sleep_ms(1000)
zz = 234 / 0
elif notif != 0:
_thread.sendmsg(_thread.getReplID(), "[%s] Got unknown notification: %u" % (_thread.getSelfName(), notif))
typ, sender, msg = _thread.getmsg()
if msg:
_thread.sendmsg(_thread.getReplID(), "[%s] Message from '%s'\n'%s'" % (_thread.getSelfName(), _thread.getThreadName(sender), msg))
time.sleep_ms(100)
t = t - 1
gc.collect()
# For LED thread we don't need more than 3K stack
_ = _thread.stack_size(3*1024)
# Start LED thread
#rth=_thread.start_new_thread("R_Led", rgbled, (100, rled))
time.sleep_ms(500)
#gth=_thread.start_new_thread("G_Led", rgbled, (250, gled))
bth=_thread.start_new_thread("B_Led", rgbled, (100, bled))
# Function to generate BME280 values string
#---------------
def bmevalues():
t, p, h = bme.read_compensated_data()
p = p // 256
pi = p // 100
pd = p - pi * 100
hi = h // 1024
hd = h * 100 // 1024 - hi * 100
#return "[{}] T={0:1g}C ".format(time.strftime("%H:%M:%S",time.localtime()), round(t / 100,1)) + "P={}.{:02d}hPa ".format(pi, pd) + "H={}.{:01d}%".format(hi, hd)
return "[{}] T={}C ".format(time.strftime("%H:%M:%S",time.localtime()), t / 100) + "P={}.{:02d}hPa ".format(pi, pd) + "H={}.{:02d}%".format(hi, hd)
# Define BME280 thread function
#-----------------------
def bmerun(interval=60):
_thread.allowsuspend(True)
sendmsg = True
send_time = time.time() + interval
while True:
while time.time() < send_time:
notif = _thread.getnotification()
if notif == 10002:
_thread.sendmsg(_thread.getReplID(), bmevalues())
elif notif == 10004:
sendmsg = False
elif notif == 10006:
sendmsg = True
elif (notif <= 3600) and (notif >= 10):
interval = notif
send_time = time.time() + interval
_thread.sendmsg(_thread.getReplID(), "Interval set to {} seconds".format(interval))
time.sleep_ms(100)
send_time = send_time + interval
if sendmsg:
_thread.sendmsg(_thread.getReplID(), bmevalues())
# 3K is enough for BME280 thread
_ = _thread.stack_size(3*1024)
# start the BME280 thread
bmeth=_thread.start_new_thread("BME280", bmerun, (60,))
# === In the 3rd thread we will run Neopixels rainbow demo ===
num_np = 144
np=machine.Neopixel(machine.Pin(21), num_np)
def rainbow(pos=1, bri=0.02):
dHue = 360*3/num_np
for i in range(1, num_np):
hue = (dHue * (pos+i)) % 360;
np.setHSB(i, hue, 1.0, bri, 1, False)
np.show()
# DEfine Neopixels thread function
#-----------------
def thrainbow_py():
pos = 0
bri = 0.02
while True:
for i in range(0, num_np):
dHue = 360.0/num_np * (pos+i);
hue = dHue % 360;
np.setHSB(i, hue, 1.0, bri, 1, False)
np.show()
notif = _thread.getnotification()
if (notif > 0) and (notif <= 100):
bri = notif / 100.0
elif notif == 1000:
_thread.sendmsg(_thread.getReplID(), "[%s] Run counter = %u" % (_thread.getSelfName(), pos))
pos = pos + 1
import math
#---------------
def thrainbow():
pos = 0
np.brightness(25)
while True:
np.rainbow(pos, 3)
notif = _thread.getnotification()
if (notif > 0) and (notif <= 100):
np.brightness(math.trunc(notif * 2.55))
elif notif == 1000:
_thread.sendmsg(_thread.getReplID(), "[%s] Run counter = %u" % (_thread.getSelfName(), pos))
pos = pos + 1
# Start the Neopixels thread
npth=_thread.start_new_thread("Neopixel", thrainbow, ())
utime.sleep(1)
machine.heap_info()
_thread.list()
# Set neopixel brightnes (%)
#_thread.notify(npth, 20)
# Get counter value from Neopixel thread
#_thread.notify(npth, 1000)