Home
-
Articles
-
Apps
-
Blogger
-
Contact Me
Digital Technology Labs - Web Design and Development

Model railway controlled by a Raspberry Pi via the internet

[ 4th October 2013, 18:07:21 ]

In this article I’ll talk you through how to get your Raspberry Pi to control a train set, and then how to get it to go to the next level by running the control off the internet and feeding a webcam, to allow anyone in the world to control the train’s speed, direction and lights from anywhere!

I started with the goal where I wanted to control the train by the Raspberry Pi, I had the Raspberry Pi and an old train set. But it was analog; I needed a digital train set and a train controller. So I did my research and from Model Zone in Lakeside I purchased a Digital controller / DCC ready train/engine, the microprocessor and the oval train set. I also purchased the Hornby Digital Controller / DCC.

Photo of oval with train Image of DCC

Unfortunately adding a microprocessor to train didn’t seem as easy as I’d hoped, so I got in contact with John Dutfield Model Railways and they very kindly fitted the microprocessor for me, and tested it out, for which I was over the moon.

So getting the kit home, I now had a train set I could control. This took a little bit of setup, but I persevered and got the kit working from the DCC by changing the dials to change speed and direction.

What I now wanted to do was to get the Raspberry Pi to communicate with the DCC. Research showed there was a USB interface from the DCC, that I could communicate with called XpressNet - download the api here. I used an old printer cable to connect the Raspberry Pi to the DCC.

I can’t recall the full details of what I did then, I found some example code for communicating with the RPi, but it was in Java, so I tried getting Java running on the RPi, which I succeeded at but it ran quite slowly, and I couldn’t get the code to work. I did further research and found this website http://www.peterwallen.talktalk.net/My_Pi/Projects/Entries/2012/10/24_Model_Railway_Automation.html which had example code for communicate with the DCC in Python. This was FANTASTIC, and I owe much of this project to this website.

Here's some images of the bits of the API that I was trying to use;

Photo of oval with train Photo of oval with train Photo of oval with train Photo of oval with train Photo of oval with train

My initial attempts were to get a command to send to the RPi, when it first started moving from a command from the RPi it was like an “it’s alive!” moment, it was great. I then developed this further into scenarios to do different things with the RPi’s lights and speed, and direction.

test.python

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import serial
import struct
import time

def parity(message):
    edb = 0
    for byte in message:
        edb ^= byte
    message.append(edb)


def send(message):
    ok = False
    trys = 1
    while(not ok and trys < 5) :
        ser.write(message)
        print 'trys = %d send:' % (trys) ,
        or byte in message:print(hex(byte)) , 
        time.sleep(.1)
        print ' receive: ',
        while ser.inWaiting() > 0 :
            enq = ser.read()
            print enq.encode('hex') ,
            if enq == '05'.decode('hex') :
                ok = True
            print
            trys +=1




try:
    ser= serial.Serial('/dev/ttyUSB1',9600)
except EnvironmentError as e:
    print e
    raise RuntimeError('Unable to open connection with Hornby Elite Controller')

#address = 0x20
address = 0003

print "hello world"
speed = 0

message = bytearray('E400000000'.decode('hex'))
message[1] = 0x13
struct.pack_into(">H",message,2,address)
message[4] = speed
message[4] |= int(b'10000000',2)
parity(message)
send(message)


ser.close()

Here is a video of the first bit of control I had over the RPi, it worked by manually altering the speed in the code and then running the python.

I then developed built the code to increase and decrease the speed, and not just to set the speed, and I wrote a programme to change direction, by slowing the train to a stop and then starting it to the original speed in the other direction. Thus I developed the traincontroller.py code, which re-used some of the code from the project above, and some of which I wrote myself;

The train controller code - written in python on the Raspberry Pi

Here is the traincontroller.py

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
import serial
import struct
import time

FORWARD = 0
REVERSE = 1
ser = 0

ON = 1
OFF = 0 

