Control a model car track with Azure and Arduino - CodeProject

:

Introduction

In my opinion the IoT is not so much about you controlling your toy helicopter with your smartphone. There’s no fundamental difference with using a wireless controller. In my view it’s more about machines informing and controlling each other without human intervention. Thinking of this I remembered the old model car track lying for decades in the attic at my parents. Apart from its fragility a major drawback was that I could never have more than one car on each track without them bumping in to each other at crossroads and junctions but also just because of speed differences running into each other’s back. Also the little cars when run at a steady speed would either run too slow at the straight sections or too fast in the corners causing them to derail.

Background

Connect an old Faller Ams modelcar track to a Windows Azure service via the internet using an Arduino computer. The Azure service contains an AI speed controller to adjust the running of the cars to prevent them from breaking out of corners and bumping into each other. Also it will adjust the speed for the cars to run faster at the straights and slower in the corners and put a maximum speed on specific cars so trucks will run slower than passenger cars. I’ve split this objective into 3 phases.

Phase I

Run a single car on a simple oval track. Control the speed and gather metric data while the car is running or standing still.

Phase II

Run two cars on the oval track divided into six separate sections. Control the speed of the cars not to bump into each other’s backs based on the gathered metrics.

Phase III

Build a more complicated track with dozens of sections, crossroads and junctions and run half a dozen cars on it. Control the speed of the cars not only not to bump into each other’s backs but also not to bump in to each other at the crossroads and junctions based on the gathered metrics.

Below is some detail on the first phase I completed by now. I'm currently working on the second phase. In this article I'm not going into code details as it's all plain C++ for the Arduino, C# for the Windows Azure service and SQL for the Sql Azure database.

The Modelcar track

For those not acquainted with the Faller Ams model car track visit this website: http://www.everyoneweb.com/falleramsautos

The Arduino Starter kit

I’ve chosen for an Arduino minicomputer as it’s very basic and provides a lot of different connection possibilities and also it’s easily extendable with additional components like the Ethernet shield I’m using to connect it to the internet. As I’m not proficient with electronics I’ve bought a starters kit that ships with a lot of basic components and simple examples.

Windows Azure and DataSpider

For the messaging system scaffolding I use my self-developed DataSpider framework. The service runs in Windows Azure. For logging and collecting the metric data there are a couple of SqlServer Azure databases.     

                              

Progress and issues

December 2014 after collecting the car track from the attic at my parents it needed cleaning and some repairs on the junctions electrics. It took a while to get the cars running again more or less. I had to do cleaning and oiling of the electric motors and some soldering of broken or disconnected parts.

For getting acquainted with the Arduino I build some of the example projects. This is a lot of fun and gives you a quick start on how it works.

Connect the Arduino to the network. For this you need an Ethernet shield that sits on top of the Arduino:

Januari 2015 connect the car track to the Arduino: The Arduino runs at 5 volts and the car track needs around 10 volts to run smoothly. For getting around this is the mosfet that can be regulated with 5 volts and can withstand the 10 volts from the power supply to run through it. To gather current data from the track I use a Hall effect sensor for the same reason as it can withstand the 10 volts/200 mAmp and provides a 2.5 volts signal to the Arduino. As already stated I’m not proficient with this stuff so it took a lot of internet search to piece this together.

The first simple setup:

With the Fleischmann DC power supply hooked up and the digital PWM output attached to the mosfet transistor allows the speed to be controlled from a little command-line program running on the PC. A Hall effect sensor (Pololu ACS715) allows for reading the current, in the chart below you can see the current runs on a baseline until a car is running on the track when it goes up. Issues encountered are mostly cars stalling due to electrics connection problems, uneven track, dirt etc. The Hall effect sensor consequently gives uneven output making calculations unreliable.

(The numbers in the graph don’t represent ‘real’ voltage or current values.)

In the graph three different cars have been measured while running with short idle gaps in between. As you can easily see from the graph, the gathered values differ in a rather broad range and as a consequence the cars cannot reliably be differentiated from each other. The gap with idle condition however is big enough to determine if anything is running or not. The couple of low readings on the ‘green’ car are due to stalling and derailing.

Basic Setup

Faller Ams Track

The Faller Ams track is connected to the Arduino via the electric components. Basic power supply is from an old Fleischmann model train track supplying between 0-17 volts

The Hall effect sensor:

 

Arduino embedded C/C++ code

