Jonas

Multiple EV3 control in Pybricks

Recommended Posts

Warm greetings to Pybricks experts!

I started to program my EV3 in Pybricks/MicroPython and it is great fun. Good job done by its creators!

Now I have built a 6-motor robot and I want to control it by 2 EV3 bricks. I know how to do it in EV3_G block diagrams but I definitely want to accomplish it in MicroPython.

My first question: What communication channel between the 2 bricks should I use? I would prefer USB cable, but Bluetooth is also OK.

The second: is there any example of a Micropython program that controls motors attached to a master EV3 and other motors attached to the other (slave) EV3 brick?

Thanks for any help.

 

Share this post


Link to post
Share on other sites

I use Bluetooth for my warehouse with 3x EV3 (1master and 1 slave on micropython, 3rd brick also as slave still on EV3-G) so far it works very fast.

As I'm on a business trip I don't have my Lego with me so can't show a picture, but this is what you'll need.

On each program at beginning:

from pybricks.messaging import BluetoothMailboxServer, BluetoothMailboxClient, LogicMailbox, NumericMailbox, TextMailbox

 

On the master brick:

server = BluetoothMailboxServer()

test_variable = NumericMailbox('speed for motor a', server)

test_variable2 = TextMailbox ('motor state', server)

server.wait_for_connection(1)

This will wait for 1(or if more needed) Bluetooth connections made to the master brick. This is why the master programs need to be started first, Always! And reach this wait line before the slave reaches the connecting code.

 

And you can make as many as you need of these variables either text,logic or numeric just as in EV3-G. The name between ' ' is same as the name in EV3-G this is how you can mix bricks with micropython and EV3-G to work together.

 

On the slaves you will need to add this:

client = BluetoothMailboxClient()

test_variable = NumericMailbox('speed for motor a', client)

test_variable2 = TextMailbox ('motor state', client)

client.connect('name_of_brick_you_want_to_connect_to')

 

This is all preparing and connecting. Then you can start sending messages like:

test_variable2.send('Waiting') #in client

test_variable.wait_new() #in client

test_variable.read() #use this to read the 50% value the server will send and put it in you motor line.

test_variable2.send('I started and moved 1000 degrees') #in client

test_variable.send(50) #in server

test_variable2.wait_new() #in server

if test_variable2.read() == 'error':

     Blablabla

elif test_variable2.read() == 'I started and... You get it ?

Share this post


Link to post
Share on other sites

Thank you, Jos and David.

I read that mentioned chapter in the documentation before, but I hoped there is another, more straightforward, way. This also means that a wired (USB) communication between the 2 bricks is not possible, right?

Share this post


Link to post
Share on other sites

I made the first experiments based on the example in the documentation and it works.

Now let me ask a more practical question. How do you do the development works in this case of multiple EV3 bricks? Do you use a separate computer to write/debug programs for each brick? Or is there a trick how to use a single PC (and single/multiple VS Code IDE) together with multiple bricks and easily switch between the bricks and their programs?

Share this post


Link to post
Share on other sites

I used to finish one brick's program and using # infront of stuff that doesn't need to work yet like BT connecting. When near the end debugging I just change quickly programs with the recent tab if needed. I did the master first as it controls it's own motors. Then program the slaves one by one. And if needed you can run the master program by starting it on the brick.

 

And just connect all bricks with USB's to the computer.

Edited by Mr Jos

Share this post


Link to post
Share on other sites

Thanks, Jos.

Is there any special reason to use USB connection? So far, I have been using Wifi connection between my PC and the bricks.

Share this post


Link to post
Share on other sites

I don't have dongles, and no space to put them in my bricks as they are in tight spaces.

IMG_20201116_190119

 

The one on the crane would hit racks with dongle, and other ones don't have access to USB port.

 

 

Share this post


Link to post
Share on other sites

I am going to use this thread to document my progress in building a robot controlled by multiple EV3 units.

I have built a robot based on Akiyuki's famous 6DOF robotic arm. Mine is slightly modified. I added a grip and removed the end rotator.

robot-small.jpg

robot-detail-small.jpg

So I used 6 EV3 motors (3 large and 3 medium). Four of them (responsible for arm and forearm movements) are controlled by Master (Server) EV3, the remaining 2 that control body rotation and grip opening are governed by a Slave (Client) EV3. I have chosen this split because the 4 above mentioned motors need to be mutually coordinated as some of the movements are more or less mechanically interlinked.
My programs are written in Pybricks micropython and for me this is the first micropython project of this size.  