def connection_open(device,baud):
  global ser
  try:
    ser = serial.Serial(device,baud)
  except EnvironmentError as e:
    print e
    raise RuntimeError('Unable to open connection with Hornby Elite Controller')

def parity(message):
  edb = 0
  for byte in message:
    edb ^= byte
  message.append(edb)



def closeConnection():
  print "Closing connection"
  ser.close()

def initialiseTrain(direction):
  #make sure it is stopped
  emergencyStop()
  #start the train moving slowly in forward direction
  speed = 15
  setSpeedAndDirection(speed, direction)
  print "should be moving at 15 at direction: " 
  #TODO: How do you output variables in print statement?

def initialiseTrainStop(direction):
  emergencyStop()

def emergencyStop():
  setSpeedAndDirection(0, FORWARD)
  time.sleep(5)


def setSpeedAndDirection(speed, direction):
  if speed < 0 or speed > 200: 
    raise RuntimeError('Speed invalid')
  #if getCurrentSpeed() != 0 and getCurrentDirection != direction:
  #  raise RuntimeError('Cannot change direction at speed') 
  connection_open('/dev/ttyUSB1',9600)  
  address = 0003
  print "Setting speed and direction"
  message = bytearray('E400000000'.decode('hex'))
  message[1] = 0x13
  struct.pack_into(">H",message,2,address)
  message[4] = speed
  #message[4] |= int(b'10000000',2)
  if direction == FORWARD : message[4] |= 0x80
  elif direction == REVERSE : message[4] &= 0x7F
  parity(message)
  send(message)
  closeConnection()


def send(message):
  global ser 
  ok = False
  trys = 1
  while(not ok and trys < 5) :
    ser.write(message)
    print 'trys = %d send:' % (trys) ,
    for byte in message:print(hex(byte)) ,
    time.sleep(.1)
    print ' receive: ',
    while ser.inWaiting() > 0 :
       enq = ser.read()
       print enq.encode('hex') ,
       if enq == '05'.decode('hex') :
         ok = True
    print
    trys +=1

def increaseSpeedFromToDirection(fromSpeed, toSpeed, direction):
  if fromSpeed >= toSpeed or fromSpeed < 0:
     raise RuntimeError('speeds invalid')
  currentSpeed = fromSpeed
  while(currentSpeed < toSpeed):
     if toSpeed - currentSpeed <= 5:
        currentSpeed = toSpeed 
     else:
        currentSpeed += 5
     print 'Would set speed to: ' + str(currentSpeed)   
     setSpeedAndDirection(currentSpeed, direction)
     time.sleep(1)  

def decreaseSpeedFromToDirection(fromSpeed, toSpeed, direction):
  if toSpeed >= fromSpeed or toSpeed < 0:
     raise RuntimeError('speeds invalid')
  currentSpeed = fromSpeed
  while(currentSpeed > toSpeed):
     if currentSpeed - toSpeed <= 5:
        currentSpeed = toSpeed
     else:
        currentSpeed -= 5
     print 'Would set speed to: ' + str(currentSpeed)
     setSpeedAndDirection(currentSpeed, direction)
     time.sleep(1)

#### Control the other functions of the train