The Arduino is very limited in resources and is programmed in C/C++. Also my C/C++ is not really that great so I needed to keep processing at the Arduino to an absolute minimum and get the data to the Windows Azure service as simple and fast as possible.

 

#include <SPI.h>

#include <Ethernet.h>


// Enter a MAC address and IP address for your controller below.

// The IP address will be dependent on your local network, and it's optional if DHCP is enabled

byte mac[] = { 0x90, 0xA2, 0xDA, 0x0D, 0xBC, 0xAE };

//IPAddress ip(192,168,15,177); //We're not going to use this, it's just for reference

static const byte deviceId[] = { 0x00, 0x00, 0x00, 0x01 };

EthernetClient client;

// named constants for the switch and motor pins

const int motorPin =  9; // the number of the motor pin

const int sensorPin =  A0; // the number of the motor pin

float val = 0;

float iloop = 0;

byte bytes = 196;

//The union is for converting the float to a byte array

typedef union {

  float floatingPoint;

  byte binary[4];

} binaryFloat;

binaryFloat v;

//Serial.print statements are for debugging on the usb connection with the monitor on the notebook


void setup() {

  // initialize serial communications at 9600 bps:

  Serial.begin(9600);

  Ethernet.begin(mac);

  //Ethernet.begin(mac, ip);

}


void loop(){

    if (! client.connected()){       

        Serial.println("Trying to connect");

        //char* host = "cloudspider.cloudapp.net";

        char* host = "192.168.178.28";

        client.setTimeout(1000);

        client.connect(host, 10100);

        if (client.connected()) {

            Serial.println("Connected to port, writing deviceId and waiting for commands...");

            client.write(deviceId, sizeof(deviceId));

        }

    }

    // write the bytes returned from the remote service on the motorpin

    // this will regulate the speed of the car

    analogWrite(motorPin, bytes);  

    // read the analog in value from the sensor to send to the remote service

    // so it will know if the car is actually running

    if(iloop < 100){

      val = val + analogRead(sensorPin);

      iloop++;

      delay(10);

    }

    else{     

      Serial.print("bytes = " );                      

      Serial.print(bytes);    

      Serial.print("\t amps = ");    

      Serial.print(val/iloop-510);

      Serial.print("\t rest = ");    

      Serial.print((val/iloop)/(bytes/100)); 

      //we have very limited memory so keep track of what we're using

      Serial.print("\t ram = ");    

      Serial.println(freeRam()); 

     

      if(client.connected()){

        // average a number of readings to flatten out the value a bit

        v.floatingPoint = val/iloop-510;

        Serial.print(v.binary[0]);

        Serial.print("\t");

        Serial.print(v.binary[1]);

        Serial.print("\t"); 

        Serial.print(v.binary[2]);

        Serial.print("\t");

        Serial.println(v.binary[3]);

        //send the value to the remote service

        client.write(v.binary, sizeof(v.binary));

       

        byte buff[1];

        //read the reply back in

        if(client.read(buff, sizeof(buff)) > 0){

          bytes = buff[0];

        }

      }

      val = 0;

      iloop = 0;

    }

}


int freeRam () {

  extern int __heap_start, *__brkval;

  int v;

  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);

}

 

Windows Azure service C# code

 

The TCP connection to pick up the Arduino messages:

public override void Listen() {

                        _listener = new TcpListener(new IPEndPoint(IPAddress.Any,_cconfig.Port));

                        _listener.Start();

                        listenForClients(_listener);


                }


                private async void listenForClients(TcpListener tcpServer)

                {

                        while (_running)

                        {

                                TcpClient tcpClient = await tcpServer.AcceptTcpClientAsync();

                                base.On_Signal(new DSSignalEventArgs(this.ToString(),"Message Received",(int)eLogLevel.Informational));

                                processClient(tcpClient);

                        }

                }


                private async void processClient(TcpClient tcpClient)

                {

                        NetworkStream stream = tcpClient.GetStream();

                        byte[] buffer = new byte[_cconfig.BufferSize];

                        int read = await stream.ReadAsync(buffer, 0, _cconfig.BufferSize);


                        _tmpmessage = null;

                        _waiting = true;

                        DSMessage message = DSMessageHooks.InitializeDSMessage();

                        message.DSBody.Message.Body.Data = buffer;                      message.DSBody.Message.Header.ProcessorID = _cconfig.ProcessorId;

                        base.On_Receipt(new ReceiptEventArgs(message));

                               

                        while(_waiting && _running){} //wait for the return value will not take long

                        message = _tmpmessage;

                               

                        await stream.WriteAsync((byte[])message.DSBody.Message.Body.Data,0,((byte[])message.DSBody.Message.Body.Data).Length);

                        base.On_Signal(new DSSignalEventArgs(this.ToString(),"Message Send",(int)eLogLevel.Informational));

                }

 

