{"id":2424,"date":"2020-12-17T20:42:35","date_gmt":"2020-12-17T19:42:35","guid":{"rendered":"http:\/\/lisby.dk\/wordpress\/?page_id=2424"},"modified":"2024-10-01T13:16:42","modified_gmt":"2024-10-01T12:16:42","slug":"automatiseret-togdrift-i-jmri","status":"publish","type":"page","link":"https:\/\/lisby.dk\/wordpress\/?page_id=2424","title":{"rendered":"Automatiseret togdrift med SCWarrants i JMRI"},"content":{"rendered":"\n<p>P\u00e5 min bane kan indtil flere tog k\u00f8re samtidig. Styret af min PC.<\/p>\n\n\n\n<p>For at opn\u00e5 dette anvender jeg JMRI p\u00e5 PC&#8217;en. JMRI er forbundet til min ECOS som s\u00e5 igen styrer selve togene og sporskifterne.<\/p>\n\n\n\n<p>Hele min bane er d\u00e6kket af virtuelle signaler. Dvs. at for enhver blok er der et signal til at dirigere togdriften. Derfor, og fordi jeg bruger signal controlled warrants, sker der ingen sammenst\u00f8d.<\/p>\n\n\n\n<p>Hvert tog har en hjemme-blok &#8211; typisk i en skyggebaneg\u00e5rd &#8211; hvor den holder, n\u00e5r den ikke er p\u00e5 tur. Hjemme-blokken er reserveret til det tog. Dvs. ingen andre tog kommer nogensinde der.<\/p>\n\n\n\n<p>I JMRI har jeg defineret et antal warrants for hvert tog. En warrant tager toget fra et stop (en station) til den n\u00e6ste. Det er vigtigt, at der er defineret tilstr\u00e6kkeligt mange warrants til at toget kan komme videre, ligegyldigt hvor den standser, dvs. at der hvor en warrant ender skal der v\u00e6re defineret en anden warrant, der kan bringe toget videre. Og uanset udgangspunktet skal toget kunne komme tilbage dertil vha. en eller flere sammenh\u00e6ngende warrants.<\/p>\n\n\n\n<p>Desuden har jeg lavet et Python script, der starter disse warrants, s\u00e5 togene bev\u00e6ger sig rundt p\u00e5 banen p\u00e5 en semi-tilf\u00e6ldig m\u00e5de. Jeg har alts\u00e5 ikke faste k\u00f8replaner. Det var tanken i starten, men det gav ikke det samme &#8220;liv&#8221;.<\/p>\n\n\n\n<p>Udover at k\u00f8re togene bidrager scriptet ogs\u00e5 til underholdningen ved at trutte i hornet, styre lyset, afvikle h\u00f8jttalerkald p\u00e5 stationerne m.v.<\/p>\n\n\n\n<p>Scriptet er delt i to: Selve scriptet og et antal tog definitioner &#8211; et pr. tog.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Data definition<\/h2>\n\n\n\n<p>En tog definition ser f.eks. s\u00e5ledes ud (forklaring f\u00f8lger):<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><span style=\"color: #339966; ;font-size: 70%; line-height: 40%;\">IC3_Warrants = []\nIC3_Warrants.append({ 'Warrant': 'IC3_3-1', 'TimeToWait': 10000, 'Direction': 'FWD', 'StartBlock': 'OB3', 'EndBlock': 'OB1', 'NextDirection': 'FWD', 'MayRepeat': 'Yes' })\nIC3_Warrants.append({ 'Warrant': 'IC3_1-3', 'TimeToWait': 10000, 'Direction': 'FWD', 'StartBlock': 'OB1', 'EndBlock': 'OB3', 'NextDirection': 'FWD', 'MayRepeat': 'Yes' })\n\nIC3EPE = \"\"\"\nprint \"IC3 entering platform\"\n\"\"\"\nIC3LPE = \"\"\"\nprint \"IC3 leaving platform\"\n\"\"\"\nIC3SLE = \"\"\"\nprint \"IC3 starting\"\nself.Throttle.setF0(True)\n\"\"\"\nIC3ELE = \"\"\"\nprint \"IC3 ending\"\nself.Throttle.setF0(False)\n\"\"\"\nIC3OB26 = \"\"\"\nprint \"IC3 entering main station\"\nself.Throttle.setF3(True)\nself.waitMsec(1000)\nself.Throttle.setF3(False)\n\"\"\"\nIC3Entertainment = { 'EPE': IC3EPE, 'LPE': IC3LPE, 'SLE': IC3SLE, 'ELE': IC3ELE, 'OB26': IC3OB26 }\nIC3Train = { 'Train': 'IC3', 'DCCAddress': 5003, 'SoundDecoder': 0, 'E': IC3Entertainment }\nIC3Definition = { 'Sensor': 'RunIC3', 'Locomotive': IC3Train, 'Warrants': IC3_Warrants, 'DefaultStartBlock': 'OB3', 'DefaultDirection': 'FWD', 'StoredStatus': 'IC3_StoredStatus', 'DisplayStatus': 'IC3_Status', 'Place': 'IC3_Place' }\n\nTrainsAndTrips['IC3'] = IC3Definition<\/span><\/pre>\n\n\n\n<p>Togdefinitionen best\u00e5r af:<br>\n&#8211; Sensor: JMRI navnet p\u00e5 en sensor, der styrer, om toget er aktivt eller inaktivt. Jeg bruger en virtuel sensor, der bliver til en \u201cknap\u201d p\u00e5 sk\u00e6rmen. Men det kan ogs\u00e5 v\u00e6re en fysisk sensor forbundet til en fysisk kontakt.<br>\n&#8211; Locomotive: Definition af lokomotivet. Se nedenfor.<br>\n&#8211; Warrants: Listen af warrants for toget.<br>\n&#8211; DefaultStartBlock: JMRI navnet p\u00e5 hjemme-blokken.<br>\n&#8211; DefaultDirection: FWD eller REV. Indikerer om toget som default starter med at k\u00f8re frem eller tilbage. &nbsp;Kun relevant, hvis toget har en endestation som hjemmeblok, og hvis toget via en vendesl\u00f8jfe nogen gange kommer \u201chjem\u201d ved at k\u00f8re fremad og andre gange ved at bakke.<br>\n&#8211; StoredStatus: Navnet p\u00e5 en JMRI memory variabel til at gemme status for toget, dvs. hvilken blok toget befinder sig i og om toget k\u00f8rte fremad eller bakkede ind i blokken.<br>\n&#8211; DisplayStatus: Navnet p\u00e5 en JMRI memory variabel, der er beregnet til at vise p\u00e5 sk\u00e6rmen, s\u00e5 man kan se status (f.eks. \u201cWaiting\u201d eller \u201cRunning\u201d) for toget.<br>\n&#8211; Place: Navnet p\u00e5 en JMRI memory variabel, der er beregnet til at vise p\u00e5 sk\u00e6rmen, hvilken blok toget befinder sig i.<\/p>\n\n\n\n<p>Lokomotiv definitionen best\u00e5r af:<br>\n&#8211; Train: JMRI navnet p\u00e5 lokomotivet.<br>\n&#8211; DCCAddress: Lokomotivets DCC adresse. For kombinationen af &nbsp;JMRI, ECOS og &nbsp;M\u00e4rklin MFX \/ M4 dekodere er der den specialitet, at eftersom meget i JMRI afh\u00e6nger af DCC adressen, og eftersom disse dekodere ikke har nogen DCC adresse, s\u00e5 er der i JMRI opfundet en DCC adresse, der er 20.000 + ECOS id\u2019en for p\u00e5g\u00e6ldende lokomotiv. Jeg ved det, for jeg har selv implementeret det i JMRI.<br>\n&#8211; SoundDecoder: 0 eller 1 alt efter om lokomotivet er udstyret med lyddekoder. Informationen bruges p.t. ikke til noget.<br>\n&#8211; E: Definition af \u201cunderholdning\u201d, dvs. nogle f\u00e5 linier Python kode, der udf\u00f8res ved bestemte lejligheder. Det kan v\u00e6re hvad som helst. Men tanken er at aktivere lyde, lys m.v. p\u00e5 toget. I eksemplet ovenfor t\u00e6ndes og slukkes lyset via F0. Det kunne s\u00e5dan set ogs\u00e5 v\u00e6re afspilning af en baneg\u00e5rdsmelding i en h\u00f8jttaler p\u00e5 en station eller d\u00e6mpning af lyset i rummet eller noget helt syvende.<\/p>\n\n\n\n<p>Underholdningen best\u00e5r af:<br>\n&#8211; EPE: Enter Platform Entertainment. Udf\u00f8res hver gang en warrant afsluttes.<br>\n&#8211; LPE: Leave Platform Entertainment. Udf\u00f8res hver gang en warrant startes.<br>\n&#8211; SLE: Start Locomotive Entertainment. Udf\u00f8res n\u00e5r toget aktiveres (vha. Sensor).<br>\n&#8211; ELE: End Locomotive Entertainment. Udf\u00f8res n\u00e5r toget stoppes (vha. Sensor).<br>\n&#8211; 0 eller flere bloknavne. Udf\u00f8res n\u00e5r toget k\u00f8rer ind i p\u00e5g\u00e6ldende blok.<\/p>\n\n\n\n<p>EPE, LPE, SLE og ELE skal v\u00e6re defineret. Bloknavnene er optionelle.<\/p>\n\n\n\n<p>For hver warrant for det p\u00e5g\u00e6ldende tog er der f\u00f8lgende attributter:<br>\n&#8211; Warrant: Navnet p\u00e5 JMRI warranten.<br>\n&#8211; TimeToWait: Det antal millisekunder, som toget skal holde ved perronen, f\u00f8r warranten s\u00e6ttes i gang. I virkeligheden tager scriptet TimeToWait for en tilf\u00e6ldig warrant med startblok hvor toget holder. For den specifikke warrant v\u00e6lges efter ventetiden. Der kan jo ske \u00e6ndringer i, hvilke blokke, der er frie, mens toget venter.<br>\n&#8211; Direction: Enten FWD eller REV. Indikerer om toget k\u00f8rer fremad eller bakker. Skal matche samme attribut i selve warranten.<br>\n&#8211; StartBlock: Navnet p\u00e5 f\u00f8rste blok i warranten.<br>\n&#8211; EndBlock: Navnet p\u00e5 sidste blok i warranten.<br>\n&#8211; NextDirection: Enten FWD, REV eller DontCare. Den warrant, der v\u00e6lges n\u00e6ste gang, skal have denne Direction v\u00e6rdi. DontCare betyder at der ikke er krav til Direction af n\u00e6ste warrant. Denne mekanisme er relevant for tog, der nogen gange vender den ene vej (k\u00f8rer fremad) og andre gange den anden (bakker), n\u00e5r den k\u00f8rer samme vej (f.eks. mod nord) gennem samme blok. Bem\u00e6rk, at det kr\u00e6ver et dobbelt s\u00e6t af warrants med hver direction repr\u00e6senteret. Der skal endvidere v\u00e6re en vendesl\u00f8jfe involveret et eller andet sted p\u00e5 banen, f\u00f8r mekanismen finder anvendelse.<br>\n&#8211; MayRepeat: Yes eller No. Indikerer hvorvidt samme warrant m\u00e5 bruges igen n\u00e6ste gang toget skal bringes ud af samme blok, ogs\u00e5 selvom der findes en alternativ warrant.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Scriptet<\/h2>\n\n\n\n<p>Scriptet best\u00e5r af forskellige elementer:<br>\n&#8211; Kode, der indl\u00e6ser alle tilg\u00e6ngelige togdefinitioner.<br>\n&#8211; En klasse (RunTrain), der arver fra AbstractAutomation klassen i JMRI. Denne klassedefinition udg\u00f8r hovedparten af koden.<br>\n&#8211; Initialiseringskode, der skaber et specifikt object af f\u00f8rn\u00e6vnte klasse for hvert tog. Herefter lever disse objekter hver sit liv i separate tr\u00e5de, der intet ved om hinanden.<\/p>\n\n\n\n<p>RunTrain klassen best\u00e5r af:<br>\n&#8211; Noget initialiseringskode.<br>\n&#8211; Hj\u00e6lpefunktion til at t\u00e6nde og slukke str\u00f8mmen p\u00e5 banen (for at v\u00e6kke et af mine tog, som kan finde p\u00e5 at g\u00e5 i bagl\u00e5s).<br>\n&#8211; Hj\u00e6lpefunktioner til at persistere status for toget p\u00e5 harddisken og til at genindl\u00e6se status som en del af initialiseringen. F\u00f8r jeg lavede denne funktionalitet skulle alle tog v\u00e6re i deres hjemme-blok og vende rigtigt, f\u00f8r den automatiske togdrift kunne startes. Nu skal de blot deaktiveres og de igangv\u00e6rende warrants afsluttes, f\u00f8r jeg lukker ned. S\u00e5 kan togdriften genoptages, n\u00e6ste gang jeg starter JMRI og banen op.<br>\n&#8211; runTrainOnce afvikler en given warrant og udf\u00f8rer underholdningskoden, opdaterer status p\u00e5 sk\u00e6rmen osv.<br>\n&#8211; handle er hoved-proceduren i alle klasser, der arver fra AbstractAutomation. Den bliver kaldt igen og igen. I alt v\u00e6sentligt venter den til Sensor bliver aktiv og udv\u00e6lger derefter den n\u00e6ste warrant og kalder runTrainOnce med den valgte warrant. Warranten bliver valgt ved en kombination af at startblokken er den blok, som toget befinder sig i, at reglerne omkring togretning beskrevet ovenfor er overholdt, at reglen om gentagelse s\u00e5 vidt muligt er overholdt samt endelig med et element af tilf\u00e6ldighed.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><span style=\"color: #339966; ;font-size: 70%; line-height: 40%;\">import jarray\nimport jmri\nimport pickle\nimport java.util\nimport threading\noblocks = jmri.InstanceManager.getDefault(jmri.jmrit.logix.OBlockManager)\n\n\n###################################################################################################\n###################################################################################################\n#\n# Each locomotive is defined by:\n#&nbsp;&nbsp;&nbsp; - the parameters that are needed to start an SCWarrant\n#&nbsp;&nbsp;&nbsp; - Entertainment in terms of a few lines of code to execute in these cases:\n#&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - EPE = Enter Platform Entertainment\n#&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - LPE = Leave Platform Entertainment\n#&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - SLE = Start Locomotive Entertainment\n#&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - ELE = End Locomotive Entertainment\n#&nbsp;&nbsp;&nbsp; - A set of alternative warrants for the locomotive out of which one is chosen at random. \n#&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; However, only if the destination block of the warrant is free.\n#\n# The following code imports all available locomotive definitions by executing any file matching\n# *_lokdef.py in any subdirectory. Each of those file should define a locomotive and add that\n# definition to the global variable (Dictionary) TrainsAndTrips.\n#\n###################################################################################################\n###################################################################################################\n\n#First a few methods to traverse the file system\nimport os\nimport fnmatch\ndef yield_files_with_extensions(folder_path, file_match):\n&nbsp;&nbsp;&nbsp; for root, dirs, files in os.walk(folder_path):\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for file in files:\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if fnmatch.fnmatch(file.lower(),file_match.lower()):\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; yield file\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break&nbsp; # without this line it traverses the subfolders too\ndef yield_files_in_subfolders(folder_path, file_match):\n&nbsp;&nbsp;&nbsp; yield [folder_path, [f for f in yield_files_with_extensions(folder_path, file_match)]]\n&nbsp;&nbsp;&nbsp; for root, dirs, files in os.walk(folder_path):\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for d in dirs:\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; subfolder = os.path.join(root, d)\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; my_files = [subfolder, [f for f in yield_files_with_extensions(subfolder, file_match)]]\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; yield my_files\n#Start in current working directory and look through all subdirectories\nthe_path = r'.'\ndef_files = [f for f in yield_files_in_subfolders(the_path, \"*_lokdef.py\")]\ndef_files1 = []\n#For each subdirectory\nfor d in def_files:\n&nbsp;&nbsp;&nbsp; dir = str(d[0])\n&nbsp;&nbsp;&nbsp; for f in d[1]:\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #Add each filename matching the pattern including its relative directory to a list\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; def_files1.append(dir+'\/'+str(f))\n#Traverse the list of files and execute each of them to define all trains \/ locomotives\nTrainsAndTrips = {}\nfor t in def_files1:\n&nbsp; with open(t) as f: exec(f.read())\n\n\n# Initialize the random number generator. We will use it to find random warrants for trains\nRandomizer = java.util.Random()\n\n\n###################################################################################################\n###################################################################################################\n#\n# CLASS IMPLEMENTING A THREAD PER LOCOMOTIVE TO RUN THAT LOCOMOTIVE\n#\n###################################################################################################\n###################################################################################################\nclass RunTrain(jmri.jmrit.automat.AbstractAutomaton) :\n\n&nbsp;&nbsp; &nbsp;global TrainsAndTrips\n&nbsp;&nbsp; &nbsp;global Randomizer\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; def powerOffOn(self):\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; print \"powerOffOn \", self.name\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # Flip the power off and on again - but not if it is already off by purpose\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (powermanager.getPower() == jmri.PowerManager.ON):\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; powermanager.setPower(jmri.PowerManager.OFF)\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.waitMsec(3000)\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; powermanager.setPower(jmri.PowerManager.ON)\n&nbsp;&nbsp; &nbsp;\n&nbsp;&nbsp; &nbsp;def init(self):\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;# init() is called exactly once at the beginning to do\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;# any necessary configuration.\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;print \"Inside init()\", self.name\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.setLocomotive(TrainsAndTrips[self.name]['Locomotive'])\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.runSensor = sensors.provideSensor(TrainsAndTrips[self.name]['Sensor'])\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;print 'sensor', TrainsAndTrips[self.name]['Sensor']\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.runSensor.setKnownState(INACTIVE)\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.StoredStatusVariable = memories.provideMemory(TrainsAndTrips[self.name]['StoredStatus'])\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.DisplayStatusVariable = memories.provideMemory(TrainsAndTrips[self.name]['DisplayStatus'])\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.PlaceVariable = memories.provideMemory(TrainsAndTrips[self.name]['Place'])\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.restoreStatus()\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;print \"Init \",self.name\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.MODE_RUN = 2\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.oldRunSensorState = False\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.setPlace(self.status['Block'])\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.setDisplayStatus(self.status['Direction'])\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;\n&nbsp;&nbsp; &nbsp;def statusFileName(self):\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return self.name+\".status\"\n\n&nbsp;&nbsp; &nbsp;def chosenWarrantFileName(self):\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return self.name+\".usedwarrants\"\n\n&nbsp;&nbsp; &nbsp;def restoreStatus(self):\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;try:\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;f = open(self.statusFileName(),\"r\")\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.status = pickle.load(f)\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;f.close()\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;except:\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.status = { 'Block': TrainsAndTrips[self.name]['DefaultStartBlock'], 'Direction': TrainsAndTrips[self.name]['DefaultDirection'] }\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;try:\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;f = open(self.chosenWarrantFileName(),\"r\")\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.previousWarrantFromBlock = pickle.load(f)\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;f.close()\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;except:\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.previousWarrantFromBlock = { 'noBlock': 'NoWarrant' }\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;\n&nbsp;&nbsp; &nbsp;def storeStatus(self):\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;print \"storeStatus 1 \",self.status\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;f = open(self.statusFileName(),\"w\")\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;print \"storeStatus 2\"\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;pickle.dump(self.status,f)\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;print \"storeStatus 3\"\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;f.close()\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;print \"storeStatus 4\"\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;f = open(self.chosenWarrantFileName(),\"w\")\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;print \"storeStatus 5\"\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;pickle.dump(self.previousWarrantFromBlock,f)\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;print \"storeStatus 6\"\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;f.close()\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.StoredStatusVariable.setValue(self.status)\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;print \"storeStatus 7\"\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return\n\n&nbsp;&nbsp; &nbsp;def updateStatus(self,block,direction):\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.status = { 'Block': block, 'Direction': direction }\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.storeStatus()\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return\n\n&nbsp;&nbsp; &nbsp;def setDisplayStatus(self,m):\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.DisplayStatusVariable.setValue(m)\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;\n&nbsp;&nbsp; &nbsp;def setPlace(self,p):\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.PlaceVariable.setValue(p)\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;\n&nbsp;&nbsp; &nbsp;def setLocomotive(self, Loc):\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;print \"setLocomotive\", Loc\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.DCCAddress = Loc['DCCAddress']\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.Train = Loc['Train']\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.SoundDecoder = Loc['SoundDecoder']\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;E = Loc['E']\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.EPE = E['EPE']\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.LPE = E['LPE']\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.SLE = E['SLE']\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.ELE = E['ELE']\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return\n\n&nbsp;&nbsp; &nbsp;def runTrainOnce(self, Route):\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;print \"runTrainOnce \",self.name\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;Warrant = Route['Warrant']\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;print \"runTrainOnce 3 \",self.name\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;w = warrants.getWarrant(Warrant)\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;print \"runTrainOnce 4 \",self.name\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.RosterEntry = w.getSpeedUtil().getRosterEntry()\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;print \"runTrainOnce 5 \",self.name\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.Throttle = self.getThrottle(self.RosterEntry)\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;print \"runTrainOnce 6 \",self.name\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.setPlace(self.status['Block'])\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;print \"runTrainOnce 10 \",self.name\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.setDisplayStatus('Ready')\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;exec self.LPE\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while (not w.isRouteFree()):\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; print \"ROUTE STOLEN \", self.name\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.waitMsec(100)\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.setDisplayStatus('Running')\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;w.runWarrant(self.MODE_RUN)\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.waitWarrantRunState(w, self.MODE_RUN)\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;print \"running \",self.name\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;block = \"Start block\"\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# Some M\u00e4rklin locomotives locks after a certain period and needs to be reset by power off\/on\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;timer = threading.Timer(40.0, self.powerOffOn)\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;timer.start()\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;while (block != None):\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.setPlace(block)\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;block = self.waitWarrantBlockChange(w)\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;print \"ude af waitWarrantBlockChange \",block\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;timer.cancel()\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (self.Entertainment.keys().count(block) &gt; 0):\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; exec self.Entertainment[block]\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;print \"ude af while loop\"\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.updateStatus(Route['EndBlock'],Route['NextDirection'])\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.setPlace(self.status['Block'])\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.setDisplayStatus(\"Entering\")\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;# Re-acquire the throttle. The Warrant has probably taken it away from us.\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.Throttle = self.getThrottle(self.RosterEntry)\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;exec self.EPE\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return\n\n&nbsp;&nbsp; &nbsp;def handle(self):\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;# handle() is called repeatedly until it returns false.\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;print \"handle \", self.name, \"waiting for sensor to run train\"\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.setDisplayStatus('Push Run')\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.setPlace(self.status['Block'])\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;if (self.DCCAddress &gt; 127):\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.Throttle = self.getThrottle(self.DCCAddress, True)\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;else:\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.Throttle = self.getThrottle(self.DCCAddress, False) \n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;if (self.runSensor.getKnownState() == INACTIVE):\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;if (self.oldRunSensorState == True):\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;# The train has been running, but shall now stop - shut off sounds etc.\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;exec self.ELE\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.oldRunSensorState = False\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.waitSensorActive(self.runSensor)\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;if (self.oldRunSensorState == False):\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;# The train is about to start running - turn on sounds etc.\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;exec self.SLE\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.oldRunSensorState = True\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;print self.name, 'sensor active'\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;#Wait and then pick a random warrant\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;WarrantCandidates = TrainsAndTrips[self.name]['Warrants']\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;print 'WarrantCandidates',WarrantCandidates\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;print 'len',len(WarrantCandidates)\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;print 'WarrantCandidates 0',WarrantCandidates[0]\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;print 'random',Randomizer.nextInt(7)\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.setDisplayStatus('Waiting')\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;print \"wait at platform \",self.name\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;TimeToWait = 1\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for Warrant in WarrantCandidates:\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (Warrant['StartBlock'] == self.status['Block']):\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TimeToWait = Warrant['TimeToWait']\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.waitMsec(TimeToWait)\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;FilteredWarrantCandidates = []\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;for Warrant in WarrantCandidates:\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;WarrantName = Warrant['Warrant']\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;WarrantMayRepeat = Warrant['MayRepeat']\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;StartBlockName = Warrant['StartBlock']\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;WarrantDirection = Warrant['Direction']\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;if (self.status['Block'] in self.previousWarrantFromBlock.keys()):\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;prevWFromBlock = self.previousWarrantFromBlock[self.status['Block']]['Warrant']\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;else:\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;prevWFromBlock = 'None'\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;print \"StartBlock: \",StartBlockName\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;if (StartBlockName == self.status['Block'] and\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;(WarrantDirection == self.status['Direction'] or self.status['Direction'] == 'DontCare') and\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;(WarrantName != prevWFromBlock or WarrantMayRepeat == 'Yes')):\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;EndBlockName&nbsp;&nbsp; = Warrant['EndBlock']\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;print \"Warrant: \", Warrant['Warrant'], \" StartBlock: \",StartBlockName, \" EndBlock: \", EndBlockName\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;StartBlock = oblocks.getBySystemName(StartBlockName)\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;EndBlock&nbsp;&nbsp; = oblocks.getBySystemName(EndBlockName)\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;w = warrants.getWarrant(WarrantName)\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;if (w.isRouteFree()):\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;FilteredWarrantCandidates.append(Warrant)\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;if (len(FilteredWarrantCandidates) == 0 and self.status['Block'] in self.previousWarrantFromBlock.keys()):\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;prevWarrant = self.previousWarrantFromBlock[self.status['Block']]\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;prevWarrantName = prevWarrant['Warrant']\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;w = warrants.getWarrant(prevWarrantName)\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;prevWarrantDirection = prevWarrant['Direction']\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;if (w.isRouteFree() and (prevWarrantDirection == self.status['Direction'] or self.status['Direction'] == 'DontCare')):\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;FilteredWarrantCandidates.append(prevWarrant)\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;if (len(FilteredWarrantCandidates) == 0):\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.waitMsec(3000)\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return 1\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;ChosenIndex = Randomizer.nextInt(len(FilteredWarrantCandidates))\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;print 'ChosenIndex',ChosenIndex\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;Warrant = FilteredWarrantCandidates[ChosenIndex]\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.previousWarrantFromBlock[self.status['Block']] = Warrant\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;print \"CHOSEN Warrant: \",Warrant\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;self.runTrainOnce(Warrant)\n&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return 1\n\n\n###################################################################################################\n###################################################################################################\n#\n# START A THREAD PER LOCOMOTIVE TO RUN THAT LOCOMOTIVE\n#\n###################################################################################################\n###################################################################################################\nfor Train in TrainsAndTrips.iterkeys():\n&nbsp;&nbsp; &nbsp;print 'start tog',Train\n&nbsp;&nbsp; &nbsp;NextTrain = RunTrain(Train)\n&nbsp;&nbsp; &nbsp;NextTrain.start()<\/span><\/pre>\n\n\n\n<p>Jeg h\u00e5ber dette kan tjene til inspiration. Alle er velkomne til at kopiere og modificere uh\u00e6mmet. S\u00e5 l\u00e6nge det er til privat brug.<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>P\u00e5 min bane kan indtil flere tog k\u00f8re samtidig. Styret af min PC. For at opn\u00e5 dette anvender jeg JMRI p\u00e5 PC&#8217;en. JMRI er forbundet til min ECOS som s\u00e5 igen styrer selve togene og sporskifterne. Hele min bane er d\u00e6kket af virtuelle signaler. Dvs. at for enhver blok er der et signal til at &hellip; <a href=\"https:\/\/lisby.dk\/wordpress\/?page_id=2424\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">Automatiseret togdrift med SCWarrants i JMRI<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-2424","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/lisby.dk\/wordpress\/index.php?rest_route=\/wp\/v2\/pages\/2424"}],"collection":[{"href":"https:\/\/lisby.dk\/wordpress\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/lisby.dk\/wordpress\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/lisby.dk\/wordpress\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/lisby.dk\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2424"}],"version-history":[{"count":15,"href":"https:\/\/lisby.dk\/wordpress\/index.php?rest_route=\/wp\/v2\/pages\/2424\/revisions"}],"predecessor-version":[{"id":3615,"href":"https:\/\/lisby.dk\/wordpress\/index.php?rest_route=\/wp\/v2\/pages\/2424\/revisions\/3615"}],"wp:attachment":[{"href":"https:\/\/lisby.dk\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2424"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}