class Train(object):
  '''
   The class describing a train object. 
   A train object is associated with each train to be controlled.
  '''
  def __init__(self,address):
    '''
   The class constructor must be called with one parameter containg the train address.
   Example:
          t1 = Train(3)
    '''
    self.address = address
    self.group = [0,0,0]

  def throttle(self,speed,direction):
    '''
   This method controls the train's throttle.
   The two parameters are:
   speed 0 - 127  where 0 = stop
   direction 0 - FORWARD
             1 - REVERSE
   For imformation about the message sent by this method refer to the XpressNet protocol:
     'Locomotive speed and direction operations'
   example:
   t1.throttle(15,FORWARD)  # move train forward with a speed of 15 steps
   t1.throttle(0,FORWARD)   # stop train      
    '''
    connection_open('/dev/ttyUSB1',9600)
    message = bytearray('E400000000'.decode('hex'))
    message[1] = 0x13   #128 speed steps
    struct.pack_into(">H",message,2,self.address)
    message[4] = speed
    if direction == FORWARD : message[4] |= 0x80
    elif direction == REVERSE : message[4] &= 0x7F
    parity(message)
    send (message)
    closeConnection()

  def function(self,num,switch):
    '''
   This method controls the train's functions e.g. lights and sound.
   The use of functions depends on the decoder fitted to the train.
   For imformation about the message sent by this method refer to the XpressNet protocol:
     'Function operation instructions'
   The two parameters are:
   num - Function number 0 - 12
   switch - 0 - OFF
            1 - ON
   example:
    t1.function(0,ON)  # switch function 0 on
    t1.function(0,OFF) # switch function 0 off      
    '''
    # function table columns
    # 0 = group 0 - 2
    # 1 = group code 0x20, 0x21, 0x22
    # 2 = on mask 
    # 3 = off mask
    function_table = [[0,0x20,0x10,0xEF], \
                      [0,0x20,0x01,0xFE], \
                      [0,0x20,0x02,0xFD], \
                      [0,0x20,0x04,0xFB], \
                      [0,0x20,0x08,0xF7], \
                      [1,0x21,0x01,0xFE], \
                      [1,0x21,0x02,0xFD], \
                      [1,0x21,0x04,0xFB], \
                      [1,0x21,0x08,0xF7], \
                      [2,0x22,0x01,0xFE], \
                      [2,0x22,0x02,0xFD], \
                      [2,0x22,0x04,0xFB], \
                      [2,0x22,0x08,0xF7], \
                     ]
    if num >= len(function_table):
      raise RuntimeError('Invaild function') 
    message = bytearray('E400000000'.decode('hex')) 
    message[1] = function_table[num][1]
    if switch == ON :
      self.group[function_table[num][0]] |= function_table[num][2]
    elif switch == OFF :
      self.group[function_table[num][0]] &= function_table[num][3]
    else :
      raise RuntimeError('Invalid switch on function')  
    message[4] = self.group[function_table[num][0]]
    struct.pack_into(">H",message,2,self.address)

    connection_open('/dev/ttyUSB1',9600) 
    parity(message)
    send(message)
    closeConnection()

Scenario testing example code

Here is some of the scenarios code;

test-lights.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import traincontroller
import time

# F0 Lights on
def lights_on(t):
  t.function(0,traincontroller.ON)
# F0 Lights off
def lights_off(t):
  t.function(0,traincontroller.OFF)



t1 = traincontroller.Train(3)

print "Lights on"
lights_on(t1)
time.sleep(5)
lights_off(t1)
print "Lights off"

scenario-01.py - Scenario1: start to 42, wait 5 sec, slow down to 0 (FORWARD)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import traincontroller
import time

print '#### Scenario1: start to 42, wait 5 sec, slow down to 0 ####'

startDirection = traincontroller.FORWARD

print '#### 1) Initialising train ####'
traincontroller.initialiseTrain(startDirection)

print '#### 2) Speeding up to 42 ####'
traincontroller.increaseSpeedFromToDirection(15, 42, startDirection)

print '#### 3) Waiting 5 seconds ####'
time.sleep(5)

print '#### 4) Slowing down to 0 ####'
traincontroller.decreaseSpeedFromToDirection(42, 0, startDirection)

print '#### Scenario1: END ####'

scenario-01reverse.py - Scenario1: start to 42, wait 5 sec, slow down to 0 (REVERSE)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import traincontroller
import time

print '#### Scenario1: start to 42, wait 5 sec, slow down to 0 ####'

startDirection = traincontroller.REVERSE

print '#### 1) Initialising train ####'
traincontroller.initialiseTrain(startDirection)

print '#### 2) Speeding up to 42 ####'
traincontroller.increaseSpeedFromToDirection(15, 42, startDirection)

