Injecting commands into while loop from Python

General discussions and questions abound development of code with MicroPython that is not hardware specific.
Target audience: MicroPython Users.
Post Reply
User avatar
roaldarboel
Posts: 2
Joined: Thu Jul 07, 2022 1:43 pm
Contact:

Injecting commands into while loop from Python

Post by roaldarboel » Thu Jul 07, 2022 2:05 pm

Hi there! I'm trying use Python on my laptop to start a while loop on a Pico - and then interrupt it/send it new commands/update parameters. I've been using mpremote/pyboard.py to interact with the device. However, I can't seem to find a way of interacting with the board when I have initiated a loop. Is there any way this can be achieved? I have looked into UART and asyncio, but I haven't been able to do it (quite posssibly I haven't quite understood how to use the modules).

The end goal is to work it into a GUI that can begin a script to collect and occasionally send data, with the ability of changing parameters or interrupting the code. I have considered just having a function with no loop and calling it repeatedly, however, the data I'm getting is a high frequencies (at least 240Hz), so in the end it's not really a feasible solution.

A minimal reproducible example without injecting code:

main.py on the board, Pico:

Code: Select all

import utime
from machine import Pin

led = Pin(25, Pin.OUT)

def toggle_forever():
    t = 0
    while True:
        led.toggle()
        print('changed state', t)
        t += 1
        utime.sleep(1)
    print("break")
In a Jupyter Notebook:

Code: Select all

from mpremote import pyboard
from serial.tools import list_ports

Code: Select all

port = list(list_ports.comports())
for p in port:
    print(p.device)

Code: Select all

pyb = pyboard.Pyboard(port[1].device, 115200)
pyb.enter_raw_repl()

Code: Select all

pyb.exec('import main; main.toggle_forever()')
I'm at a loss about this. Hope someone can help with some ideas! Thanks!

User avatar
dhylands
Posts: 3821
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: Injecting commands into while loop from Python

Post by dhylands » Fri Jul 08, 2022 12:21 am

The problem is that your toggle_forever is running, well, forever.

The only way to tell if data is available on sys.stdin is to use poll or select. It looks like you're running on an ESP. I have a pyboard so I modified your example a bit to run on my pyboard.

I created forever.py:

Code: Select all

import time
import pyb
import sys
import select

led = pyb.LED(4)

poll = select.poll()
poll.register(sys.stdin, select.POLLIN)

def toggle_forever():
    poll_delay = 1
    t = 0
    while True:
        led.toggle()
        print('changed state', t)
        t += 1
        events = poll.poll(poll_delay * 100)
        for file in events:
            if file[0] == sys.stdin:
                ch = sys.stdin.read(1)
                print('Got', ch)
                if ch >= '0' and ch <= '9':
                    poll_delay = ord(ch) - ord('0')
                    print('Changing delay to', poll_delay, 'seconds')
and then I created run_forever.py:

Code: Select all

#!/usr/bin/env python

import pyboard
import time
# Initialize PyBoard and connect in REPL mode
pyb = pyboard.Pyboard('/dev/ttyACM1')
pyb.enter_raw_repl()
# Execute the file that reports the pulse and time ticks
print('execfile poll_stdin.py')
pyb.execfile('forever.py')
print('About to call toggle_forever')
pyb.exec_raw_no_follow("toggle_forever()")

# Wait for 3 seconds and then set the delay to 2
print('Sleeping for 3 seconds')
time.sleep(3)
print('Sending a 2')
pyb.serial.write(b'2')

# Wait for 6 seconds and then set the delay to 1
print('Sleeping for 3 seconds')
time.sleep(3)
print('Sending a 2')
pyb.serial.write(b'1')

# Exit REPL and close the connection
pyb.exit_raw_repl()
pyb.close()
Now if I execute run_forever.py then I see this on my PC:

Code: Select all

About to call toggle_forever
Sleeping for 3 seconds
Sending a 2
Sleeping for 3 seconds
Sending a 1
And when it sends the 2 you should see the blinking slow down, and then when it sends the 1 you should see it speed up again.

So you could do something like this:

Code: Select all

import time
import pyb
import sys
import select

led = pyb.LED(4)

poll = select.poll()
poll.register(sys.stdin, select.POLLIN)

def toggle_forever():
    poll_delay = 1
    t = 0
    while True:
        led.toggle()
        print('changed state', t)
        t += 1
        events = poll.poll(poll_delay * 1000)
        for file in events:
            if file[0] == sys.stdin:
                ch = sys.stdin.read(1)
                print('Got', ch)
                if ch >= '0' and ch <= '9':
                    poll_delay = ord(ch) - ord('0')
                    print('Changing delay to', poll_delay, 'seconds')
and then

User avatar
dhylands
Posts: 3821
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: Injecting commands into while loop from Python

Post by dhylands » Fri Jul 08, 2022 12:41 am

And if you change run_forever.py to look like this instead:

Code: Select all

#!/usr/bin/env python

import select
import time

def log_for(secs):
    global pyb
    end = time.time() + secs
    while time.time() < end:
        events = poll.poll(1000)
        for file in events:
            if file[0] == pyb.serial.fileno():
                ch = pyb.serial.read(1)
                print(ch.decode('utf-8'), end='')



import pyboard
import time
# Initialize PyBoard and connect in REPL mode
pyb = pyboard.Pyboard('/dev/ttyACM1')

poll = select.poll()
poll.register(pyb.serial, select.POLLIN)

pyb.enter_raw_repl()
# Execute the file that reports the pulse and time ticks
pyb.execfile('forever.py')
print('About to call toggle_forever')
pyb.exec_raw_no_follow("toggle_forever()")

# Wait for 3 seconds and then set the delay to 2
print('Sleeping for 3 seconds')
log_for(3)
print('Sending a 2')
pyb.serial.write(b'2')

# Wait for 6 seconds and then set the delay to 1
print('Sleeping for 3 seconds')
log_for(3)
print('Sending a 1')
pyb.serial.write(b'1')

log_for(3)

# Exit REPL and close the connection
pyb.exit_raw_repl()
pyb.close()
then it will print out any bytes that the code running on micropython prints. So I now see:

Code: Select all

About to call toggle_forever
Sleeping for 3 seconds
changed state 0
changed state 1
changed state 2
changed state 3
changed state 4
changed state 5
changed state 6
changed state 7
changed state 8
changed state 9
changed state 10
changed state 11
changed state 12
changed state 13
changed state 14
changed state 15
changed state 16
changed state 17
changed state 18
changed state 19
changed state 20
changed state 21
changed state 22
changed state 23
changed state 24
changed state 25
changed state 26
changed state 27
changed state 28
changed state 29
changed state 30
cSending a 2
Sleeping for 3 seconds
hanged state 31
Got 2
Changing delay to 2 seconds
changed state 32
changed state 33
changed state 34
changed state 35
changed state 36
changed state 37
changed state 38
changed state 39
changed state 40
changed state 41
changed state 42
changed state 43
changed state 44
changed state 45
changed state 46
cSending a 1
hanged state 47
Got 1
Changing delay to 1 seconds
changed state 48
changed state 49
changed state 50
changed state 51
changed state 52
changed state 53
changed state 54
changed state 55
changed state 56
changed state 57
changed state 58
changed state 59
changed state 60
changed state 61
changed state 62
changed state 63
changed state 64
changed state 65
changed state 66
changed state 67
changed state 68
changed state 69
changed state 70
changed state 71
changed state 72
changed state 73
changed state 74
changed state 75
changed state 76
changed state 77

User avatar
roaldarboel
Posts: 2
Joined: Thu Jul 07, 2022 1:43 pm
Contact:

Re: Injecting commands into while loop from Python

Post by roaldarboel » Sat Jul 09, 2022 5:17 pm

Thanks Dave! This does exactly what I was after! I've tested it out and it works like a charm. I'll look a bit more into select and poll to get a better understanding of the possibilities. :D

Post Reply