Cosmik42

[Automation] Practical Example of Train Automation using only Powered Up devices

Recommended Posts

Posted (edited)

I am working for almost 2 months now on an open automation software called "The Lego Train Project".
I thought I would make a separate and clean post to show a very concrete example with full source code.

First let's see how it looks like in action:

The software used in this demo can be downloaded for free here: https://www.dropbox.com/sh/zl0tqfmthhp48tt/AADeVc1lU5znYSvnlAN8HL-1a?dl=1

Here is how it is setup:

49428971_2439129329647552_86557490129865

I have 4 hubs used in this example:

 - Red Train Hub with a train motor and a color detector.
 - Yellow Train Hub with a train motor and color detector.
 - Switch 1 Hub with a simple motor
 - Switch 2 Hub with a simple motor and a distance detector.

(Note that the dashboard has 2 more hubs but they are not used in this example).

Let's dive now into Sequence #1.
When you click Start there, it activates the following code:

// Desactivate detectors to let the train leave
Hub[0].State[0] = 1;

// "Switch 2" is routing to large route
Hub[2].ActivateSwitchToRight("A");
// Wait for Switch to Activate in Full - Double Cross Switch is pretty slow to switch
Wait(1000); 
// Start "Red Train" 
Hub[4].SetMotorSpeed("A", 60); 
// Let the train go through
Wait(3000);

// "Switch 2" is routing to small route
Hub[2].ActivateSwitchToLeft("A"); 
// Wait for Switch to Activate in Full
Wait(1000); 
// Start "Yellow Train"
Hub[1].SetMotorSpeed("A", 60); 
// Let the train go through
Wait(3000); 

// Prepare "Switch 1" to welcome "Red Train"
Hub[0].ActivateSwitchToRight("A");  
// Slow Down Yellow Train
Hub[4].SetMotorSpeed("A", 50); 

// Re-activate Events for both trains
Hub[0].State[0] = 0;
            

Then we need 2 separate Sensor Events to be able to stop trains.

First Event triggers when Yellow Train drive above 'White'. It then execute the following code:

// We make sure the trains left. We don't want to stop them too early!
if (Hub[0].State[0] == 0)
{
    // Stop Yellow Train
    Hub[1].Stop("A");
    // Switch Prepare Train on the Left
    Hub[0].ActivateSwitchToLeft("A");
}

This codes waits for the initial sequence to be done and if it triggers, stops the Yellow Train and activate the first switch to prepare to welcome the Red Train.

Second Event triggers when the Detector of Switch 2 has a train passing in front of it. It then execute the following code:

if (Hub[0].State[0] == 0)
{
    // Stop Red Train
    Hub[4].Stop("A");
}

I hope you find this useful!

Edited by Cosmik42

Share this post


Link to post
Share on other sites

Damn it this looks great.

 

Some idea for future (maybe less distant as it's breaking change): alter the API, so that port is not an argument to method, but rather another index to use:

    Hub[0].Ports["A"].SetMotorSpeed(...)

 

Or even

    hub[0]["A"].SetMotorSpeed...

What's the advantage? One could specify alias:

 

    var redTrain = Hub[0]["A"]

which then could be used in code. Just think how many comments would not be needed with this change:)

PS. I'm also statically-typed programming freak, so I wonder how the API could be made more type safe :)

Share this post


Link to post
Share on other sites
20 minutes ago, Bartosz said:

Some idea for future (maybe less distant as it's breaking change): alter the API, so that port is not an argument to method, but rather another index to use:

    Hub[0].Ports["A"].SetMotorSpeed(...)

Or even

    hub[0]["A"].SetMotorSpeed...

What's the advantage? One could specify alias:

Mmm ... this should be doable! I'll let you know.

Share this post


Link to post
Share on other sites

Yes it's C#, but you write this directly in my tool. No need for a visual studio.

Share this post


Link to post
Share on other sites

Hi,

here are some questions concerning the project V0.3.1 from 8th of January, hope not to many...:blush:

1.) the value right to Hub name ist the batterylevel?

2.) the "speed" doesn't reflect the changes  (the slider does)

3.) are there any properties that can be used?
like "Hub[0].BatteryLevel" or similar?
Didn't you give an example with "IsActive" (but that might be an earlier version)
Or this replaced by filling some slots in "state"?

4.) I still have some trouble with RampMotorSpeed

