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
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

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.