Cosmik42

Control all your Powered Up & Power Function (SBrick) devices with a single software

Recommended Posts

My project is to duplicate the bypass utility in the Akiyuki Great Ball Contraption module "Mechanical Train".  

Basically, two trains share a single out-and-back segment of track.  Midway between the endpoints is a section of tracks with two switches creating two bypass segments.  Each train takes the left segment and stops and waits for the other train to occupy the other segment, after which both trains proceed.

I'm using the 88009 Powered Up hubs, 88011 train motors, and 88007 color sensors which see colored tiles in the track to identify where to stop at the endpoints and in each bypass segment. 

I think that each train needs its own program to read the colors and react independently from the other train.  Then when each train stops and waits at a bypass segment, it needs to update a global variable that the other train can read.  When both trains see that the other train has updated its global variable, they proceed and reset their variables until the next cycle.

Experimenting with the BAP Global Functions & Code, it looks like its meant for constants and functions, not variables.  Program code sequences inherit whatever is in the Globals and can modify any variables, but another code sequence cannot see those changes.

I found a solution using Threads.  For demonstration, within the Globals, a Boolean variable is set to false.  Also within Globals, two functions are defined. When launched from a Code Sequence, the Red() function reads the global variable value when launched, waits five seconds and updates the variable to true.  The Blue() function reads that initial value when launched, waits ten seconds and reads the variable and finds that the value has changed.  So two independent threads can share a global variable.  I know to ensure that only one thread updates a variable.  

Here's the demonstration code in Global Functions & Code:

public static bool updatedByRed = false;

public static void Red()
{
    MainBoard.WriteLine("Red thinks updatedByRed at launch = " + updatedByRed.ToString());
    for (int k = 0; k < 5; k++)
    { 
        MainBoard.WriteLine("Red...." + k.ToString());
        System.Threading.Thread.Sleep(1000);
    }
    updatedByRed = true;
    MainBoard.WriteLine("Red thinks updatedByRed after 5 seconds = " + updatedByRed.ToString());
}

public static void Blue()
{   
    MainBoard.WriteLine("Blue thinks updatedByRed at launch = " + updatedByRed.ToString()); 
    for (int j = 0; j < 10; j++)
    {
        MainBoard.WriteLine("Blue..." + j.ToString());
        System.Threading.Thread.Sleep(1000);
    }
    MainBoard.WriteLine("Blue thinks updatedByRed after 10 seconds = " + updatedByRed.ToString());
}

Here's the Sequence Code that does the work:

System.Threading.Thread redThread  = new System.Threading.Thread(Red);
System.Threading.Thread blueThread = new System.Threading.Thread(Blue);

redThread.Start();
blueThread.Start();

The results are encouraging:

Red thinks updatedByRed at launch = False
Blue thinks updatedByRed at launch = False
Red....0
Blue...0
Red....1
Blue...1
Red....2
Blue...2
Red....3
Blue...3
Red....4
Blue...4
Red thinks updatedByRed after 5 seconds = True
Blue...5
Blue...6
Blue...7
Blue...8
Blue...9
Blue thinks updatedByRed after 10 seconds = True

The problem occurs when trying to use a Hub in one of the threads.  With Hub[0] connected and recognized by BAP and shown in the Objects section of the Global code, adding a line to the thread (other code not shown for brevity):

public static void Blue()
{
    Hub[0].SetMotorSpeed("A", 50);
}

generates:
Compilation Error (CS0118): 'LegoTrainProject.Hub' is a 'type' but is used like a 'variable'
Compilation Error (CS1998): This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Rewriting the Global function to accept a Hub parameter:

public static void parameterBlue(Hub train)
{   
    train.SetMotorSpeed("A", 50);
}

compiles OK, but running the modified Sequence code:

System.Threading.Thread redThread  = new System.Threading.Thread(Red);
System.Threading.Thread blueThread = new System.Threading.Thread(parameterBlue);

redThread.Start();
blueThread.Start(Hub[0]);