print '#### 3) Waiting 5 seconds ####'
time.sleep(5)

print '#### 4) Slowing down to 0 ####'
traincontroller.decreaseSpeedFromToDirection(42, 0, startDirection)

print '#### Scenario1: END ####'

scenario-02.py - Scenario2: start to 84, wait 5 sec, slow down to 0

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import traincontroller
import time

print '#### Scenario2: start to 84, wait 5 sec, slow down to 0 ####'

startDirection = traincontroller.FORWARD

print '#### 1) Initialising train ####'
traincontroller.initialiseTrain(startDirection)

print '#### 2) Speeding up to 84 ####'
traincontroller.increaseSpeedFromToDirection(15, 84, startDirection)

print '#### 3) Waiting 5 seconds ####'
time.sleep(5)

print '#### 4) Slowing down to 0 ####'
traincontroller.decreaseSpeedFromToDirection(84, 0, startDirection)

print '#### Scenario2: END ####'

scenario-03.py - Scenario3: start to 127, wait 5 sec, slow down to 0

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import traincontroller
import time

print '#### Scenario3: start to 127, wait 5 sec, slow down to 0 ####'

startDirection = traincontroller.FORWARD

print '#### 1) Initialising train ####'
traincontroller.initialiseTrain(startDirection)

print '#### 2) Speeding up to 168 ####'
traincontroller.increaseSpeedFromToDirection(15, 127, startDirection)

print '#### 3) Waiting 5 seconds ####'
time.sleep(5)

print '#### 4) Slowing down to 0 ####'
traincontroller.decreaseSpeedFromToDirection(127, 0, startDirection)

print '#### Scenario3: END ####'

scenario-04.py - Scenario4: start to 84, change direction to 84 then stop

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import traincontroller
import time

max_speed = 84

print '#### Scenario4: start to ' + str(max_speed) + ', change direction to 84 then stop ####'

startDirection = traincontroller.FORWARD

print '#### 1) Initialising train ####'
traincontroller.initialiseTrain(startDirection)

print '#### 2) Speeding up to 84 ####'
traincontroller.increaseSpeedFromToDirection(15, max_speed, startDirection)

print '#### 3) Waiting 5 seconds ####'
time.sleep(5)

print '#### 4) Slowing down to 0 ####'
traincontroller.decreaseSpeedFromToDirection(max_speed, 0, startDirection)

newDirection = traincontroller.REVERSE

print '#### 5) Waiting 5 seconds ####'
time.sleep(5)


print '#### 6) Speeding up to 84 ####'
traincontroller.increaseSpeedFromToDirection(0, max_speed, newDirection)

print '#### 7) Waiting 5 seconds ####'
time.sleep(5)

print '#### 8) Slowing down to 0 ####'
traincontroller.decreaseSpeedFromToDirection(max_speed, 0, newDirection)


print '#### Scenario4: END ####'

scenario-05.py - Scenario4: start to 84, change direction to 84 then wait for longer and then stop

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import traincontroller
import time

max_speed = 60

print '#### Scenario5: start to ' + str(max_speed) + ', change direction to 84 then continue for a fewminutes then stop ####'

startDirection = traincontroller.FORWARD

print '#### 1) Initialising train ####'
traincontroller.initialiseTrain(startDirection)

print '#### 2) Speeding up to 84 ####'
traincontroller.increaseSpeedFromToDirection(15, max_speed, startDirection)

print '#### 3) Waiting 5 seconds ####'
time.sleep(5)

print '#### 4) Slowing down to 0 ####'
traincontroller.decreaseSpeedFromToDirection(max_speed, 0, startDirection)

newDirection = traincontroller.REVERSE

print '#### 5) Waiting 5 seconds ####'
time.sleep(5)


print '#### 6) Speeding up to 84 ####'
traincontroller.increaseSpeedFromToDirection(0, max_speed, newDirection)

print '#### 7) Waiting 360 seconds ####'
time.sleep(360)