--------------
Hub[0].RampMotorSpeed("A", 75,3000);
--------------
Works fine

-------------
Hub[0].RampMotorSpeed("A", 75,3000);
Wait(5000);
Hub[0].Stop("A");
-------------
does not?

5.) here is my little ramp routine, it allows a start value, which is very useful
Works fine, but cannot be stopped during "loop"

int n = 25;
int t = 5;
int v = 70;
String port = "A";
var z = Hub[0];
while (n < v)
{
   z.SetMotorSpeed(port,n*-1);
   z.Wait(t*1000/v);    
   n++;
   string s = Convert.ToString(n);
   MainBoard.WriteLine(s);
}  

MainBoard.WriteLine("End ramp");
          
z.Wait(2000);
int min = 20;
t = 1;
while (n > min)
{
   z.SetMotorSpeed(port,n*-1);
   z.Wait(t*1000/v);    
   n--;
   string s = Convert.ToString(n);
   MainBoard.WriteLine(s);
}  
z.Stop(port);

Thanks a lot for your work and patience!

 

Share this post


Link to post
Share on other sites
21 minutes ago, Lok24 said:

the value right to Hub name ist the batterylevel?

Yep

 

21 minutes ago, Lok24 said:

the "speed" doesn't reflect the changes  (the slider does)

It should. Do you have a case where it does not?

 

21 minutes ago, Lok24 said:

3.) are there any properties that can be used?

It's poorly documented, indeed!
I will make a full list of properties.

21 minutes ago, Lok24 said:

I still have some trouble with RampMotorSpeed

The exemple you give failed because of a poor implementation of the 'Wait()' function.
I actually fixed it this morning!
Could you try again with this version?
https://www.dropbox.com/sh/1tqcy0zuylgdn7p/AAAAwo-mzu5GcqLBRYiDOzNDa?dl=1
If this fixes your issue, I will deploy it in the next version.

21 minutes ago, Lok24 said:

5.) here is my little ramp routine, it allows a start value, which is very useful
Works fine, but cannot be stopped during "loop"

The latest version above actually contains a Ramp with a fromSpeed:

 public void RampMotorSpeed(string port, int fromSpeed, int toSpeed, int timeinms)
 

Edited by Cosmik42

Share this post


Link to post
Share on other sites

Hi, thank you for your quick response.

here is the slider:

untitled2e.png.292f3a73eab655b910a7b948c241bf3e.png

 

And here the "Wait" command", "Compiling failed".
Ramp with start-value works fine.

 

untitled2hjhk.png.48689b55029caa597da595ebc20bce52.png

 

 

Edited by Lok24

Share this post


Link to post
Share on other sites

Can you copy paste the error and put it in Google Translate into English?

Thank you!

Share this post


Link to post
Share on other sites

I can and I will...

... here it is....

Compiling failed.
Error (CS0103): The name 'Wait' does not exist in the current context.
Error (CS1998): This async method lacks the 'await' operators, so it runs synchronously. You should consider using the await operator or await Task.Run (...) to wait for non-blocking API calls or perform CPU-bound tasks on a background thread.

--------------------

Compiling failed.
Error (CS0103): Der Name 'Wait' ist im aktuellen Kontext nicht vorhanden.
Error (CS1998): In dieser Async-Method fehlen die 'await'-Operatoren, weshalb sie synchron ausgeführt wird. Sie sollten die Verwendung des 'await'-Operators oder von 'await Task.Run(...)' in Betracht ziehen, um auf nicht blockierende API-Aufrufe zu warten bzw. CPU-gebundene Aufgaben auf einem Hintergrundthread auszuführen.

 

Edited by Lok24

Share this post


Link to post
Share on other sites

Indeed, much better, THX

Works:

Hub[0].RampMotorSpeed("A",25, 75,3000);
Wait(5000);
Hub[0].Stop("A");

 

Does not what I expect :innocent:

Hub[0].RampMotorSpeed("A",25, 75,3000);
Wait(5000);
Hub[0].RampMotorSpeed("A", 75,25,3000);
Hub[0].Stop("A");

 

 

Edited by Lok24

Share this post


Link to post
Share on other sites

So now the 'Wait(6000)' simply stop the execution for 6 seconds while letting any Ramp or other asynchronous things do what they are supposed to do.

One last thing. I just did one more upload to try to fix your speed label.
From the screenshot you shared I can tell your default windows font size is larger than mine, which is why the speed value doesn't show.
I just increased slighted the width of that label. Let me know if that fixes it!