Run Code results:
Compiling failed.
Error (CS1502): The best overloaded method match for 'System.Threading.Thread.Thread(System.Threading.ThreadStart)' has some invalid arguments
Error (CS1503): Argument 1: cannot convert from 'method group' to 'System.Threading.ThreadStart'

Experimenting with different ways to pass a parameter to a thread:

var blueThread = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(parameterBlue));
blueThread.Start(Hub[0]);

Run Code results:
Compiling failed.
Error (CS0123): No overload for 'parameterBlue' matches delegate 'System.Threading.ParameterizedThreadStart'

So now I'm out of ideas on how to make this work.  Any suggestions will be appreciated.

Share this post


Link to post
Share on other sites

Hi Walt, you are going for an overcomplicated solution here, and unfortunately you cannot use Threads because they cannot access the Hubs which are not a static member.

Watching your video, I think all of this can be accomplished with simple events like If train 1 arrive at sensor 1 => Execute Sequence. (Sequence could like this: "stop for 4 seconds. Then resume at speed s". 

 

Share this post


Link to post
Share on other sites

Hi Cosmik42,

Thanks for the quick reply and clarification about Threads.  My concern about just having each train wait something like 4 seconds is that after ten minutes or so they would inevitably get out of sync due to different battery levels and thus speeds in the two trains.  I hope to have something that can run for many hours at a convention like Bricks By The Bay in Santa Clara, California, where I show my GBC stuff.

I'll keep trying things.   Your BAP is too cool to abandon.  Thanks, again for creating it.  

Walt

Share this post


Link to post
Share on other sites
On 2/6/2020 at 10:46 AM, Cosmik42 said:

I am not 100% sure what you are trying to achieve, but you can check the option to allow getting into a section only if the very next is free to. 

 

I tried to handle a track with a crossing (see page 25). I named whole the section with the crossing 'section 2', to prevent clashes. Therefore  at switch number 2 (HS-2 in the picture), you come from section 2 and going left you proceed on section 2. Creating a path in de self-driving module with 2 after 2, the end of section trigger doesn 't fire. This results in an endless loop.

Share this post


Link to post
Share on other sites
On 2/29/2020 at 7:56 PM, Walt White said:

My concern about just having each train wait something like 4 seconds is that after ten minutes or so they would inevitably get out of sync due to different battery levels and thus speeds in the two trains.

Very good thinking forward!

I would then not make a 'wait 5 second' but wait for the other wagon to reach another trigger.

Share this post


Link to post
Share on other sites

Here's a solution I came up with (and tested, more on that later):

Utilising three colors I use one one per end so the trains know which direction to take (typing this I also came up with a solution using the same color on both ends, though untested), and the third on both bypasses.

The color events on both trains and for both ends stop the train, set a state to the appropiate direction (+/-1), set a state so the train will ignore the endcolor (otherwise it'd stop again after driving back over the plate), clear the state for waiting, wait for an amount of time, and then sets the speed to speedlevel * direction.

When a train sees the color for the bypass it looks if the other train is waiting (wait state mentioned above) and if so just starts the other train. Otherwise it stops and sets its own wait state.

I mentioned testing earlier because, while logically sound my test setup didn't work reliably. Sooner or later (3 reverses at the latest IIRC) one of the trains just doesn't see the bypass color (as in it doesn't even show up in the log); it's always the same train, even when I replaced it with another one. There's also a substantual lag between the train going over the tiles and reacting to it, sometimes up to a full track length. I don't know if this stems from BAP or it using the color/distance combination mode of the sensor since I did tests sending trains at full speed over 2 length color tiles in color mode and my software (nowhere near release ready) picked it up both immediately and reliably.

So, take all that as you will, was nonetheless a fun undertaking in problem solving for me.

Share this post


Link to post
Share on other sites

Hi Cosmic42 and others,

 

I still find it odd that in the self-driving module when you say a train starts in section X it immediately says "going to section Y and leaving section X", although the train doesn't have passed the end section sensor for X. This makes it hard to start with more than 1 train, especially if a train doesn't start at the beginning of a path.

Share this post


Link to post
Share on other sites

@AlecDGeneric -- thanks for the details.  Other builder's experiences is exactly what I need to help me troubleshoot this situation.  It sounds like we are both using the same basic approach, now that I'm trying to use one big program loop for both trains instead of using separate code segments for each train.

Your physical layout sounds very close to mine and we are both using sensors on the trains, not next to segments of the track like many others seem to be using.  Using sensors next to the track would require three hubs (one at each endpoint and one at at the bypass segments) in addition to the hubs on each train.  We are trying to do this all with two hubs - one on each train.

I'm using the same color (red) at each end and at each bypass segment, and keeping track of location in the C# code by cycling through a known pattern of the four locations in a known order.

You described "a substantial lag between the train going over the tiles and reacting to it, sometimes up to a full track length".  For the same reason, I've had to run the train at 35% as the "fast" speed because setting the speed to zero doesn't stop the train.  It coasts for quite some time.  It's not like the EV3 motors that have a "brake" mode to really stop when you tell it to stop.

I've attached a picture of my test track.

Other things I have noted, some of which are specific to my project, some of which might help other builders:

  • BAP version 1.5 lists three recommended colors for the 88007 color sensor:  red, white, and yellow.  No other colors or lack of color can be reliably read at this time.  I haven't tried reading the logs that other builders mention, and my test track is put away for a few days, but I will start looking at the logs when I resume work on this.
  • Another interesting feature of the 88007 color sensor is that once it reads one of the three recommended colors, it will continue to report that color until it sees one of the other two recommended colors.  So I have white tiles at the transition to "open track".
  • Adjacent red and white tiles will trigger a YELLOW reading from the sensor when directly over the transition, especially if the train is moving slow.  I'm now counting consecutive readings and only acting on a color if it is read four or more times in a row.  I have the same condition with my Mindstorms EV3 train and EV3 color sensor.
  • The sensor reads my desktop (imitation wood grain) testing area as YELLOW when it's reading the open track, so I'm using white panels under the track.  At the convention, there will be dark tablecloth under all the track.
  • My Great Ball Contraption (GBC) endpoints require a relatively slow train when loading and unloading at the endpoints, so as the train approaches either endpoint I use yellow to trigger "slow down", then red to trigger stop and reverse in slow speed.  As the train sees the yellow segment while leaving the endpoints, the code tells the train to speed up.  
  • At one of the endpoints the train needs to wait two seconds for the GBC ball loading process.

I'm not giving up yet.  Hopefully by the end of this week I'll get time to try a single large program loop to make this project work.

BypassTrackSmallest.jpg

Share this post


Link to post
Share on other sites
Just now, Walt White said:

 It's not like the EV3 motors that have a "brake" mode to really stop when you tell it to stop.

They have.

Three modes:

-Float
- Stop
- Brake

Share this post


Link to post
Share on other sites
1 minute ago, Lok24 said:

They have.

Three modes:

-Float
- Stop
- Brake

I'm curious how you implement the Stop mode.  I've just been using the two modes from the EV3-G blocks:

Coast.png

Share this post


Link to post
Share on other sites

You use the EV3 blocks for the PU-Motors?

We are talking about PU Motors?

1.) motor runs a while after stopping

2.) Motor stops immediately, but can then be easily turned manually

3.) Motor stops immediately, but cannot be  turned manually