For the Server-Client communication I created 3 mailboxes: Two used by the Master to send the Client messages and another used by the client to provide feedback (or confirmation). The master specifies requested actions as text messages (e.g. 'TurnReset', 'TurnTarget') sent via Textmailbox. Parameters for some of the actions (like speed, angle, stop-commands) are packed into a single int32 number and sent by a NumericTextbox. The Client responses by brief confirmation text messages via a Textmailbox.

The Server controls all the 6 motors by functions which have similar declarations and names. The functions for the remote motors include communication with the other EV3.

A sample code at the server side:

# a locally controled motor
def motorTwistTarget (speed, target, then_opt, wait_opt):
    if (target > TwistMoveLimitPlus):
        target = TwistMoveLimitPlus
    if (target < TwistMoveLimitMinus):
        target = TwistMoveLimitMinus
    motorTwist.run_target(speed, target, then=then_opt, wait=wait_opt)

# a remote controled motor    
def motorGripOpen_Remote ():
    mboxAct.send ('GripOpen')
    mboxCon.wait ()
    encParam = motorParamEncode (GripSpeedLimit, GripOpen, Stop.HOLD, True)
    mboxPar.send (0)
    mboxCon.wait ()

def motorGripTarget_Remote (speed, target, then_opt, wait_opt):
    if (target > TurnMoveLimitPlus):
        target = TurnMoveLimitPlus
    if (target < TurnMoveLimitMinus):
        target = TurnMoveLimitMinus
    mboxAct.send ('GripTarget')
    mboxCon.wait ()
    encParam = motorParamEncode (speed, target, then_opt, wait_opt)
    mboxPar.send (encParam)
    mboxCon.wait ()
    return (mboxCon.read ())

 A sample code at the client side:
 

# MAIN CONTROL LOOP
action = 'Start'
while (action != 'End'):
    mboxAct.wait()
    action = mboxAct.read()
    mboxCon.send('OK')
    print (action)
    
    if (action == 'TurnReset'):
        motorTurnReset ()
        mboxCon.send('Done')

    elif (action == 'GripReset'):
        motorGripReset ()
        mboxCon.send('Done')

    elif (action == 'TurnTarget'):
        mboxPar.wait()
        params = mboxPar.read()
        speed, target, then_opt, wait_opt = motorParamDecode (params)
        motorTurnTarget (speed, target, then_opt, wait_opt)
        mboxCon.send('Done')

    elif (action == 'GripTarget'):
        mboxPar.wait()
        params = mboxPar.read()
        speed, target, then_opt, wait_opt = motorParamDecode (params)
        motorGripTarget (speed, target, then_opt, wait_opt)
        mboxCon.send('Done')

    elif (action == 'GripOpen'):
        mboxPar.wait()
        params = mboxPar.read()
        speed, target, then_opt, wait_opt = motorParamDecode (params)
        motorGripOpen (GripSpeedLimit, 40, Stop.HOLD, True)
        mboxCon.send('Done')
    
    elif (action == 'GripClose'):
        mboxPar.wait()
        params = mboxPar.read()
        motorGripClose (GripSpeedLimit)
        mboxCon.send('Done')      
 
mboxCon.send('All done')

As I said before, this is my first Pybrick project that includes also Bluetooth communication, so an advice from experts is welcome.

At the moment, the robot is able to perform basic movements as shown in the video. 

The next steps and improvements in the software should focus mainly on:

  • enabling parallel movements of Server-controlled and Client-controlled motors (at the moment it does not work as intended)
  • better coordination of the wrist movements and program compensation of the mechanical interlinks
  • some sort of algorithmic position planning based on applied inverse kinematics
Edited by Jonas

Share this post


Link to post
Share on other sites

Nice what you've done so far for a first bigger project!

For the robot I like what you did with the gripper, using tires! Some movements seem very limited, is it not possible to make them bigger by moving some of the stopping points? I'm just in the stage where all 6 axis get calibrated correctly. Will make some programmed movements tomorrow and maybe make a video showing how I made it possible to keep some axis 360°+.

Share this post


Link to post
Share on other sites

The currently set movement limits are as follows:

  • body rotation: -90 to 90 deg  (mainly due to cables)
  • arm down/up rotation: -40 to 90 deg (due to construction limits and for sake of robot stability)
  • forearm down/up rotation: -60 to 60 deg (due to construction limits and robot stability)
  • wrist twist:-175 to 175 deg (almost 360 deg) - here I see the only possibility to have unlimited 360 deg rotation guarded by an optical sensor 
  • wrist up/down rotation: -90 to 90 deg (to avoid conflicts with driving gears)
  • gripper opening: 0 to 50 deg

 

