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.


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;





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


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


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;

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.