Of cause not the two simple ones, but all six(?) others.

Edited by Lok24

Share this post


Link to post
Share on other sites

@Walt White members of the forum and the trains group on FB have reported better success when mounting the sensor sideways and downwards.

You can also use the different hub.states to store and act upon the different stages of your layout. 

Share this post


Link to post
Share on other sites

@Lok24 -->  Sorry for the confusion.  I was just talking in general about the Mindstorms environment and the PU environment.  I connected an EV3 to BAP but haven't done any testing of that combination yet.

@Nivst --> My Great Ball Contraption loading station has a very tight spacing so the train can only be six studs wide right now.  If needed, I can try to reconfigure the GBC loading and unloading stations to allow mounting the sensor on the side of the train.

Edited by Walt White

Share this post


Link to post
Share on other sites

Does anyone know how to set the PoweredUp color and distance sensor LED to a certain color?

You can do it in the PoweredUp app and  setting it to white seems to improve color detection.

@Cosmik42 have you thought about adding the option to control the color sensing threshold? Color detection works a lot better with the LEGO app.

Share this post


Link to post
Share on other sites
On 3/6/2020 at 3:17 PM, Nivst said:

@Cosmik42 have you thought about adding the option to control the color sensing threshold? Color detection works a lot better with the LEGO app.