Share this post


Link to post
Share on other sites

Hi, I need a help.

I am stuck in the problem of concurrent control of motors on the local and remote EV3 unit.

As shown above I can start a remote motor by sending a message to its EV3 and wait until that EV3 sends back a message that the motor has done. Something like

mboxControl.send (ParamsForRemoteMotor) # send a control command to the client
mboxConfirm.wait ()  # wait for message from client

This works but the wait statement does not allow for concurrent control. I tried several solutions to replace the wait by a loop where I repeatedly read the mboxConfirm and check if its content has changed, but it does not work as intended. It seems that I miss some important information how the mailboxes operate. Unfortunately, it is not explained in the brief Pybricks documentation.

In other words, my goal is to have something like that:

motorArmTarget (ArmSpeed, ArmTarget, Stop.HOLD, False) # local motor
motorForeTarget (ForeSpeed, ForeTarget, Stop.HOLD, False) # local motor
motorLiftTarget (LiftSpeed, LiftTarget, Stop.HOLD, False) # local motor
motorTwistTarget (TwistSpeed, TwistTarget, Stop.HOLD, False) # local motor
mboxControl.send ('motorTurnTarget, TurnSpeed, TurnTarget, Stop.HOLD, False)) # remote motor
while not (motorArm.control.done() and motorFore.control.done() and motorLift.control.done() and motorTwist.control.done() and mboxConfirm.read()=='TurnDone'):
   wait(10)

Any advice or hint?

Edited by Jonas

Share this post


Link to post
Share on other sites

You need to put a small wait after sending the BT command. By the time the movement command reaches the slave, and slave sends the  message I'm busy, the master already reaches the checkbox and reads an old 'TurnDone' and thinks it's OK. Been there done that :)

def move_all_motors(axis1axis2axis3axis4axis5axis6sp1sp2sp3sp4sp5sp6):
    yaw_base_bt_sp.send(int(sp1))
    yaw_base_bt_num.send(int(axis1 / 12 * 140))
    pitch_base.run_target(sp2, int(axis2 * 10), wait=False)
    pitch_arm.run_target(sp3, int(axis3 * 12.5), wait=False)
    roll_arm.run_target(sp4, int(axis4 * 5), wait=False)
    yaw_arm.run_target(sp5, int((axis5 * 10) + (axis4 / 5 * 10)), wait=False)
    roll_head_bt_sp.send(int(sp6))
    roll_head_bt_num.send(int((axis6 * 7) - (axis4 * 1) + (axis5 * 4 / 7)))
    wait(150)
    while pitch_base.control.done() != True or pitch_arm.control.done() != True or roll_arm.control.done() != True or yaw_arm.control.done() != True or commands_bt_text.read() != "No movement":
        continue

The wait 150ms gives the slave some time to return something else then "No movement".

 

    if yaw_base.control.done() == True and roll_head.control.done() == True:
        commands_bt_text.send("No movement")
        wait(100)
    else:
        commands_bt_text.send("Moving")
        wait(100)

Running on the slave in a While True: Loop that starts once all initiating is finished. The wait 100 was added for not slowing down rest of the program.

Edited by Mr Jos

Share this post


Link to post
Share on other sites

Thank you very much, Jos.

I think I tried something very similar, but probably the waiting was not long enough. Or maybe I had another bug inside. The good thing is that now I can be sure that it must work, eventually.

BTW, I follow and admire your current project. It inspired me to learn more about the robotics. Three days ago, I knew nothing about Denavit and Hartenberg. Now I have watched multiple videos about it and filled several papers by pictures of triangles, equations and matrices. It reminds me my uni years more than 40 years ago, though my brain was much fresher and faster those days.

Share this post


Link to post
Share on other sites

Implemented and works!

Thanks once again and wish you good luck in your project!

Share this post


Link to post
Share on other sites
1 hour ago, Jonas said:

Thank you very much, Jos.

I think I tried something very similar, but probably the waiting was not long enough. Or maybe I had another bug inside. The good thing is that now I can be sure that it must work, eventually.

BTW, I follow and admire your current project. It inspired me to learn more about the robotics. Three days ago, I knew nothing about Denavit and Hartenberg. Now I have watched multiple videos about it and filled several papers by pictures of triangles, equations and matrices. It reminds me my uni years more than 40 years ago, though my brain was much fresher and faster those days.

Haha, same here. Was very fun reading and watching everything about the transformation matrices. Didn't know Denavit either 1 week ago, and calculating manually with matrices was already 10years ago I did it last time at school. I was glad once I had the forward kinematics working. Now just little bit more on the inverse kinematics, hope I have some time this week to read more about the transformation I need to use.