Share this post


Link to post
Share on other sites

Yes, it accelerares with in three seconds, the wait is six, so it runs fur further three seconds.

 

11 minutes ago, Cosmik42 said:

One last thing. I just did one more upload to try to fix your speed label.
From the screenshot you shared I can tell your default windows font size is larger than mine, which is why the speed value doesn't show.
I just increased slighted the width of that label. Let me know if that fixes it!

Sorry, but no. Moving the slider very carefully only 7 numbers are shown:

0, 2,5,8,-2,-5,8

untitled2gg.png.520e0f97089789ab365ea76e760eaa10.png

But thats not critical in any way.

Is there a method to use "RampMotorSpeed" to decrease the speed?

Edited by Lok24

Share this post


Link to post
Share on other sites
34 minutes ago, Lok24 said:

Sorry, but no. Moving the slider very carefully only 7 numbers are shown:

Again, it's because your default font is much bigger than mine. I will try to find a way to force the size of the font in that label.

35 minutes ago, Lok24 said:

Is there a method to use "RampMotorSpeed" to decrease the speed?

RampMotorSpeed("A", 0, 1000) should decelerate over 1 second.

Share this post


Link to post
Share on other sites

Thanks.

It's not that important with the numbers,

Perhaps I find a way to change de default font size in Winows.

Share this post


Link to post
Share on other sites

This seems like a great idea!  One of these days one of you gurus will have to learn me on some coding lol

Share this post


Link to post
Share on other sites
17 hours ago, Lok24 said:

Does not what I expect :innocent:

Hub[0].RampMotorSpeed("A",25, 75,3000);
Wait(5000);
Hub[0].RampMotorSpeed("A", 75,25,3000);
Hub[0].Stop("A");

 

 

For this to 2nd Ramp to have any effect, you need to wait for the RampSpeed to Finish like this:

Hub[0].RampMotorSpeed("A",25, 75,3000);
Wait(5000);
Hub[0].RampMotorSpeed("A", 75,25,3000);
Wait(3000);
Hub[0].Stop("A");

Share this post


Link to post
Share on other sites

Ooops! USER ERROR! :blush:

I'll test that during weekend, thanks.

Hope I 'll get a complete layout soon.

 

 

Share this post


Link to post
Share on other sites
16 hours ago, Cosmik42 said:

So now the 'Wait(6000)' simply stop the execution for 6 seconds while letting any Ramp or other asynchronous things do what they are supposed to do.

That's getting interesting. How's this handled under the hood?

 

2 minutes ago, Cosmik42 said:

Hub[0].RampMotorSpeed("A", 75,25,3000);
Wait(3000);
Hub[0].Stop("A");

 

Maybe the API should have the non-async and async variants for such methods so that it's clear what's happening?

Share this post


Link to post
Share on other sites
Quote

That's getting interesting. How's this handled under the hood?

I had to create this behavior because a simple Thread.Sleep was killing sensors reading, async behaviors, etc.

Here is what I do:

 e.CodeToRun.Replace("Wait(", "await Task.Delay(")

 

Just now, Bartosz said:

Maybe the API should have the non-async and async variants for such methods so that it's clear what's happening?

Maybe! 

Edited by Cosmik42

Share this post


Link to post
Share on other sites

I'm fully happy as it is now.

Ramp takes a time, normaly I wouldn't drive a train a specific period, but till next sensor event.

But as my boost-Set isn't opened yet there are no sensors...

Share this post


Link to post
Share on other sites

Dear all,

I like to add a screen shot to explain my problem, but how to make a picture with about 0,03MB where you are able to see something?

So will will try with pure text.

I try to understand how the self driving moduls is working. I started with a simple scenario, 2 trains and 3 sections (0,1,2) and without any switch. To detect the end of my sections I am using 3 wedo 1 motion sensors which are linked with several sbricks. The two trains are runnung with 2 powered up hubs.

My sensors are showing always a distance value of 4. If a train is passing the sensors the value goes down to 0 for a short moment. Afterwards they show againthe value 4.

The first train has the start position in sector 0, the second has the start position in sector 1.

I defined the path 0,1,2,0.

I expect that the second train drives to sector 2 because this one is free. When this train leaves sector 1 I expect that the first train drives to sector 1 because this one is free now...

After starting the selfdriving mode and klicking on the two green start buttons of the I get the follwing messages:

All Sensor Events for Self-Driving are active.
Anti-Collision is active over 3 sections!
Event #3 triggered - SBrick V on port C has a distance below 4
Event #2 triggered - SBrick H  on port B has a distance below 4
HUB NO.1 - Event discarded - Train is not running
Event discarded - Unknown train on Section 1, nothing done
Event #3 triggered - SBrick V on port C has a distance below 4
Event #2 triggered - SBrick H  on port B has a distance below 4
HUB NO.1 - Event discarded - Train is not running
Event discarded - Unknown train on Section 1, nothing done
HUB NO.1 self-driving path is set to New Path! Resuming from Section 2
HUB NO.1 - stopped because Section 0 is occupied
Event #3 triggered - SBrick V on port C has a distance below 4
Event #2 triggered - SBrick H  on port B has a distance below 4
HUB NO.1 - Event discarded - Waiting to for next section.
Event discarded - Unknown train on Section 1, nothing done
Event #3 triggered - SBrick V on port C has a distance below 4
Event #2 triggered - SBrick H  on port B has a distance below 4
HUB NO.1 - Event discarded - Waiting to for next section.
Event discarded - Unknown train on Section 1, nothing done
Event #3 triggered - SBrick V on port C has a distance below 4
Event #2 triggered - SBrick H  on port B has a distance below 4
HUB NO.1 - Event discarded - Waiting to for next section.
Event discarded - Unknown train on Section 1, nothing done
HUB NO.4 self-driving path is set to New Path! Resuming from Section 0
HUB NO.4 - Next section is not cleared! We slow down
Could not set Motor Speed to 40 for HUB NO.4 because no default port are setup
HUB NO.4 - Allowed to move to Section 1
HUB NO.1 - stopped because Section 0 is occupied
Event #3 triggered - SBrick V on port C has a distance below 4
Event #2 triggered - SBrick H  on port B has a distance below 4
HUB NO.1 - Event discarded - Waiting to for next section.
HUB NO.4 - stopped because Section 2 is occupied
Event #3 triggered - SBrick V on port C has a distance below 4
Event #2 triggered - SBrick H  on port B has a distance below 4
HUB NO.1 - Event discarded - Waiting to for next section.
HUB NO.4 - Event discarded - Waiting to for next section.
Event #3 triggered - SBrick V on port C has a distance below 4
Event #2 triggered - SBrick H  on port B has a distance below 4
HUB NO.1 - Event discarded - Waiting to for next section.
HUB NO.4 - Event discarded - Waiting to for next section.
Event #3 triggered - SBrick V on port C has a distance below 4
Event #2 triggered - SBrick H  on port B has a distance below 4
HUB NO.1 - Event discarded - Waiting to for next section.
HUB NO.4 - Event discarded - Waiting to for next section.
HUB NO.1 - stopped because Section 0 is occupied
HUB NO.4 - stopped because Section 2 is occupied
Event #3 triggered - SBrick V on port C has a distance below 4
Event #2 triggered - SBrick H  on port B has a distance below 4
HUB NO.1 - Event discarded - Waiting to for next section.
HUB NO.4 - Event discarded - Waiting to for next section.
Event #2 triggered - SBrick H  on port B has a distance below 4
HUB NO.4 - Event discarded - Waiting to for next section.
Event #3 triggered - SBrick V on port C has a distance below 4
HUB NO.1 - Event discarded - Waiting to for next section.
HUB NO.4 has cleared section Section 0
Event #3 triggered - SBrick V on port C has a distance below 4
Event #2 triggered - SBrick H  on port B has a distance below 4
HUB NO.1 - Event discarded - Waiting to for next section.
HUB NO.4 - Event discarded - Waiting to for next section.
HUB NO.4 - stopped because Section 2 is occupied
HUB NO.1 - Next section is not cleared! We slow down
Could not set Motor Speed to 40 for HUB NO.1 because no default port are setup
HUB NO.1 - Allowed to move to Section 0
Sensor Events for Program 'Self-Driving' are stopped.
HUB NO.1 path has been cleared and train is stopped
HUB NO.4 - Attempting to clear path and stop train, but waiting for lock to finish
HUB NO.1 has cleared section Section 2
HUB NO.4 - wait is aborted!
HUB NO.4 path has been cleared and train is stopped

At this point I stopped the self-driving mode.

No train was started. What do I have to do to get the trains running?

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.