For further analysis I want the data in a SqlAzure database so I can run queries and reports on it. Of course the Sql Azure code is just plain SqlServer connection code:

public override object SendReceive(object OutgoingMessage) {

                        int reply = 0;

                        DSMessage message = (DSMessage)OutgoingMessage;

                       

                        using(SqlConnection conn = new SqlConnection(_config.ConnectString)){

                                conn.Open();

                                SqlCommand command = conn.CreateCommand();

                                command.CommandType = System.Data.CommandType.Text;

                                command.CommandText = (string)message.DSBody.Message.Body.Data;

                                SqlDataReader reader = command.ExecuteReader();

                                if(reader.Read()){

                                        reply = reader.GetInt32(0);

                                }

                                conn.Close();

                        }

                        message.DSBody.Message.Body.Data = reply;

                        return message;

                }

 

The Windows Azure service currently doesn’t do much besides storing the data in the SqlServer databases. This logic will be expanded in the next project phase.

Download MVI_0422_comp.mp4

 

Now the track is divided into 6 separate sections, two straights and four 90° curves:

 

 

The multiplexer

 

To drive the 6 sections I’m using a multiplexer. This is an IC component designed to split one lead into multiple leads (demultiplexer) or the other way round to converse multiple leads into one (multiplexer).The Arduino has 6 PWM ports so I could wire each section into one of them. Apart from blowing up one PWM port during my experiments, leaving me with one short, this will also become a problem in phase three connecting dozens of sections. The multiplexer 4051 has 8 ports and is very cheap so this I will use. With 5 PWM ports still functioning this will drive 40 sections and that should be enough for now.

The multiplexer sits on the left of the breadboard and on the right are the six mosfets it controls. Below these are the opto-couplers discussed in the next section. When you look closely at the attached video clip you can see the car already slowing down in the curves using this setup.

The Hall-effect sensor (again)

This turned out to be a problem case. In the initial setup I used the sensor from the first phase and read the output of the six ports from the multiplexer into an array for each section.

Unfortunately this didn’t give sensible readings. Currently I assume the sensor can’t keep up with the high-speed switching of the ports. This means I will have to use 6 separate sensors and these are not cheap. For this phase I could order 5 more but for the third phase that would become really costly so I had to find another solution. A possible alternative is to measure the voltage drop over a resistor when the current starts flowing. I have been stuck on this for a while as neither of these solutions worked correctly. I got a lot of interference on the other channels so I put this question on the Arduino forum and got some great help. It came out the ACS715 optocoupler I was using was not fitted for this setup so I ordered half a dozen ACPL-P480 IC’s that produced a reliable signal of either 0 or 50+ that I can easily read into the AI module. Also these are a lot cheaper which is nice when the track is expanded in the future. With a lot of thanks to the Arduino forum poster the schematics now look like this:

The collected metrics in the Sql Azure database:

You can easily see the car running from section 1 to section 2 and section 3 from the corresponding section current readings. With the sensors finally working all that’s left to do is finalize the Azure service logic.

The AI module

The code below for the AI module is still incomplete but it shows how I intend to address it. The TrackSection class serves as the base class for all sections. For now I only have straights and curves but in phase 3 there will be also junctions and crossroads and some special sections for example for waiting at a petrol station or a bus stop (I have a minibus :-)). This will provide the flexibility to expand. The class is serializable (As are the referenced objects, car and metrics). The TrackFactory will deserialize the xml definition file retrieved from an Azure blob.

public override MemoryStream DataStreamGet(object DataLocation) {

                        MemoryStream datastream = new MemoryStream();


                        CloudStorageAccount storageAccount = CloudStorageAccount.Parse(_connstring);

                        CloudBlobClient blobStorage = storageAccount.CreateCloudBlobClient();

                        string blobPath = (string)DataLocation;

                        CloudBlob blob = blobStorage.GetBlobReference(blobPath);

                        blob.DownloadToStream(datastream);

                        datastream.Position = 0;


                        return datastream;

                }

Only the methods are abstract so you have to define the logic in each derived class to update the state and calculate the actual speed on that section (MaxPower).