17 minutes ago, Jonas said:

Implemented and works!

Thanks once again and wish you good luck in your project!

I'm glad it works for you! Happy to help others, maybe sometimes it won't be the best solution possible, as I only started using Python like 2months ago for first time ever I think. (I work as mechanical service tech for industrial machines, that's why I'm fascinated with all kinds of robotics/automatisation) but the programming is just for fun and learning for me.

Share this post


Link to post
Share on other sites

Hello, fellow robotmak3rs, I am returning after 1 month spent by occasional programming, experimenting and testing.

The communication between the two EV3s has been managed, hence I focused on the inverse kinematic control task. My robot is a modified (simplified) version of Akiyuki's, to which I added a simple gripper. This also means that I did not have to bother with the most complicated part - wrist posing. Actually, I was dealing with a 4 DoF scheme only. I have made a diagram of the main robot parameters and coordinates:

kinematicscheme2.jpg

After that I spent some time to solve geometric tasks to obtain equations that describe relations between [x,y,z] coordinates and the 4 angles depicted in the picture, both in forward and backward (inverse) direction. Then I wrote simple Python functions for each basic step in FW and INV kinematics. I decided to do it step by step rather than in a single routine as I wanted to keep control (and full understanding) in each step. I am a beginner in Python so my code is as it is. I have made screenshots of the main kinematic functions in my program. 

pgm1s.jpg

pgm2s.jpg

pgm3s.jpg

pgm4s.jpg

Some other screenshots are in my Brickshelf gallery

After writing the code and a lot of debugging, I played with some toy tasks like moving in the 3D space based on [x,y,z] coordinates. Yet, my main interest was to see if the robot can do some more practical tasks, like using its gripper to move (pick and place) objects, e.g. something similar to chess figures. I made a test bench where I could place small tables and objects in positions given by [x,y,z] coordinates measured in studs. And now, I faced real-world problems. I saw that the positioning was not as precise as I hoped. What made my headache was a strange behavior for the negative values of the y coordinates. While for positive y values the positioning was rather good, for the negative ones the deviation from the correct position was unacceptable (1-2 studs). I checked the program by altering between forward and inverse computation and it always gave the same relations between spacial and angular parameters. Also I checked the computation for mirrored positions that differed only in the sign of the y coordinate, and the values were exactly same (except of the sign for 1 angle). Hence, my conclusion is that the issue has a mechanical reason (gear backlash and probably also some sort of tension in the construction), that causes the robot body to drift towards positive y values. You can see the issue in the video:

At the moment, I do not see any idea how to improve the movements of the robot. To be honest, I am also tempting to build a new (smaller and more compact) robot now, after I gained some practical experience. So today was the robot's last show and tomorrow it will donor its key organs to its - hopefully better - successor.

 

Share this post


Link to post
Share on other sites

When you build your new one, try putting at least 2 gears on each joint. The backlash will get smaller by just having 2, and if needed you can put the 2nd gear 1 teeth further, making the joint under tension and having no backlash at all. That is, if you put good bracing that it can not skip gears to release the tension. It is what I do on my theta1, 2gears with small tension.

Have fun rebuilding it! In my opinion it always gets better when I rebuild the same kind of machines, as you know the shortcomings of the previous.

Share this post


Link to post
Share on other sites
On 1/30/2021 at 6:02 PM, Mr Jos said:

When you build your new one, try putting at least 2 gears on each joint. The backlash will get smaller by just having 2, and if needed you can put the 2nd gear 1 teeth further, making the joint under tension and having no backlash at all. That is, if you put good bracing that it can not skip gears to release the tension. It is what I do on my theta1, 2gears with small tension.

Have fun rebuilding it! In my opinion it always gets better when I rebuild the same kind of machines, as you know the shortcomings of the previous.

Thanks for your advice. Actually, all my joints had at least 2 gears - simply because I followed Akiyuki's design where he had applied his long-term experience.

During the robot disassembly process I played a bit with the robot rotation assembly. When I was building it weeks ago, I could just guess the internal solution from Akiyuki's video. And now I found at least 2 major reasons for the large backlash.

First, I had used the large EV3 motor, which is known to have a large intrinsic backlash. Next time I am going to use the medium motor, which is better in this aspect.

Second, the drive train controlling the robot rotation included an 8t gear, which is also known for the largest backlash between all the Lego gears. Next time I want to use only double bevel gears that are larger and have better meshing properties.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.