Unfortunately what you get is what the API gives me!

On 3/6/2020 at 3:17 PM, Nivst said:

Does anyone know how to set the PoweredUp color and distance sensor LED to a certain color?

You can set it in the option of each hub inside BAP.

Share this post


Link to post
Share on other sites

Hi Cosmik42, will you make the software compatible with 4dbrix switch motors? I have a few ones and i would love to use them with BAP...and do you think you could add arduino compatibility too? The distance sensors are really cheap and arduino offer lots of possibilities...i read that the lego colour sensors don t work very well, arduino colour sensors are less expensive perhaps will they work better...at this time i did not try any solution for automation, i m trying to find the best solution with cheaper accessories. Original lego sensors are too expensive...so i think to use arduino...i hope you can add this to the software...thanks for reply

Share this post


Link to post
Share on other sites
On 3/8/2020 at 4:32 PM, Cosmik42 said:

You can set it in the option of each hub inside BAP.

@Cosmik42That's for the hub LED, the color sensor has its own LED.

Also, I'm trying to randomize my layout a bit - I've built a siding that I want the train to take randomly by stopping and backing into a switch - but I don't want to hit it every time it goes through the main loop.

I added this to one of my triggers, but Hub.State doesn't seem to like being assigned an int through a variable.

Random rnd = new Random();
int rand_num = rnd.Next(1, 10);
Hub[0].State[100] = rand_num;

The result is this error and breaking out of the execution code:

FATAL ERROR In Program: Index was outside the bounds of the array.

Any help would be appreciated!

Share this post


Link to post
Share on other sites
3 hours ago, Nivst said:

@Cosmik42That's for the hub LED, the color sensor has its own LED.

Also, I'm trying to randomize my layout a bit - I've built a siding that I want the train to take randomly by stopping and backing into a switch - but I don't want to hit it every time it goes through the main loop.

I added this to one of my triggers, but Hub.State doesn't seem to like being assigned an int through a variable.


Random rnd = new Random();
int rand_num = rnd.Next(1, 10);
Hub[0].State[100] = rand_num;

The result is this error and breaking out of the execution code:


FATAL ERROR In Program: Index was outside the bounds of the array.

Any help would be appreciated!

Perhaps try Hub[0].State[99] instead of Hub.State[100]?

Although the properties suggest an array indexed from 0 to 100, it also states 100 slots of integers, which could mean the index range is 0 to 99.

David

Share this post


Link to post
Share on other sites
22 hours ago, djm said:

Perhaps try Hub[0].State[99] instead of Hub.State[100]?

Although the properties suggest an array indexed from 0 to 100, it also states 100 slots of integers, which could mean the index range is 0 to 99.

David

What a newbie mistake!

This is why you don't do complicated things at 23:00 :blush:

The thing that threw me off was that the help says:

- State[0-100]	- 100 slots of integers...

Seems like 99 is also not available - same fatal error.

Thanks @djm!

Share this post


Link to post
Share on other sites

Hi Nivst - You have 2 arrays - the Hub[0]. This error could trigger if you did not connect your device.

The second one State[] - I just verified and 99 should work, but to avoid any issue, start from State[0], State[1]. etc.

Share this post