Each section has a reference to the previous section and the next section. It will pick up the reference to the car object in the previous section to get its maximum speed (MaxPower). There’s no other way of keeping track of which car is driving where since the cars themselves are not connected and can’t be communicated with. The reference to the next section will tell if there’s a car running upfront and at what speed. This will serve in determining the new section state and the calculation of the speed (Not implemented in the code below yet).

using System;

using System.Collections.ObjectModel;

using System.IO;

using System.Xml.Serialization;


namespace ModelCarTrack{


    [SerializableAttribute()]

        public static class TrackFactory{

                private static Collection<TrackSection> _tracksections;


                public static Collection<TrackSection> TrackSections {

                        get { return _tracksections; }

                }


                static TrackFactory(){}


                public static TrackSection UpdateMetrics(TrackMetrics NewMetrics){

                        TrackSection _section = _tracksections[NewMetrics.SectionId];

                        _section.Metrics = NewMetrics;

                        _section.UpdateSectionState();

                        _section.CalculatePower();

                        return _section;

                }


                public static void buildTrack(MemoryStream TrackConfiguration){


                        _tracksections = (Collection<TrackSection>)new XmlSerializer(typeof(Collection<TrackSection>)).Deserialize(TrackConfiguration);


                }

   

        }


        [SerializableAttribute()]

        public abstract class TrackSection{

                private int _id;

                private ModelCar _modelcar;

                private TrackMetrics _metrics;

                private TrackSectionState _state;

                private TrackSectionType _type;

                private int _previoussectionid;

                private int _nextsectionid;

                private byte _maxpower;


                public int TrackSectionId {

                        get { return _id; }

                        set { _id = value; }

                }


                public byte MaxPower {

                        get { return _maxpower; }

                        set { _maxpower = value; }

                }


                public int NextSectionId {

                        get { return _nextsectionid; }

                        set { _nextsectionid = value; }

                }


                public int PreviousSectionId {

                        get { return _previoussectionid; }

                        set { _previoussectionid = value; }

                }


                public TrackSectionState SectionState {

                        get { return _state; }

                        set { _state = value; }

                }


                public TrackSectionType TrackSectionType {

                        get { return _type; }

                        set { _type = value; }

                }


                public TrackMetrics Metrics {

                        get { return _metrics; }

                        set { _metrics = value; }

                }


                public ModelCar Car {

                        get { return _modelcar; }

                        set { _modelcar = value; }

                }


                public abstract void UpdateSectionState();

               

                public abstract void CalculatePower();

               

        }


        [SerializableAttribute()]

        public class StraightSection:TrackSection {


                public override void UpdateSectionState() {

                                               

                        if(base.Metrics.Current < 1.10)

                                base.SectionState = TrackSectionState.Idle;

                        else{  

                                base.SectionState = TrackSectionState.Running;

                                base.Car = TrackFactory.TrackSections[base.PreviousSectionId].Car;

                                TrackFactory.TrackSections[base.PreviousSectionId].Car = null;

                                TrackFactory.TrackSections[base.PreviousSectionId].SectionState = TrackSectionState.Idle;

                        }

                }


                public override void CalculatePower() {


                        switch(base.SectionState){

                                case TrackSectionState.Idle: case TrackSectionState.Waiting:

                                        base.Metrics.Power = 0;

                                        break;

                                case TrackSectionState.WarmingUp: case TrackSectionState.Running:

                                        base.Metrics.Power = base.Car.MaxPower < base.MaxPower ? base.Car.MaxPower : base.MaxPower;

                                        break;

                                default:

                                        break;

                        }

                }


        }


        [SerializableAttribute()]

        public class CurvedSection:TrackSection {


                public override void UpdateSectionState() {

                                               

                        if(base.Metrics.Current < 1.10)

                                base.SectionState = TrackSectionState.Idle;

                        else{  

                                base.SectionState = TrackSectionState.Running;

                                base.Car = TrackFactory.TrackSections[base.PreviousSectionId].Car;

                                TrackFactory.TrackSections[base.PreviousSectionId].Car = null;

                                TrackFactory.TrackSections[base.PreviousSectionId].SectionState = TrackSectionState.Idle;

                        }

                }


                public override void CalculatePower() {


                        switch(base.SectionState){

                                case TrackSectionState.Idle: case TrackSectionState.Waiting:

                                        base.Metrics.Power = 0;

                                        break;

                                case TrackSectionState.WarmingUp: case TrackSectionState.Running:

                                        base.Metrics.Power = base.Car.MaxPower < base.MaxPower ? base.Car.MaxPower : base.MaxPower;

                                        break;

                                default:

                                        break;

                        }

                }


        }