print '#### 8) Slowing down to 0 ####'
traincontroller.decreaseSpeedFromToDirection(max_speed, 0, newDirection)


print '#### Scenario5: END ####'

You can see that all the commands / actions you need for the scenarios are in the traincontroller.

I then decided I wanted to control this via the web. My first attempt can be described by this diagram.

Architecture diagrams of first attempt

Original design What I was originally trying to do

But this required i) to read the state of the train - something I could not get to work correctly. And ii) to send the command to the RPi via a web server on the RPi. But for this I needed to fix the IP of the RPi, get a web server running (which I succeeded at) and then get the Apache to python bridge / module working - which unfortunately I was unsuccessful at. So I went about re-architecting the solution, and this diagram describes the final attempt/solution that I developed.

Here are some note from my attempt at reading the memory;

readCabMemory16(3, CabMemorySerial.CAB_CURR_SPEED);
readCabMemory16(3, 32);


    private void readCabMemory16(int cabNum, int offset) {
        int nceCabAddr = (cabNum * CabMemorySerial.CAB_SIZE) + CabMemorySerial.CS_CAB_MEM_PRO + offset;
        replyLen = NceMessage.REPLY_16; // Expect 16 byte response
        waiting++;
        byte[] bl = NceBinaryCommand.accMemoryRead(3);
        NceMessage m = NceMessage.createBinaryMessage(tc, bl, NceMessage.REPLY_16);
        tc.sendNceMessage(m, this);
    }

    NceBinaryCommand.accMemoryRead(3)


    int addr_h = address/256;
        int addr_l = address & 0xFF;

        byte []retVal = new byte [3];
        retVal[0] = (byte) (READ16_CMD);//read 16 bytes command
        retVal[1] = (byte) (addr_h); //high address
        retVal[2] = (byte) (addr_l); //low address


0x8F - read16_cmd
003 / 256
003 & 0xFF

My design if I can't get the speed and direction from train The end solution

You can see I significantly reduced the complexity, by instead of reading the state of the train, assuming the train starts from rest, I kept the state of the train in memory of the python code - so the python code always knows the state of the train. I then created a mysql database table on my dataflame LAMP server to store the latest command for the RPi to send to the train. I then wrote a php script to read the current state from the mysql database, and another script to update the state/command in the mysql database. Then in a loop the RPi reads the state of the train from the LAMP server by a HTTP get request, and it does this once every x seconds, and compares it with the current state of the train (that it knows) and then actions the appropriate set of commands to the DCC in order to get to the new state.

For example if it is to change direction, the RPi then orchestrates a series of set speed to a lower and lower value, and then sends the change direction command, and then sends a series of set speed commands to increasing speeds, thus simulating a deceleration, change direction and then acceleration. It does this using old school Prince of Percia type behaviour, reading a command, and once it reads the command it stops reading commands until the action has been performed. At that point it will pick up the latest command again.

I then wrote a web interface, where you could enter the details of the train; speed, direction and lights on/off, and this would run the php update script, thus controlling the train. Here is a picture of the web interface;

The web interface

Here is the php read status script;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php 
header("Content-type: text/xml");
echo '<?xml version="1.0" encoding="utf-8" standalone="yes"?>'; echo "\n";
echo '   <train>'; echo "\n";
echo '      <id>003</id>'; echo "\n";
function getAllTrainAttrInformation() {

		include '../****/openConnection.php';

		$query  = "SELECT max(id), speed, direction, lights FROM train_attribute_data WHERE id = ( SELECT MAX( id ) FROM train_attribute_data )";
		
		$result = mysql_query($query);

		$allTrainAttributeInformation = array();

		while($row = mysql_fetch_array($result, MYSQL_ASSOC))
		{
                        $speed = $row['speed'];
                        if(intval($speed) >= 0 and intval($speed) <= 127) {
                             echo '      <speed>' . $speed . '</speed>'; echo "\n";
                        } else {
			    echo '      <speed>0</speed>'; echo "\n";
			}
			$direction = $row['direction']; 
			if('FORWARD' == $direction or 'REVERSE' == $direction) {
			    echo '      <direction>' . $direction . '</direction>'; echo "\n";
			} else {
			    echo '      <direction>FORWARD</direction>'; echo "\n";
			}
			$lights = $row['lights']; 
			if($lights == 0) $lightsText = "off";
			else $lightsText = "on";
                        echo '      <lights>' . $lightsText . '</lights>'; echo "\n";
		}

                include '../****/closeConnection.php';

}
getAllTrainAttrInformation();
?>
</train>

