Biermann Posted December 27, 2020 Posted December 27, 2020 (edited) Hello all, I'm new to mindstorms and I'm trying to do some first programming attemps, but I have a problem. I prefer usage of python over scratch, but I don't know how to handle multi thread (parallel) processes using python. What I'm talking about is following: In scratch I can easily create more independent blocks which are all active and all are performed when event happens (condition is met). I can have e.g. something like this in scratch: 1) Sensor C - if color is green Start motor A 2) Sensor C - if color is red Stop motor A 3) Sensor E - if distance is smaller than 20 cm Stop motor A In above mentioned case both color sensor is able to stop the motor in case of red color, but also distance sensor in case of distance smaller than 20cm. Program is always checking both these sensors and waiting for data from them. But how can I do the same thing in python? If I do something like following, then it won't work for obvious reason because all those "waiting" functions are blocking and therefore if code is waiting for distance sensor, it won't react fo colors and vice versa: pohon = Motor('B') color_sensor = ColorSensor('C') distance_sensor = DistanceSensor('D') while True: color = color_sensor.wait_for_new_color() if color == 'red': pohon.stop() elif color == 'green': pohon.start(50) distance_sensor.wait_for_distance_closer_than(20, 'cm') pohon.stop() I can (for some cases) replace it with loop which checks current status and reacts appropriately without waiting such as while True: color = color_sensor.get_color() if color == 'red': pohon.stop() elif color == 'green': pohon.start(50) distance = distance_sensor.get_distance_cm() if distance < 20: pohon.stop() But I dont like it, it will be not usable in all cases and it doesn't allow to perform parallel activities (e.g. if I will request to move motor A on red and move motor B when distance is < 20 cm, then this will allow to do it only sequentially when one of motors will always move first and another second - even in case that distance will go under 20cm at the same time when color sensor detects red). I expect that I'm looking for something like define new methods via DEF where each method will be similar to one of those scratch blocks (probably having infinite loop inside) and somehow run them as an independent processes (something like fork?) so all methods will be working at the same time and I will be able to handle multiple events at the same time. Is there some possibility to do this? Can you please help me with some example or link me to some resource where is described this micropython in better detail than in Mindstorms knowledge base? Thank you very much. Edited December 27, 2020 by Biermann Quote
Mr Jos Posted December 27, 2020 Posted December 27, 2020 I quickly made one with one of my program variables, you will need to change them to yours, and try to understand how it works. You can "def"ine some routines, and just call them in your program once, or as in the example make a thread that you can start, it then will start it and continue with the rest of the program. #add to beginning of your program from threading import Thread #after your variable defining make a sub program like this def motor_drive_operating(): while True: if color_fork.color() != Color.GREEN: continue else: motor_drive.run(50) while True: if color_fork.color() == Color.RED or ultra_base.distance() < 20: motor_drive.stop() break else: continue #make a variable from the sub program motor_drive_loop = Thread(target=motor_drive_operating) #start the sub program motor_drive_loop.start() #The rest of program can continue here. Quote
Lok24 Posted December 27, 2020 Posted December 27, 2020 (edited) Hi, in Python you use "Threads", thats very simple. They are all running parallel all the time. Did any one suceed in storing them in seperate files? Edited December 27, 2020 by Lok24 Quote
Biermann Posted December 27, 2020 Author Posted December 27, 2020 Thank you guys, I'll check this example code :) Your help is very appreciated Quote
David Lechner Posted December 27, 2020 Posted December 27, 2020 The Python that runs on SPIKE Prime and MINDSTORMS Robot Inventor is actually MicroPython rather than regular Python. And it doesn't have threading enabled, so threads are not an option for the MINDSTORMS Robot Inventor hub. One common way to do this in Python without threading is to use generator functions as coroutines. Here is an example for SPIKE Prime I made a while ago: https://gist.github.com/dlech/fa48f9b2a3a661c79c2c5880684b63ae Quote
JopieK Posted December 27, 2020 Posted December 27, 2020 You might get away with it by using interrupts. They are not threads but help you to make 'almost' sequential code. Quote
Biermann Posted December 28, 2020 Author Posted December 28, 2020 (edited) Thank you for additional info guys. It's pretty bad that those threads are not supported as they are more understandable for me than those yields. Though I think I was able to use them for what I need (or needed when I was asking this question). And maybe also two additional questions. 1) When I'm using this way for creating parallelism, then it probably doesn't make sense to use those "wait_for_...." methods of objects at all right? Because it would block program execution inside generator. If I understand it correctly, then this "workaround" with generators and yields just uses yield to be able to stop one method for a while, run another for a while, then stop it and continue for a while with next one etc. So it makes kind of multitasking for those generators and allows each of them to run periodically and so it's needed to avoid long-running block of codes there as it will break this paralelism (because in fact it's still running sequentially and it only seems to be parallel because each block until yield is performed very quickly 2) Is there some possibility how to use some other IDE for development of Mindstorm programs (such as MS Visual Studio Code or IntelliJ Idea)? I really miss autocomplete functionality (why shoud I look into knowledge base if IDE can offer me all available options) and linting etc. Edited December 28, 2020 by Biermann Quote
David Lechner Posted December 28, 2020 Posted December 28, 2020 Your understanding as described in 1) is correct. You will have to make your own replacements for blocking (long-running) functions that use yield instead. For example, wait_for_... is replaced with while not ...: yield. Writing code this way does take some getting used to, but it does have one nice advantage over threads. Threads can switch at any point, so extra locking mechanisms are often needed synchronize threads to avoid concurrency issues. But with generator functions, we know that a function will only pause at the yield keyword, so many of these concurrency issues are never a problem to begin with. I don't know of anything will code completion for the Robot Inventor yet. (But it is on our roadmap at Pybricks.) Quote
Biermann Posted December 29, 2020 Author Posted December 29, 2020 Thank you David for all your help and tips. Looking forward when Mindstorm's hub will be supported by Pybricks :) And in the meantime, I'll have to invest some time into proper learning of python... Quote
arturomoncadatorres Posted January 7, 2021 Posted January 7, 2021 (edited) On 12/27/2020 at 4:33 PM, David Lechner said: The Python that runs on SPIKE Prime and MINDSTORMS Robot Inventor is actually MicroPython rather than regular Python. And it doesn't have threading enabled, so threads are not an option for the MINDSTORMS Robot Inventor hub. One common way to do this in Python without threading is to use generator functions as coroutines. Here is an example for SPIKE Prime I made a while ago: https://gist.github.com/dlech/fa48f9b2a3a661c79c2c5880684b63ae Expand I had the same issue than Biermann. I was trying to replicate Charlie's "Drum solo" using (the vanilla version of) MicroPython, in which the arm motors move simultaneously, but asynchronously. After breaking my head and quite some Googling, I came across this thread. Maybe there is a better, simple, and/or cleaner solution for my problem. However, I am very happy with how @David Lechner's works. Thank you! If you are curious, you can find my script here. Edited January 29, 2021 by arturomoncadatorres Updated link Quote
David Lechner Posted January 9, 2021 Posted January 9, 2021 Cool. Nice to see you figured out the send() method for generator functions too! Link seems broken. Here is a new one: https://github.com/arturomoncadatorres/lego-mindstorms/blob/main/base/charlie/programs/drum_solo.py Quote
arturomoncadatorres Posted January 29, 2021 Posted January 29, 2021 On 1/9/2021 at 5:01 PM, David Lechner said: Link seems broken Expand Whoops, my bad. Moved some files and forgot to update it. Thanks for letting me know 😉 Quote
Coder Shah Posted January 30, 2021 Posted January 30, 2021 Maybe this will be helpful https://www.antonsmindstorms.com/2021/01/27/python-motor-synchronization-coordinating-multiple-spike-or-mindstorms-motors/ Quote
Munchkin255 Posted July 25, 2021 Posted July 25, 2021 I finally figured out how to use Yield to run parallel threads in Micro Python (Spike Prime). I know this is a quite old thread, I do feel however that the topic is important enough and still relevant to justify its revival. Please see my sample Spike Prime program below which shows a simple way to run parallel activities using Yield. I am not a programmer so there might be a more "correct" way to do this - it does however work for me. Best regards Carsten from spike import PrimeHub, LightMatrix, Button, StatusLight, ForceSensor, MotionSensor, Speaker, ColorSensor, App, DistanceSensor, Motor, MotorPair from spike.control import wait_for_seconds, wait_until, Timer from math import * import sys hub = PrimeHub() hub.light_matrix.show_image('HAPPY') class Yield_func: def __init__ (self): self.result = 0 self.goon = 1 def add(self): """ As long as the go on variable is 1 this method will add 1 to the result. Then the check method determines that the goal has been reached it will change go on to 0 causing this method to stop counting. For a driving robot this method could be a routine to drive the robot until reaching a target. """ while self.goon == 1: self.result = self.result + 1 yield yield def draw(self): """ This method demonstrates a routine that is running together with the other methods. It will turn on LED in the Light Matrix to match the counted value. For a driving robot thus method for example control a grapper arm which moves while the robot is moving """ hub.light_matrix.off() while self.goon == 1 and self.result <= 25: #turns on LED to match self.result count x = int(self.result / 5) y = self.result - (x*5) hub.light_matrix.set_pixel(x, y) yield yield def check(self, target): """ This method checks if the desired target has been reached. When target is reached the go on variable is changed to 0 For a driving robot the method could use input from distance or light sensors to determine if the robot should stop. """ while self.result < abs(target): yield self.goon = 0 yield mat = Yield_func() check = mat.check(25) pluss = mat.add() led = mat.draw() while mat.goon == 1: next(led) next(pluss) next(check) wait_for_seconds(0.1) #print ("mat.result: ", mat.result) print ("done") sys.exit() Quote
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.