        [SerializableAttribute()]

        public class ModelCar{

                private int _carid;

                private byte _maxpower;


                public byte MaxPower {

                        get { return _maxpower; }

                        set { _maxpower = value; }

                }


                public int ModelCarId {

                        get { return _carid; }

                        set { _carid = value; }

                }

        }


        [SerializableAttribute()]

        public class TrackMetrics{

                private byte _power;

                private float _current;

                private int _sectionid;


                public int SectionId {

                        get { return _sectionid; }

                        set { _sectionid = value; }

                }


                public float Current {

                        get { return _current; }

                        set { _current = value; }

                }


                public byte Power {

                        get { return _power; }

                        set { _power = value; }

                }

        }


        [SerializableAttribute()]

        public enum TrackSectionType {

                Straight,

                RightHandCurve,

                LeftHandCurve,

                Junction,

                Crossroad,

                Crossing

        }


        [SerializableAttribute()]

        public enum TrackSectionState {

                Idle,

                Waiting,

                WarmingUp,

                Running

        }

}

The Xml definition:

<?xml version="1.0" encoding="utf-8"?>

<ArrayOfTrackSection xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">

  <TrackSection>

    <TrackSectionId>0</TrackSectionId>

    <MaxPower>255</MaxPower>

    <NextSectionId>1</NextSectionId>

    <PreviousSectionId>5</PreviousSectionId>

    <TrackSectionType>Straight</TrackSectionType>

    <Car>

      <MaxPower>196</MaxPower>

      <ModelCarId>1</ModelCarId>

    </Car>

  </TrackSection>

  <TrackSection>

    <TrackSectionId>1</TrackSectionId>

    <MaxPower>128</MaxPower>

    <NextSectionId>2</NextSectionId>

    <PreviousSectionId>0</PreviousSectionId>

    <TrackSectionType>RightHandCurve</TrackSectionType>

  </TrackSection>

  <TrackSection>

    <TrackSectionId>2</TrackSectionId>

    <MaxPower>128</MaxPower>

    <NextSectionId>3</NextSectionId>

    <PreviousSectionId>1</PreviousSectionId>

    <TrackSectionType>RightHandCurve</TrackSectionType>

  </TrackSection>

  <TrackSection>

    <TrackSectionId>3</TrackSectionId>

    <MaxPower>255</MaxPower>

    <NextSectionId>4</NextSectionId>

    <PreviousSectionId>2</PreviousSectionId>

    <TrackSectionType>Straight</TrackSectionType>

  </TrackSection>

  <TrackSection>

    <TrackSectionId>4</TrackSectionId>

    <MaxPower>128</MaxPower>

    <NextSectionId>5</NextSectionId>

    <PreviousSectionId>3</PreviousSectionId>

    <TrackSectionType>RightHandCurve</TrackSectionType>

  </TrackSection>

  <TrackSection>

    <TrackSectionId>5</TrackSectionId>

    <MaxPower>128</MaxPower>

    <NextSectionId>0</NextSectionId>

    <PreviousSectionId>4</PreviousSectionId>

    <TrackSectionType>RightHandCurve</TrackSectionType>

  </TrackSection>

</ArrayOfTrackSection>

 

Putting it all together

With the electronics in place and the basic logic set up in the Azure service it’s time to put it all together and have two cars running on the oval shorttrack. In the attached video you can see the white and the blue Ford Taunus running.

Download MVI_0515_comp.mp4

The white car is faster than the blue car with the same voltage on the track so it will gain gradually on the blue car. Eventually it’s so close the logic put’s it on Waiting to keep the distance. When the blue car has advanced to the next section the white car is fired up again as you can see in the debug trace below:

And this is the Wireshark trace while running in my local development environment:

This was the objective of phase 2 and completes it. The code logic for this is:

public override Collection<ReplyMessage> UpdateSectionState(TrackMetrics NewMetrics) {

                        Collection<ReplyMessage> replies = new Collection<ReplyMessage>();

                        ReplyMessage reply;

                       

                        if(!(NewMetrics.Current > 0 && NewMetrics.Current < 10)){ //values between 0 and 10 are artifacts

                                                               

                                if(base.Metrics.Current == 0 && NewMetrics.Current > 0 && this.SectionState != TrackSectionState.Running){ //a car has entered the section

                                        if(((TrackSection)TrackFactory.TrackSections[base.NextSectionId]).Car == null

                                                || (this.Car != null && ((TrackSection)TrackFactory.TrackSections[base.NextSectionId]).Car.ModelCarId == this.Car.ModelCarId)){ //the next section is free                        

                                                //set the section to Running

                                                this.SectionState = TrackSectionState.Running;

                                                this.CalculatePower();


                                                reply = new ReplyMessage();

                                                reply.SectionId = (byte)base.TrackSectionId;

                                                reply.Power = base.Metrics.Power;      

                                                replies.Add(reply);    

                                                //set the previous section to Idle

                                                ((TrackSection)TrackFactory.TrackSections[base.PreviousSectionId]).SectionState = TrackSectionState.Idle;

                                                ((TrackSection)TrackFactory.TrackSections[base.PreviousSectionId]).Car = null;

                                                ((TrackSection)TrackFactory.TrackSections[base.PreviousSectionId]).CalculatePower();


                                                reply = new ReplyMessage();

                                                reply.SectionId = (byte)((TrackSection)TrackFactory.TrackSections[base.PreviousSectionId]).TrackSectionId;

                                                reply.Power = ((TrackSection)TrackFactory.TrackSections[base.PreviousSectionId]).Metrics.Power;      

                                                replies.Add(reply);    

                                                //set the next section to WarmingUp

                                                ((TrackSection)TrackFactory.TrackSections[base.NextSectionId]).SectionState = TrackSectionState.WarmingUp;

                                                ((TrackSection)TrackFactory.TrackSections[base.NextSectionId]).Car = base.Car;

                                                ((TrackSection)TrackFactory.TrackSections[base.NextSectionId]).CalculatePower();


                                                reply = new ReplyMessage();

                                                reply.SectionId = (byte)((TrackSection)TrackFactory.TrackSections[base.NextSectionId]).TrackSectionId;

                                                reply.Power = ((TrackSection)TrackFactory.TrackSections[base.NextSectionId]).Metrics.Power;  

                                                replies.Add(reply);

                                        }

                                        else{ //the next section is occupied

                                                if(this.Car != null){

                                                        //set the section to Waiting

                                                        this.SectionState = TrackSectionState.Waiting;

                                                        this.CalculatePower();


                                                        reply = new ReplyMessage();

                                                        reply.SectionId = (byte)base.TrackSectionId;

                                                        reply.Power = base.Metrics.Power;      

                                                        replies.Add(reply);    

                                                        //set the previous section to Idle

                                                        ((TrackSection)TrackFactory.TrackSections[base.PreviousSectionId]).SectionState = TrackSectionState.Idle;

                                                        ((TrackSection)TrackFactory.TrackSections[base.PreviousSectionId]).Car = null;

                                                        ((TrackSection)TrackFactory.TrackSections[base.PreviousSectionId]).CalculatePower();


                                                        reply = new ReplyMessage();

                                                        reply.SectionId = (byte)((TrackSection)TrackFactory.TrackSections[base.PreviousSectionId]).TrackSectionId;

                                                        reply.Power = ((TrackSection)TrackFactory.TrackSections[base.PreviousSectionId]).Metrics.Power;      

                                                        replies.Add(reply);    

                                                }

                                        }              

                                }

                                this.Metrics.Current = NewMetrics.Current;

                                               

                        }

                        return replies; //return the Collection with commands to send to the Arduino

                }

I'm not particularly pleased with this code as it contains too many if conditions. Like this it's not scalable when the complexity increases. But for now it works and I'll look into refactoring it as preparation for phase 3.

Up until now this has been a very fruitful project for me. I learned a lot of technology previously unknown to me, for instance regarding electronics. What I found a real challenge is wiring up and controlling a device that was never designed for this in the past. And it's a lot of fun too.

I'll have to look into several issues as preparation for phase 3:

  • Design a suitable more interesting track with the track sections I have available
  • Expand the available pins either with multiplexers or a bigger Arduino: The challenge will be to do this on a limited budget and without exploding the electronic complexity.
  • Refactor the current C# cloud logic to cope with increased complexity
  • Create better trace information to monitor the state of the track at any given moment.

These track sections are waiting to be connected:

And here are the cars (at least the ones that are still functioning):

This project will be ongoing for a long time yet. I've already mastered several hurdles and although the outcome is still uncertain I'm confident I'll get it somehow.