Here is the example XML output;

1
2
3
4
5
6
<train>
  <id>003</id>
  <speed>0</speed>
  <direction>REVERSE</direction>
  <lights>off</lights>
</train>

Here is the php update status script;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?php 
header("Content-type: text/xml");
echo '<?xml version="1.0" encoding="utf-8" standalone="yes"?>'; echo "\n";
echo '   <train>'; echo "\n";
echo '      <id>003</id>'; echo "\n";

$actionInput = $_GET["action"];
$speedInput = $_GET["speed"];
$directionInput = $_GET["direction"];
$lightsInput = $_GET["lights"];

if('sendCommand' == $actionInput and intval($speedInput) >= 0 and intval($speedInput) <= 127 and ($directionInput == 'FORWARD' or $directionInput == 'REVERSE') and ($lightsInput == 'on' or $lightsInput == 'off')) { 
   $action = 'sendCommand';
   $speed = intval($speedInput);
   $direction = $directionInput;
   if($lightsInput == 'on') $lights = 1;
   else $lights = 0;
   insertAllTrainAttrInformation($speed, $direction, $lights);
} else {
   echo '<error>Invalid command</error>'; echo "\n";
}

function insertAllTrainAttrInformation($newSpeed, $newDirection, $newLights) {

                echo '<command>Update: ' . $newSpeed . ' ' . $newDirection . ' ' . $newLights . '</command>'; echo "\n";

		include '../*****/openConnection.php';;
		
		$query = "INSERT INTO train_attribute_data (id, train_id, speed, direction, lights) VALUES (null, '003', " . $newSpeed . ", '" . $newDirection . "', '" . $newLights . "')";
		
		if(!mysql_query($query)) {
		    die('<error>' . mysql_error() . '</error>');
		}

                echo '      <speed>' . $newSpeed . '</speed>'; echo "\n";
		echo '      <direction>' . $newDirection . '</direction>'; echo "\n";
                echo '      <lights>' . $newLights . '</lights>'; echo "\n";

                include '../****/closeConnection.php';

}
?>
</train>

Here is the final scripts that run to control the train using the above methods remotely via http - this is the pyton code running on the Raspberry Pi;

controlTrainViaWeb.py

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import urllib2  
import sre
import traincontroller
import time

currentSpeed = 0
currentDirection = traincontroller.FORWARD
currentLights = traincontroller.OFF

def parseAddress(input):         
  if input[:7] != "http://":
    if input.find("://") != -1: 
       print "Error: Cannot retrieve URL, protocol must be HTTP"
       sys.exit(1)
    else:                         
       input = "http://" + input         
       return input