Link to post
Share on other sites

@Cosmik42 thanks for the reply, I should be able to manage my layout without needing state[99] ;)

Any chance you had time to check the sensor LED bit?

Attaching the PoweredUp app control of this setting.

Screenshot_20200314-013414_Powered Up.jpg

Share this post


Link to post
Share on other sites

Dear all,

In the meantime two trains are running in self driving mode on my track with two switches and a track crossing.

It´s working fine as long as I set the speed on a good level. Not to slow, not to fast.

But the real speed of the trains is of course depending on the battery level.

 

1) How can I write a code to bring them (train speed and corresponding battery level) in a good relationship.

I guess this should be possible as I see the propertie "Battery Level" and it´s already shown next to the hubs on the screen. By the way for one train it´s currently 98% and for the second one it`s 0%.

98% seems to be corect, but not 0%. Both batterie blocks are fully loaded and both trains are driving fast. Hopefully It´s just a display problem and internal the propertie "Battery Level" will give the correct result.

My main problem is that until now I have no experiance with C# and I have to learn the syntax first with some examples.

Who can write for me these code lines? I suppose those codelines should be entered in different sections (button "Configure", button "Edit code")?

I have in mind to have codes lines which are similar to:

Battery level X: Speed level Y:

X > 90    =>    Y= 70

80 < X < =90    =>    Y= 75

70 < X < =80    =>    Y= 80

60 < X < =70    =>    Y= 85

50 < X < =60    =>    Y= 90

40 < X < =50    =>    Y= 95

X <= 40    =>    Y= 100

 

2) The next level of code lines is of course to do the similar in addition with the speed "danger is ahead"

 

Thank you for your help to learn my first programming steps with C# :-)

Share this post


Link to post
Share on other sites
On 3/15/2020 at 4:03 PM, Didicas said:

Dear all,

In the meantime two trains are running in self driving mode on my track with two switches and a track crossing.

It´s working fine as long as I set the speed on a good level. Not to slow, not to fast.

But the real speed of the trains is of course depending on the battery level.

 

1) How can I write a code to bring them (train speed and corresponding battery level) in a good relationship.

I guess this should be possible as I see the propertie "Battery Level" and it´s already shown next to the hubs on the screen. By the way for one train it´s currently 98% and for the second one it`s 0%.

98% seems to be corect, but not 0%. Both batterie blocks are fully loaded and both trains are driving fast. Hopefully It´s just a display problem and internal the propertie "Battery Level" will give the correct result.

My main problem is that until now I have no experiance with C# and I have to learn the syntax first with some examples.

Who can write for me these code lines? I suppose those codelines should be entered in different sections (button "Configure", button "Edit code")?

I have in mind to have codes lines which are similar to:

Battery level X: Speed level Y:

X > 90    =>    Y= 70

80 < X < =90    =>    Y= 75

70 < X < =80    =>    Y= 80

60 < X < =70    =>    Y= 85

50 < X < =60    =>    Y= 90

40 < X < =50    =>    Y= 95

X <= 40    =>    Y= 100

 

2) The next level of code lines is of course to do the similar in addition with the speed "danger is ahead"

 

Thank you for your help to learn my first programming steps with C# :-)

Anybody able to help me?

Share this post


Link to post
Share on other sites

I'm not sure about the variables that contain the battery level and the speed level but you could do something like that:

switch (X/10) {

default:
case 10:
case 9: 

    Y = 70;
    break;
case 8:
    Y = 75;
   break;
case 7:
    Y = 80;
    break;

}

Share this post


Link to post
Share on other sites
Just now, Tcm0 said:

I'm not sure about the variables that contain the battery level and the speed level but you could do something like that:

switch (X/10) {

default:
case 10:
case 9: 

    Y = 70;
    break;
case 8:
    Y = 75;
   break;
case 7:
    Y = 80;
    break;

}

Or turn the logic into a formula: Y=aX+b and then just calculate the power level from the battery level in a single variable assignment.

 

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.