def updateAttributes():
  address = "http://www.kevingordon.org.uk/projects/railway-controller/getTrainSpeedAndDirectionAndLights.php"
  try:
    website = urllib2.urlopen(address) 
  except urllib2.HTTPError, e:
    print "Cannot retrieve URL: HTTP Error Code", e.code
    raise RuntimeError("Cannot retrieve URL: HTTP Error Code", e.code)
  except urllib2.URLError, e:
    print "Cannot retrieve URL: " + e.reaseon[1]
    raise RuntimeError("Cannot retrieve URL: " + e.reaseon[1])
  website_html = website.read()
  matches = sre.findall('<direction>(.*?)</direction>', website_html)
  direction = matches[0]
  print "direction: " + direction
  if direction == "FORWARD" and currentDirection == traincontroller.FORWARD:
    print "no change in direction"
  elif direction == "FORWARD" and currentDirection == traincontroller.REVERSE:
    print "CHANGE DIRECTION REVERSE --> FORWARD"
    changeDirection() 
  elif direction == "REVERSE" and currentDirection == traincontroller.REVERSE:
    print "no change in direction"
  elif direction == "REVERSE" and currentDirection == traincontroller.FORWARD:
    print "CHANGE DIRECTION FORWARD --> REVERSE"
    changeDirection()
  matches = sre.findall('<speed>(.*?)</speed>', website_html)
  speed = matches[0]
  print "speed: " + speed
  if int(speed) < 0 or int(speed) > 127:
    raise RuntimeException("Speed is invalid")
  if int(speed) < currentSpeed:
    print("Decrease speed from " + str(currentSpeed) + " to " + speed)
    decreaseSpeed(int(speed))
  elif int(speed) > currentSpeed:
    print("Increase speed from " + str(currentSpeed) + " to " + speed) 
    increaseSpeed(int(speed))
  elif int(speed) == currentSpeed:
    print("No change in speed required")
  matches = sre.findall('<lights>(.*?)</lights>', website_html)
  lightson = matches[0]
  print "lightson: " + lightson 
  if lightson == "on" and currentLights == traincontroller.ON:
    print "No change in lights"
  elif lightson == "on" and currentLights == traincontroller.OFF:
    print "Turn lights on"
    turnLightsOn()
  elif lightson == "off" and currentLights == traincontroller.OFF:
    print "No change in lights"
  elif lightson == "off" and currentLights == traincontroller.ON:
    print "Turn lights off"
    turnLightsOff()
  else:
    print "Unrecognised lights: " + lightson

def changeDirection():
  global currentDirection
  global currentSpeed
  if currentDirection == traincontroller.FORWARD: newDirection = traincontroller.REVERSE
  elif currentDirection == traincontroller.REVERSE: newDirection = traincontroller.FORWARD
  if currentSpeed > 0:
    traincontroller.decreaseSpeedFromToDirection(currentSpeed, 0, currentDirection)     
    time.sleep(1)
    if currentDirection == traincontroller.FORWARD: newDirection = traincontroller.REVERSE
    elif currentDirection == traincontroller.REVERSE: newDirection = traincontroller.FORWARD
    else: raise RuntimeException("Problem setting new direction")
    traincontroller.increaseSpeedFromToDirection(0, currentSpeed, newDirection)
  currentDirection = newDirection

def decreaseSpeed(speed):
  global currentSpeed
  traincontroller.decreaseSpeedFromToDirection(currentSpeed, speed, currentDirection)
  currentSpeed = speed

def increaseSpeed(speed):
  global currentSpeed
  traincontroller.increaseSpeedFromToDirection(currentSpeed, speed, currentDirection)
  currentSpeed = speed

def turnLightsOn():
  global currentLights
  t1 = traincontroller.Train(3)
  print "Lights on"
  lights_on(t1)
  currentLights = traincontroller.ON

def turnLightsOff():
  global currentLights
  t1 = traincontroller.Train(3)
  print "Lights off"
  lights_off(t1)
  currentLights = traincontroller.OFF

def lights_on(t):
  t.function(0,traincontroller.ON)

def lights_off(t):
  t.function(0,traincontroller.OFF)



def loopAndUpdateAttributes():
  while True:
    updateAttributes()
    time.sleep(5)
 
def startUpTrain():
  global currentDirection
  global currentSpeed
  global currentLight
  currentDirection = traincontroller.FORWARD
  traincontroller.initialiseTrainStop(currentDirection)
  currentSpeed = 0
  currentLights = traincontroller.OFF
  loopAndUpdateAttributes()
 
startUpTrain()

getHttpData.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import urllib2  
import sre
import traincontroller
import time

currentSpeed = 0
currentDirection = traincontroller.FORWARD
currentLights = traincontroller.OFF

def parseAddress(input):         
  if input[:7] != "http://":
    if input.find("://") != -1: 
       print "Error: Cannot retrieve URL, protocol must be HTTP"
       sys.exit(1)
    else:                         
       input = "http://" + input         
       return input

def updateAttributes():
  address = "http://www.kevingordon.org.uk/projects/railway-controller/getTrainSpeedAndDirectionAndLights.php"
  try:
    website = urllib2.urlopen(address) 
  except urllib2.HTTPError, e:
    print "Cannot retrieve URL: HTTP Error Code", e.code
    raise RuntimeError("Cannot retrieve URL: HTTP Error Code", e.code)
  except urllib2.URLError, e:
    print "Cannot retrieve URL: " + e.reaseon[1]
    raise RuntimeError("Cannot retrieve URL: " + e.reaseon[1])
  website_html = website.read()
  matches = sre.findall('<direction>(.*?)</direction>', website_html)
  direction = matches[0]
  print "direction: " + direction
  if direction == "FORWARD" and currentDirection == trainController.forward:
    print "no change in direction"
  elif direction == "FORWARD" and currentDirection == trainController.reverse:
    print "CHANGE DIRECTION REVERSE --> FORWARD"
  elif direction == "REVERSE" and currentDirection == trainController.reverse:
    print "no change in direction"
  elif direction == "REVERSE" and currentDirection == trainController.forward:
    print "CHANGE DIRECTION FORWARD --> REVERSE"
  matches = sre.findall('<speed>(.*?)</speed>', website_html)
  speed = matches[0]
  print "speed: " + speed
  if int(speed) < 0 or int(speed) > 127
    raise RuntimeException("Speed is invalid")
  if int(speed) < currentSpeed
    print("Decrease speed from " + currentSpeed + " to " + speed
  elif int(speed) > currentSpeed
    print("Increase speed from " + currentSpeed + " to " + speed 
  matches = sre.findall('<lights>(.*?)</lights>', website_html)
  lightson = matches[0]
  print "lightson: " + lightson 
  if lightson == "ON" and currentLights == trainController.ON
    print "No change in lights"
  elif lightson == "ON" and currentLights == trainController.OFF
    print "Turn lights on"
  elif lightson == "OFF" and currentLights == trainController.OFF
    print "No change in lights"
  elif lightson = "OFF" and currentLights == trainController.ON
    print "Turn lights off"


def loopAndUpdateAttributes():
  while True:
    updateAttributes()
    time.sleep(5)
 
def startUpTrain():
  traincontroller.initialiseTrain()
  currentDirection = traincontroller.FORWARD
  currentSpeed = 15
  currentLights = traincontroller.OFF
  loopAndUpdateAttributes()
 

The final step was to add a webcam - at the moment this is run off a Mac, the next step to the project is to get a RPi camera to stream the video of the train set. I added the webcam to the train set controller website.

Here is a video of the train being controlled over the internet via a web interface;

Here is a video of the train being controlled over the internet via a web interface, with the web cam showing the train;


So the end result is a internet controlled train-set, and I’ve had people from India, Wales and the USA controlling the train-set. Those that have used it in this way have developed a game where by you need to send the stop command at the perfect time to get the train to decelerate and stop at the platform.

Requests to run the train remotely are welcome, and with enough interest I will setup the train-set / model railway and let you have a go at controlling it.

Digital Technology Labs - Web Design and Development




Author: Kevin Gordon
Location: Essex - United Kingdom
Hosted On: DataFlame

[ Articles - Apps - Blogger - Contact Me ]
What it's about; You'll mainly find content from Blogger wrapped up into a larger post, or details of a tech project I have undertaken, or some useful information I have guarnered from the internet. Please feedback you comments either directly to me or by leaving a facebook message. Also please link through to Facebook, Twitter and Linked In, or subscribe to my site's RSS feed. (Please note none of the views or content reflected here is in any way an representation or association with the larger multinational automotive company I currently work for).