diff --git a/README.md b/README.md index 76c08339..1f698e8b 100644 --- a/README.md +++ b/README.md @@ -22,11 +22,16 @@ The website also included a number of user guides that can be downloaded (in PDF My youTube channel also has a number of videos demonstrating the use of the application: [https://www.youtube.com/@DCCModelRailwaySignalling](https://www.youtube.com/@DCCModelRailwaySignalling) -## What's new for Release 4.6.1: +## What's new for Release 4.7.0: -* HOTFIX - Removed GPIO sensor de-bounce as this was causing sensor triggering issues -* Improvement - Reduced scroll rate when scrolling canvas via mouse (drag and drop) in Run Mode -* Improvement - Increased fontsize and padding for Route Buttons (easier to select via touchscreen) +* Track Sections, Track Sensors and Text Boxes can be hidden in RUN Mode if required - this enables the +schematic to be annonated with design notes in Edit Mode (which are then hidden in Run Mode). Also +provides flexibility for implementing other niche automation use cases on your layout. +* Ability to set the background colour of Text Boxes to 'Transparent (i.e. no fill colour) +* Point/signal button sizes can be changed via general settings (aids selection via touchscreen monitors) +* The colour of the Schematic Route buttons can now be set by the user (for differentiating route groups) +* Setup of Schematic Routes can now be triggered from Track Sensors (useful for converging routes) +* Bugfix to schematic editor - to prevent right mouse clicks whilst object moves ae in progress ![Example Screenshot2](https://github.com/johnrm174/model-railway-signalling/blob/main/README_screenshot1.png) @@ -43,34 +48,20 @@ email: enquiries@model-railway-signalling.co.uk For a first time installation use:
-$ pip install model-railway-signals <== This should work for most python installations
+$ pip install model-railway-signals             <== This should work for most python installations
 or
-$ python -m pip install model-railway-signals <== If the command line version of pip is not installed/enabled
+$ python -m pip install model-railway-signals   <== If the command line version of pip is not installed/enabled
 or
-$ python -m pip install model-railway-signals <== If you have multiple major versions of python installed
+$ python3 -m pip install model-railway-signals  <== If you have multiple major versions of python installed
 
When installing the application on later versions of python you may get the following error:
 error: externally-managed-environment
-
-× This environment is externally managed
-╰─> To install Python packages system-wide, try apt install
-    python3-xyz, where xyz is the package you are trying to
-    install.
-
-    If you wish to install a non-Debian-packaged Python package,
-    create a virtual environment using python3 -m venv path/to/venv.
-    Then use path/to/venv/bin/python and path/to/venv/bin/pip. Make
-    sure you have python3-full installed.
-
-    If you wish to install a non-Debian packaged Python application,
-    it may be easiest to use pipx install xyz, which will manage a
-    virtual environment for you. Make sure you have pipx installed.
-
-    See /usr/share/doc/python3.11/README.venv for more information.
 
-To overcome this, remove the 'EXTERNALLY-MANAGED' file from the default python installation and then re-attempt the install (as above). -Note that this is the file location for the Raspberry Pi python 3.11 installation. Other platforms may use a different location for this file: +To overcome this, add the '--break-system-packages' argument to the command - e.g. +
+$ pip install --break-system-packages model-railway-signals 
+
 $ sudo mv /usr/lib/python3.11/EXTERNALLY-MANAGED /usr/lib/python3.11/EXTERNALLY-MANAGED.old
 
@@ -80,15 +71,15 @@ $ pip install --upgrade model-railway-signals <== This should work fo or $ python -m pip install --upgrade model-railway-signals <== If the command line version of pip is not installed/enabled or -$ python -m pip install --upgrade model-railway-signals <== If you have multiple major versions of python installed +$ python3 -m pip install --upgrade model-railway-signals <== If you have multiple major versions of python installed To remove the application:
-$ pip uninstall model-railway-signals <== This should work for most python installations
+$ pip uninstall model-railway-signals                    <== This should work for most python installations
 or
 $ python -m pip uninstall model-railway-signals          <== If the command line version of pip is not installed/enabled
 or
-$ python -m pip uninstall model-railway-signals          <== If you have multiple major versions of python installed
+$ python3 -m pip uninstall model-railway-signals         <== If you have multiple major versions of python installed
 
To install a specific version of the application the application:
@@ -96,7 +87,7 @@ $ pip install model-railway-signals==4.5.0               <== This should work fo
 or
 $ python -m pip install model-railway-signals==4.5.0     <== If the command line version of pip is not installed/enabled
 or
-$ python -m pip install model-railway-signals==4.5.0     <== If you have multiple major versions of python installed
+$ python3 -m pip install model-railway-signals==4.5.0    <== If you have multiple major versions of python installed
 
The application has minimum external dependencies (over and above the 'standard' python installation), 'pyserial' and 'paho-mqtt', both of which should automatically get installed with the application. diff --git a/configuration_examples/automation_colour_light_example.sig b/configuration_examples/automation_colour_light_example.sig index 36f46ab1..89b0d4dc 100644 --- a/configuration_examples/automation_colour_light_example.sig +++ b/configuration_examples/automation_colour_light_example.sig @@ -15,7 +15,7 @@ false, "" ], - "bbox": 168, + "bbox": 172, "dccaspects": [ [ [ @@ -897,34 +897,36 @@ }, "05c99664-2259-4355-a881-4c3e2c4279d3": { "background": "grey85", - "bbox": 171, + "bbox": 175, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 1, "justify": 2, "posx": 1574.0, "posy": 185.0, - "tags": "05c99664-2259-4355-a881-4c3e2c4279d3", + "tags": "textbox1", "text": "TT-DS10" }, "0cdde9d4-28ce-4644-8601-f613605ff8f4": { "background": "grey85", - "bbox": 174, + "bbox": 178, "border": 0, "colour": "black", "font": "Courier", "fontsize": 16, "fontstyle": "bold ", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 2, "justify": 2, "posx": 819.0, "posy": 44.0, - "tags": "0cdde9d4-28ce-4644-8601-f613605ff8f4", + "tags": "textbox2", "text": "Colour Light Automation Example" }, "146287d7-4772-46bc-8952-89c98ff36bb0": { @@ -934,7 +936,7 @@ 0, 0 ], - "bbox": 180, + "bbox": 184, "colour": "black", "endx": 1675, "endy": 275, @@ -947,24 +949,26 @@ }, "147bdac0-2cb4-4de7-a45d-a87b46155eab": { "background": "grey85", - "bbox": 183, + "bbox": 187, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 3, "justify": 2, "posx": 36.0, "posy": 288.0, - "tags": "147bdac0-2cb4-4de7-a45d-a87b46155eab", + "tags": "textbox3", "text": "Up Main" }, "233a09d0-5c3b-4fd7-9838-1045b392fc85": { - "bbox": 187, + "bbox": 191, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 10, "mirror": "", @@ -974,34 +978,36 @@ }, "237bec79-c24f-4c2f-956a-a12133fa79db": { "background": "grey85", - "bbox": 190, + "bbox": 194, "border": 0, "colour": "black", "font": "Courier", "fontsize": 12, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 4, "justify": 2, "posx": 575.0, "posy": 150.0, - "tags": "237bec79-c24f-4c2f-956a-a12133fa79db", + "tags": "textbox4", "text": "MPD" }, "2809aba0-8417-4ffe-b8ab-9d0bcfd4b777": { "background": "grey85", - "bbox": 193, + "bbox": 197, "border": 0, "colour": "black", "font": "Courier", "fontsize": 12, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 5, "justify": 2, "posx": 1460.0, "posy": 149.0, - "tags": "2809aba0-8417-4ffe-b8ab-9d0bcfd4b777", + "tags": "textbox5", "text": "Goods Yard" }, "350ab106-55fd-4920-800f-606a103c9392": { @@ -1011,7 +1017,7 @@ 0, 0 ], - "bbox": 199, + "bbox": 203, "colour": "black", "endx": 0, "endy": 275, @@ -1025,7 +1031,7 @@ "36c92467-cd2d-4608-bd3d-1ae2fe2fb9b0": { "alsoswitch": 6, "automatic": false, - "bbox": 205, + "bbox": 209, "colour": "black", "dccaddress": 1005, "dccreversed": false, @@ -1106,7 +1112,7 @@ false, "" ], - "bbox": 222, + "bbox": 226, "dccaspects": [ [ [ @@ -1966,18 +1972,19 @@ }, "3d9905d7-3a8c-42d0-ada6-e78748273374": { "background": "grey85", - "bbox": 225, + "bbox": 229, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 6, "justify": 2, "posx": 690.0, "posy": 245.0, - "tags": "3d9905d7-3a8c-42d0-ada6-e78748273374", + "tags": "textbox6", "text": "AA-4Awpl&lhf" }, "3f11b41e-b356-402c-b59e-b314963b3723": { @@ -1987,7 +1994,7 @@ 0, 0 ], - "bbox": 231, + "bbox": 235, "colour": "black", "endx": 1300.0, "endy": 225.0, @@ -2010,7 +2017,7 @@ false, "" ], - "bbox": 242, + "bbox": 246, "dccaspects": [ [ [ @@ -2902,40 +2909,43 @@ }, "4c400af8-bf96-447e-85f4-9928287360c5": { "background": "grey85", - "bbox": 245, + "bbox": 249, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 7, "justify": 2, "posx": 1141.0, "posy": 240.0, - "tags": "4c400af8-bf96-447e-85f4-9928287360c5", + "tags": "textbox7", "text": "AA-4Awpl" }, "500f7750-b750-4d33-b9fe-1e6373da674d": { "background": "grey85", - "bbox": 248, + "bbox": 252, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 8, "justify": 2, "posx": 512.0, "posy": 239.0, - "tags": "500f7750-b750-4d33-b9fe-1e6373da674d", + "tags": "textbox8", "text": "TT-DS5HS" }, "5424b00f-f0ad-4900-8b9c-7d858a2e05d0": { - "bbox": 252, + "bbox": 256, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 4, "mirror": "", @@ -2955,7 +2965,7 @@ false, "" ], - "bbox": 271, + "bbox": 275, "dccaspects": [ [ [ @@ -3836,7 +3846,7 @@ false, "" ], - "bbox": 288, + "bbox": 292, "dccaspects": [ [ [ @@ -4706,7 +4716,7 @@ false, "" ], - "bbox": 305, + "bbox": 309, "dccaspects": [ [ [ @@ -5597,7 +5607,7 @@ false, "" ], - "bbox": 322, + "bbox": 326, "dccaspects": [ [ [ @@ -6457,18 +6467,19 @@ }, "632a3066-6bfb-4269-81f1-c73b4b294ae9": { "background": "grey85", - "bbox": 325, + "bbox": 329, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 9, "justify": 2, "posx": 335.0, "posy": 240.0, - "tags": "632a3066-6bfb-4269-81f1-c73b4b294ae9", + "tags": "textbox9", "text": "TT-DS5HS" }, "64b572a6-a2d0-4010-b33f-cd73b16f3f24": { @@ -6483,7 +6494,7 @@ false, "" ], - "bbox": 342, + "bbox": 346, "dccaspects": [ [ [ @@ -7353,7 +7364,7 @@ false, "" ], - "bbox": 353, + "bbox": 357, "dccaspects": [ [ [ @@ -8235,7 +8246,7 @@ "6c28ce24-e08c-4845-8e06-b21f418911eb": { "alsoswitch": 0, "automatic": true, - "bbox": 358, + "bbox": 362, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -8255,56 +8266,60 @@ }, "6df4e851-8669-4940-8dc9-fec3e1873294": { "background": "grey85", - "bbox": 361, + "bbox": 365, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 10, "justify": 2, "posx": 51.0, "posy": 191.0, - "tags": "6df4e851-8669-4940-8dc9-fec3e1873294", + "tags": "textbox10", "text": "Set\nNext\nTrain" }, "709c0d2f-3d16-4669-b53f-38614ef26ea2": { "background": "grey85", - "bbox": 364, + "bbox": 368, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 11, "justify": 2, "posx": 1636.0, "posy": 212.0, - "tags": "709c0d2f-3d16-4669-b53f-38614ef26ea2", + "tags": "textbox11", "text": "Down Main" }, "75eb7e1b-08a6-4223-8ed8-f31381186102": { "background": "grey85", - "bbox": 367, + "bbox": 371, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 12, "justify": 2, "posx": 165.0, "posy": 237.0, - "tags": "75eb7e1b-08a6-4223-8ed8-f31381186102", + "tags": "textbox12", "text": "TT-DS10" }, "7648db7c-97c7-41f3-beee-e60656b9173a": { - "bbox": 371, + "bbox": 375, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 14, "mirror": "", @@ -8313,9 +8328,10 @@ "tags": "section14" }, "768c5e02-b80c-4025-a27a-ab5a225ae105": { - "bbox": 375, + "bbox": 379, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 9, "mirror": "", @@ -8325,18 +8341,19 @@ }, "7b1d99fb-c02e-48be-9c48-c5645301eb85": { "background": "grey85", - "bbox": 378, + "bbox": 382, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 13, "justify": 2, "posx": 1143.0, "posy": 81.0, - "tags": "7b1d99fb-c02e-48be-9c48-c5645301eb85", + "tags": "textbox13", "text": "Signals 2 and 3 each\nControlled by a single\nSignalist SC1 Decoder\nBase Address = 601 and 633" }, "8232da6f-9aeb-45bd-8519-039297c6d3b9": { @@ -8346,7 +8363,7 @@ 0, 0 ], - "bbox": 384, + "bbox": 388, "colour": "black", "endx": 800.0, "endy": 225.0, @@ -8369,7 +8386,7 @@ false, "" ], - "bbox": 395, + "bbox": 399, "dccaspects": [ [ [ @@ -9240,18 +9257,19 @@ }, "8c773174-1179-4350-946c-4e8548569e33": { "background": "grey85", - "bbox": 398, + "bbox": 402, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 14, "justify": 2, "posx": 1144.0, "posy": 133.0, - "tags": "8c773174-1179-4350-946c-4e8548569e33", + "tags": "textbox14", "text": "AA-4Awpl" }, "8efa0c5d-19c3-4826-a797-34453446bb67": { @@ -9261,7 +9279,7 @@ 0, 0 ], - "bbox": 404, + "bbox": 408, "colour": "black", "endx": 1200.0, "endy": 175.0, @@ -9274,72 +9292,77 @@ }, "956e68f3-b5ca-4869-9be5-186c3b2b2663": { "background": "grey85", - "bbox": 407, + "bbox": 411, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 15, "justify": 2, "posx": 1628.0, "posy": 311.0, - "tags": "956e68f3-b5ca-4869-9be5-186c3b2b2663", + "tags": "textbox15", "text": "Set\nNext\nTrain" }, "9754f858-e3bb-4c96-a3a6-d70b3cc8287f": { "background": "grey85", - "bbox": 410, + "bbox": 414, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 16, "justify": 2, "posx": 1324.0, "posy": 135.0, - "tags": "9754f858-e3bb-4c96-a3a6-d70b3cc8287f", + "tags": "textbox16", "text": "Ground Signals 7, 13 & 14\nControlled by a single\nSignalist SC1 Decoder\nBase Address = 601" }, "9a537d13-0794-4f88-8386-67b76bbd39d7": { "background": "grey85", - "bbox": 413, + "bbox": 417, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 17, "justify": 2, "posx": 501.0, "posy": 315.0, - "tags": "9a537d13-0794-4f88-8386-67b76bbd39d7", + "tags": "textbox17", "text": "TT-DS10" }, "9f0bd8e5-accc-4abc-8491-8db955715941": { "background": "grey85", - "bbox": 416, + "bbox": 420, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 18, "justify": 1, "posx": 850.0, "posy": 387.0, - "tags": "9f0bd8e5-accc-4abc-8491-8db955715941", + "tags": "textbox18", "text": "TT-DS10 - Train Tech 4 aspect DCC signal\nTT-DS5HS - Train Tech 4 aspect DCC signal with flashing aspects\nAA-4Awpl - Absolute Aspects 4 aspect signal with position light\nAA-4wpl&lhf - Absolute Asp'ts 4 asp signal with pos light & LH Feather" }, "a19e74b3-1746-478b-92c3-2ae4183b4c71": { - "bbox": 420, + "bbox": 424, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 6, "mirror": "", @@ -9359,7 +9382,7 @@ false, "" ], - "bbox": 437, + "bbox": 441, "dccaspects": [ [ [ @@ -10229,7 +10252,7 @@ false, "" ], - "bbox": 454, + "bbox": 458, "dccaspects": [ [ [ @@ -11094,7 +11117,7 @@ 1, 1 ], - "bbox": 460, + "bbox": 464, "colour": "black", "endx": 1250.0, "endy": 175.0, @@ -11107,18 +11130,19 @@ }, "abd81429-2c2a-4b04-bc62-81a103da6e24": { "background": "#76d0d9", - "bbox": 463, + "bbox": 467, "border": 0, "colour": "black", "font": "Courier", "fontsize": 9, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 19, "justify": 2, "posx": 950.0, "posy": 200.0, - "tags": "abd81429-2c2a-4b04-bc62-81a103da6e24", + "tags": "textbox19", "text": " Platform 2/3 " }, "acbd35af-8e88-400e-979b-44e1de8c927d": { @@ -11133,7 +11157,7 @@ false, "" ], - "bbox": 480, + "bbox": 484, "dccaspects": [ [ [ @@ -11993,24 +12017,26 @@ }, "b31a2cd1-3494-4f09-95b8-f50e6708bdeb": { "background": "grey85", - "bbox": 483, + "bbox": 487, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 20, "justify": 2, "posx": 121.0, "posy": 315.0, - "tags": "b31a2cd1-3494-4f09-95b8-f50e6708bdeb", + "tags": "textbox20", "text": "TT-DS10" }, "b3b255c4-744a-4476-a7d0-3535e718fac1": { - "bbox": 487, + "bbox": 491, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 3, "mirror": "", @@ -12019,9 +12045,10 @@ "tags": "section3" }, "b55de778-8bb5-4d62-965e-d62ca9aa136d": { - "bbox": 491, + "bbox": 495, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 13, "mirror": "", @@ -12031,24 +12058,25 @@ }, "b778ffd2-cf39-4c24-b299-58e6131ff2d2": { "background": "grey85", - "bbox": 494, + "bbox": 498, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 21, "justify": 2, "posx": 304.0, "posy": 318.0, - "tags": "b778ffd2-cf39-4c24-b299-58e6131ff2d2", + "tags": "textbox21", "text": "TT-DS10" }, "b8c1ca15-569a-42d8-9692-c4634b06e70b": { "alsoswitch": 4, "automatic": false, - "bbox": 501, + "bbox": 505, "colour": "black", "dccaddress": 1003, "dccreversed": false, @@ -12114,7 +12142,7 @@ 0, 0 ], - "bbox": 507, + "bbox": 511, "colour": "black", "endx": -50.0, "endy": 225.0, @@ -12128,7 +12156,7 @@ "c52e3e22-22c7-4623-bd67-8cacac93ba06": { "alsoswitch": 1, "automatic": false, - "bbox": 514, + "bbox": 518, "colour": "black", "dccaddress": 1002, "dccreversed": false, @@ -12194,7 +12222,7 @@ 1, 1 ], - "bbox": 520, + "bbox": 524, "colour": "black", "endx": 525.0, "endy": 175.0, @@ -12217,7 +12245,7 @@ false, "" ], - "bbox": 531, + "bbox": 535, "dccaspects": [ [ [ @@ -13107,9 +13135,10 @@ ] }, "cf7ab31e-b781-435b-8b96-0398da28e8f1": { - "bbox": 535, + "bbox": 539, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 7, "mirror": "", @@ -13118,9 +13147,10 @@ "tags": "section7" }, "d2c6a4dd-630c-4836-ad26-52269177edcc": { - "bbox": 539, + "bbox": 543, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 1, "mirror": "", @@ -13129,9 +13159,10 @@ "tags": "section1" }, "d53174a7-e4e9-425d-94a8-80507bd85a04": { - "bbox": 543, + "bbox": 547, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 5, "mirror": "", @@ -13140,9 +13171,10 @@ "tags": "section5" }, "d5d3aa4e-cc00-4d66-a1c6-8b52bb7b1a92": { - "bbox": 547, + "bbox": 551, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 12, "mirror": "", @@ -13162,7 +13194,7 @@ false, "" ], - "bbox": 564, + "bbox": 568, "dccaspects": [ [ [ @@ -14023,7 +14055,7 @@ "df337c59-d6c8-420a-9165-628b58e8be08": { "alsoswitch": 0, "automatic": true, - "bbox": 569, + "bbox": 573, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -14042,9 +14074,10 @@ "ybuttonoffset": 0 }, "e3b1ad43-1e39-456d-aad8-7943e5864ac8": { - "bbox": 573, + "bbox": 577, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 11, "mirror": "", @@ -14054,34 +14087,36 @@ }, "e4853677-978f-4fc5-b03d-c92d0bb216ea": { "background": "#76d0d9", - "bbox": 576, + "bbox": 580, "border": 0, "colour": "black", "font": "Courier", "fontsize": 9, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 22, "justify": 2, "posx": 950.0, "posy": 300.0, - "tags": "e4853677-978f-4fc5-b03d-c92d0bb216ea", + "tags": "textbox22", "text": " Platform 1 " }, "e54253c4-ec5b-4a4a-835d-5e3b5eb4e183": { "background": "grey85", - "bbox": 579, + "bbox": 583, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 23, "justify": 2, "posx": 586.0, "posy": 114.0, - "tags": "e54253c4-ec5b-4a4a-835d-5e3b5eb4e183", + "tags": "textbox23", "text": "Signal 1 Controlled by a \nsingle Signalist SC1 Decoder\nBase Address = 617" }, "ed8d646c-60cc-4a5a-b8f0-cbd337263db8": { @@ -14096,7 +14131,7 @@ false, "" ], - "bbox": 590, + "bbox": 594, "dccaspects": [ [ [ @@ -14986,9 +15021,10 @@ ] }, "ee774c2b-3946-44aa-9254-a31f2bebecb8": { - "bbox": 594, + "bbox": 598, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 15, "mirror": "", @@ -15008,7 +15044,7 @@ false, "" ], - "bbox": 605, + "bbox": 609, "dccaspects": [ [ [ @@ -15930,9 +15966,10 @@ ] }, "f1dbf1a3-0d5b-43e2-a476-18ccb8b0b920": { - "bbox": 609, + "bbox": 613, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 8, "mirror": "", @@ -15943,7 +15980,7 @@ "f2b7d2d9-eb4d-4e55-859f-100131262fe2": { "alsoswitch": 0, "automatic": true, - "bbox": 614, + "bbox": 618, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -15963,24 +16000,26 @@ }, "f514300f-50b5-477c-9673-8cacdaac0c5b": { "background": "grey85", - "bbox": 617, + "bbox": 621, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 24, "justify": 2, "posx": 836.0, "posy": 132.0, - "tags": "f514300f-50b5-477c-9673-8cacdaac0c5b", + "tags": "textbox24", "text": "Ground Signals 5, 6 & 15\nControlled by a single\nSignalist SC1 Decoder\nBase Address = 609" }, "f771894a-e7a8-4e26-a9d0-d316f9aff774": { - "bbox": 621, + "bbox": 625, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 2, "mirror": "", @@ -15990,18 +16029,19 @@ }, "f7dc96fe-3f96-4f6b-8a63-8589f6b82d33": { "background": "grey85", - "bbox": 624, + "bbox": 628, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 25, "justify": 2, "posx": 1343.0, "posy": 318.0, - "tags": "f7dc96fe-3f96-4f6b-8a63-8589f6b82d33", + "tags": "textbox25", "text": "TT-DS10" }, "f7e885c9-faa7-4ee6-87df-9c035c5dadeb": { @@ -16016,7 +16056,7 @@ true, "8" ], - "bbox": 644, + "bbox": 648, "dccaspects": [ [ [ @@ -16938,7 +16978,7 @@ false, "" ], - "bbox": 661, + "bbox": 665, "dccaspects": [ [ [ @@ -17798,34 +17838,36 @@ }, "ff661e89-2111-4e98-8191-9fe3d3e39471": { "background": "grey85", - "bbox": 664, + "bbox": 668, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 26, "justify": 2, "posx": 787.0, "posy": 316.0, - "tags": "ff661e89-2111-4e98-8191-9fe3d3e39471", + "tags": "textbox26", "text": "TT-DS10" }, "ffc65294-6626-4af4-9a43-305530b7f891": { "background": "grey85", - "bbox": 667, + "bbox": 671, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 27, "justify": 2, "posx": 1525.0, "posy": 317.0, - "tags": "ffc65294-6626-4af4-9a43-305530b7f891", + "tags": "textbox27", "text": "TT-DS10" } }, @@ -17932,11 +17974,12 @@ }, "general": { "automation": true, + "buttonsize": 8, "editmode": true, "filename": "/home/john/model-railway-signalling/configuration_examples/automation_colour_light_example.sig", "info": "This layout is a development of the basic interlocking example to demonstrate signalling automation\n(note that the application needs to be in 'run' mode for all automation features to function).\n\nTrack sensors (such as those from TrainTech, Heathcote Electronics, DCC Concepts etc) can be located \nwith each signal and connected in to the R-Pi's GPIO ports (via appropriate opto isolator circuits).\nEach GPIO port can then be mapped to a signal to generate 'signal passed' events - In 'edit' mode,\nclick on a signal and select the 'automation' tab to view the configuration. 'Signal passed' events\ncan also be triggered by clicking on the small button at the base of each signal (as an aid to \ndevelopment and testing of signalling schemes before going 'live' with the R-Pi).\n\nTrack sections can be added to the schematic to provide a mimic display of track occupancy (when a\ntrain passes a signal (signal must be 'off') it gets passed from the section behind to the section ahead.\nThe required behavior is configured via the 'automation' tab of each signal. For example, signal 1\ncontrols two 'routes' so when passed, section 2 will be cleared and either section 12 or 3 will be set \nto occupied, depending on which route the signal is cleared for.\n\nAll main signals are configured to be 'overridden' if the track section ahead is occupied. This means\nthat each signal will automatically change to 'on' when passed (section ahead is occupied) and then\nrevert to'off' as soon as the section ahead is cleared. The actual aspect displayed when 'off' will \ndepend on the aspect of the signal ahead (specified via the 'interlocking tab'). This means that any\nsignals on the layout that don't need a level of manual control can be 'fully automated' (no buttons).\n\nTo simulate prototypical aspect changes for trains going off scene, the 'exit' signals (signals 4 and\n11 in this example) can be configured as 'timed signals'. Once passed they are overriden to 'on' (as\nper the other signals on the layout) but then cycle back through the aspects back to 'proceed'.\n\nTrack sensors can also positioned slightly before the signal and mapped to generate 'signal approached'\nevents to simulate 'approach control'. In this example, signal 1 is configured for 'release on yellow'\napproach control for the diverging route (in the case of the diverging route having a speed restriction).\nThis means the signal (when 'off') will initially display a yellow aspect (with the signals behind \ndisplaying flashing yellow and flashing double yellow). When the train approaches the signal (having\nslowed down for the diverging route) it will automatically change to green.\n\nTo demonstrate all the above in action, set 'run' mode and reset the layout to set all signals, points\nand track sections back to their default states. unlock, switch and re-lock points 2 and 3 for the\ndiverging loop line and set signals 8, 1, 2 and 4 to 'off'. Right click the far left track section and\nenter a train designation code of your choice (this sets the section to 'occupied'. Now, move the train\nthrough the schematic, clicking the 'signal passed' button at the base of each signal along the route\nin turn (don't forget to click the 'signal approached' button ahead of signal 1 if you want to see\napproach control working). When you get to signal 4, the train will disappear 'off scene' and the signal\nwill cycle back through the aspects (with a short delay between aspects) until it gets back to green.\n\nAlternatively, if running on a Raspberry pi, the track sensors can be triggered directly via the Ri-Pi's \nGPIO ports (momentarily connect the appropriate GPIO pin to one of the R-Pi's 0V DC pins). Select \n'Settings' =>'Sensors' from the Menubar to view the configuration. In this example the Track Sensor IDs\nhave been mapped to the GPIO port number, but any numbering scheme can be used (e.g. allocate the same ID \nfor the sensor as the signal ID it is triggering). \n\nThe layout is fully configured to support all possible train movements (including shunting movements)\nso have a play - but note that signals can only be 'passed' when 'off' for the track occupancy\nchanges to work correctly (the application currently doesn't support any form of SPAD functionality)\n\n\n\n", "spadpopups": false, - "version": "Version 4.6.1" + "version": "Version 4.7.0" }, "gpio": { "portmappings": [ diff --git a/configuration_examples/automation_semaphore_example.sig b/configuration_examples/automation_semaphore_example.sig index 774de103..39ca7aee 100644 --- a/configuration_examples/automation_semaphore_example.sig +++ b/configuration_examples/automation_semaphore_example.sig @@ -15,7 +15,7 @@ true, "" ], - "bbox": 790, + "bbox": 794, "dccaspects": [ [ [ @@ -907,7 +907,7 @@ true, "" ], - "bbox": 817, + "bbox": 821, "dccaspects": [ [ [ @@ -1772,7 +1772,7 @@ 0, 0 ], - "bbox": 823, + "bbox": 827, "colour": "black", "endx": 1275, "endy": 225, @@ -1795,7 +1795,7 @@ true, "" ], - "bbox": 850, + "bbox": 854, "dccaspects": [ [ [ @@ -2654,9 +2654,10 @@ ] }, "1b3f1dca-d687-4ec6-98e3-cc017d9f844a": { - "bbox": 854, + "bbox": 858, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 11, "mirror": "", @@ -2665,9 +2666,10 @@ "tags": "section11" }, "23ef06a2-5597-4aba-920c-ca15828a7733": { - "bbox": 858, + "bbox": 862, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 9, "mirror": "", @@ -2687,7 +2689,7 @@ true, "" ], - "bbox": 885, + "bbox": 889, "dccaspects": [ [ [ @@ -3557,7 +3559,7 @@ true, "" ], - "bbox": 912, + "bbox": 916, "dccaspects": [ [ [ @@ -4448,7 +4450,7 @@ false, "" ], - "bbox": 938, + "bbox": 942, "dccaspects": [ [ [ @@ -5307,9 +5309,10 @@ ] }, "3c5565ee-45f3-4298-a7da-5c1c52ebf2d2": { - "bbox": 942, + "bbox": 946, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 13, "mirror": "", @@ -5324,7 +5327,7 @@ 0, 0 ], - "bbox": 948, + "bbox": 952, "colour": "black", "endx": 1375, "endy": 225, @@ -5336,9 +5339,10 @@ "tags": "line2" }, "40da50ad-40cf-4f2f-a86f-eef794d41ace": { - "bbox": 952, + "bbox": 956, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 2, "mirror": "", @@ -5358,7 +5362,7 @@ false, "" ], - "bbox": 960, + "bbox": 964, "dccaspects": [ [ [ @@ -6249,9 +6253,10 @@ ] }, "49436626-93f4-4b47-b9ae-5221c9d27577": { - "bbox": 964, + "bbox": 968, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 7, "mirror": "", @@ -6271,7 +6276,7 @@ true, "" ], - "bbox": 992, + "bbox": 996, "dccaspects": [ [ [ @@ -7147,7 +7152,7 @@ 0, 0 ], - "bbox": 998, + "bbox": 1002, "colour": "black", "endx": 1375, "endy": 275, @@ -7170,7 +7175,7 @@ false, "" ], - "bbox": 1045, + "bbox": 1049, "dccaspects": [ [ [ @@ -8040,7 +8045,7 @@ false, "" ], - "bbox": 1053, + "bbox": 1057, "dccaspects": [ [ [ @@ -8922,7 +8927,7 @@ "6c28ce24-e08c-4845-8e06-b21f418911eb": { "alsoswitch": 4, "automatic": false, - "bbox": 1060, + "bbox": 1064, "colour": "black", "dccaddress": 101, "dccreversed": false, @@ -8993,7 +8998,7 @@ false, "" ], - "bbox": 1086, + "bbox": 1090, "dccaspects": [ [ [ @@ -9852,9 +9857,10 @@ ] }, "85d79875-69a8-4f48-b990-56add64e268d": { - "bbox": 1090, + "bbox": 1094, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 1, "mirror": "", @@ -9869,7 +9875,7 @@ 0, 0 ], - "bbox": 1096, + "bbox": 1100, "colour": "black", "endx": 1275, "endy": 175, @@ -9892,7 +9898,7 @@ false, "" ], - "bbox": 1104, + "bbox": 1108, "dccaspects": [ [ [ @@ -10762,9 +10768,10 @@ ] }, "94fd8cf6-a410-4f67-9da5-c985edcbf688": { - "bbox": 1108, + "bbox": 1112, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 3, "mirror": "", @@ -10784,7 +10791,7 @@ false, "" ], - "bbox": 1134, + "bbox": 1138, "dccaspects": [ [ [ @@ -11649,7 +11656,7 @@ 0, 0 ], - "bbox": 1140, + "bbox": 1144, "colour": "black", "endx": 0, "endy": 275, @@ -11661,9 +11668,10 @@ "tags": "line5" }, "a6e56e82-869d-4a63-b9fe-649476f59a62": { - "bbox": 1144, + "bbox": 1148, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 12, "mirror": "", @@ -11678,7 +11686,7 @@ 1, 1 ], - "bbox": 1150, + "bbox": 1154, "colour": "black", "endx": 1325, "endy": 175, @@ -11701,7 +11709,7 @@ false, "" ], - "bbox": 1176, + "bbox": 1180, "dccaspects": [ [ [ @@ -12560,9 +12568,10 @@ ] }, "af408a69-7a43-4f8e-9453-728ec2747514": { - "bbox": 1180, + "bbox": 1184, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 6, "mirror": "", @@ -12573,7 +12582,7 @@ "b8c1ca15-569a-42d8-9692-c4634b06e70b": { "alsoswitch": 0, "automatic": true, - "bbox": 1185, + "bbox": 1189, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -12592,9 +12601,10 @@ "ybuttonoffset": 0 }, "bd8c0b62-add0-4714-b49a-7bbae0da4124": { - "bbox": 1189, + "bbox": 1193, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 8, "mirror": "", @@ -12614,7 +12624,7 @@ false, "" ], - "bbox": 1215, + "bbox": 1219, "dccaspects": [ [ [ @@ -13479,7 +13489,7 @@ 0, 0 ], - "bbox": 1221, + "bbox": 1225, "colour": "black", "endx": 0, "endy": 225, @@ -13493,7 +13503,7 @@ "c52e3e22-22c7-4623-bd67-8cacac93ba06": { "alsoswitch": 1, "automatic": false, - "bbox": 1228, + "bbox": 1232, "colour": "black", "dccaddress": 100, "dccreversed": false, @@ -13559,7 +13569,7 @@ 1, 1 ], - "bbox": 1234, + "bbox": 1238, "colour": "black", "endx": 675, "endy": 175, @@ -13571,9 +13581,10 @@ "tags": "line8" }, "c88a11f5-d1b0-4a02-869e-f53d2c930635": { - "bbox": 1238, + "bbox": 1242, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 5, "mirror": "", @@ -13593,7 +13604,7 @@ false, "" ], - "bbox": 1246, + "bbox": 1250, "dccaspects": [ [ [ @@ -14483,9 +14494,10 @@ ] }, "d5bac97c-de07-407d-966d-83ea0ae26a83": { - "bbox": 1250, + "bbox": 1254, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 10, "mirror": "", @@ -14496,7 +14508,7 @@ "d9840a46-28fd-40dd-a059-df46b9a762e2": { "alsoswitch": 6, "automatic": false, - "bbox": 1256, + "bbox": 1260, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -14566,9 +14578,10 @@ "ybuttonoffset": 0 }, "dcf7ea0d-cd58-45bf-842e-d4e6e87c9a2a": { - "bbox": 1260, + "bbox": 1264, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 15, "mirror": "", @@ -14579,7 +14592,7 @@ "df337c59-d6c8-420a-9165-628b58e8be08": { "alsoswitch": 0, "automatic": true, - "bbox": 1265, + "bbox": 1269, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -14598,9 +14611,10 @@ "ybuttonoffset": 0 }, "e0705584-fbf0-47f0-a695-810489e4d5d4": { - "bbox": 1269, + "bbox": 1273, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 4, "mirror": "", @@ -14620,7 +14634,7 @@ false, "" ], - "bbox": 1277, + "bbox": 1281, "dccaspects": [ [ [ @@ -15512,7 +15526,7 @@ "f14ef6e8-7b7b-4568-b0e6-c105889caf10": { "alsoswitch": 0, "automatic": true, - "bbox": 1282, + "bbox": 1286, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -15542,7 +15556,7 @@ false, "" ], - "bbox": 1290, + "bbox": 1294, "dccaspects": [ [ [ @@ -16475,7 +16489,7 @@ true, "" ], - "bbox": 1320, + "bbox": 1324, "dccaspects": [ [ [ @@ -17386,9 +17400,10 @@ ] }, "fc4b8a11-3862-417f-ba09-493456b59064": { - "bbox": 1324, + "bbox": 1328, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 14, "mirror": "", @@ -17500,11 +17515,12 @@ }, "general": { "automation": true, + "buttonsize": 8, "editmode": false, "filename": "/home/john/model-railway-signalling/configuration_examples/automation_semaphore_example.sig", "info": "This layout is a development of the basic interlocking example to demonstrate signalling automation\n(note that the application needs to be in 'run' mode for all automation features to function).\n\nTrack sensors (such as those from TrainTech, Heathcote Electronics, DCC Concepts etc) can be located \nwith each signal and connected in to the R-Pi's GPIO ports (via appropriate opto-isolator circuits).\nEach GPIO port can then be mapped to a signal to generate 'signal passed' events - In 'edit' mode,\nclick on a signal and select the 'automation' tab to view the configuration (note that external\nGPIO sensors haven't been configured for this particular example layout). 'Signal passed' events\ncan also be triggered by clicking on the small button at the base of each signal (as an aid to \ndevelopment and testing of signalling schemes before going 'live' with the R-Pi).\n\nTrack sections can be added to the schematic to provide a mimic display of track occupancy (when a\ntrain passes a signal (signal must be 'off') it gets passed from the section behind to the section ahead.\nThe required behavior is configured via the 'automation' tab of each signal. For example, signal 1\ncontrols two 'routes' so when passed, section 2 will be cleared and either section 12 or 3 will be set \nto occupied, depending on which route the signal is cleared for.\n\nAll main signals are configured to be 'overridden' if the track section ahead is occupied. This means\nthat each signal will automatically change to 'on' when passed (section ahead is occupied) and then\nrevert to 'off' as soon as the section ahead is cleared. Note that some of the signals in this example \nhave been configured as 'fully automatic' (without a control button). This means that they will be 'off'\nby default and controlled entirely by whether the track section ahead is occupied or not.\n\nTo simulate prototypical aspect changes for trains going off scene, the 'exit' signals (signals 4 and\n11 in this example) can be configured as 'timed signals'. Once passed (when 'off') they are overriden \nto 'on' (as per the other signals on the layout) but then revert to 'off' after the specified delay.\n\nTrack sensors can also be positioned slightly before the signal and mapped to generate 'signal \napproached' events to simulate 'approach control'. In this example, all non-fully-automatic home signals \nare configured for 'release on red based on the signals ahead'. This means that if any home signals ahead\nare showing danger then the home signal will also be overridden to 'on' by default.As the train approaches\nthe signal (at a slow speed as the signal is against it) then the signal will be released to 'off' to\nallow the train to pass and then revert back to the overidden 'on' state when the signal is passed.\nSignal 1 is also configured for 'release on red' for the diverging (low-speed) route. In this case it\nwill be overridden to 'on' even if all home signals ahead are 'off', only released as the train approaches.\n\nTo demonstrate all the above in action, set 'run' mode and reset the layout to set all signals, points\nand track sections back to their default states. Right click the far left track section and enter a train\ndesignation code of your choice (this sets the section to 'occupied'). Set signals 8, 1 and 3 to 'off' (note\nthat they will remail overridden to 'on' as home signal ahead 4 is still showing 'on'. Now, move the train\nthrough the schematic, clicking the 'signal passed' button at the base of each signal along the route\nin turn (don't forget to click the 'signal approached' button ahead of each signal if you want to see\napproach control working). When the train reaches signal 4, set the signal to 'off' and trigger the signal\npassed event. The track section before the signal will be cleared and the signal overridden to 'on' as part\nof the timed sequence. After a short delay it will revert to 'off' and when it does, all home signals behind\nwill also revert to 'off' (as they will no longer be overridden on a home signal ahead).\n\nThe layout is fully configured to support all possible train movements (including shunting movements)\nso have a play - but note that signals can only be 'passed' when 'off' for the track occupancy\nchanges to work correctly (the application currently doesn't support any form of SPAD functionality)\n\n", "spadpopups": false, - "version": "Version 4.6.1" + "version": "Version 4.7.0" }, "gpio": { "portmappings": [], diff --git a/configuration_examples/colour_light_signals.sig b/configuration_examples/colour_light_signals.sig index 7fbcacb5..cb0b7164 100644 --- a/configuration_examples/colour_light_signals.sig +++ b/configuration_examples/colour_light_signals.sig @@ -15,7 +15,7 @@ false, "" ], - "bbox": 1430, + "bbox": 1434, "dccaspects": [ [ [ @@ -885,7 +885,7 @@ false, "" ], - "bbox": 1447, + "bbox": 1451, "dccaspects": [ [ [ @@ -1755,7 +1755,7 @@ false, "" ], - "bbox": 1464, + "bbox": 1468, "dccaspects": [ [ [ @@ -2625,7 +2625,7 @@ false, "" ], - "bbox": 1481, + "bbox": 1485, "dccaspects": [ [ [ @@ -3495,7 +3495,7 @@ false, "" ], - "bbox": 1499, + "bbox": 1503, "dccaspects": [ [ [ @@ -4365,7 +4365,7 @@ false, "" ], - "bbox": 1510, + "bbox": 1514, "dccaspects": [ [ [ @@ -5235,7 +5235,7 @@ false, "" ], - "bbox": 1521, + "bbox": 1525, "dccaspects": [ [ [ @@ -6105,7 +6105,7 @@ false, "" ], - "bbox": 1538, + "bbox": 1542, "dccaspects": [ [ [ @@ -6975,7 +6975,7 @@ false, "" ], - "bbox": 1555, + "bbox": 1559, "dccaspects": [ [ [ @@ -7845,7 +7845,7 @@ false, "" ], - "bbox": 1572, + "bbox": 1576, "dccaspects": [ [ [ @@ -8715,7 +8715,7 @@ false, "" ], - "bbox": 1592, + "bbox": 1596, "dccaspects": [ [ [ @@ -9585,7 +9585,7 @@ false, "" ], - "bbox": 1609, + "bbox": 1613, "dccaspects": [ [ [ @@ -10455,7 +10455,7 @@ false, "" ], - "bbox": 1620, + "bbox": 1624, "dccaspects": [ [ [ @@ -11325,7 +11325,7 @@ false, "" ], - "bbox": 1637, + "bbox": 1641, "dccaspects": [ [ [ @@ -12195,7 +12195,7 @@ false, "" ], - "bbox": 1654, + "bbox": 1658, "dccaspects": [ [ [ @@ -13065,7 +13065,7 @@ false, "" ], - "bbox": 1671, + "bbox": 1675, "dccaspects": [ [ [ @@ -13935,7 +13935,7 @@ false, "" ], - "bbox": 1688, + "bbox": 1692, "dccaspects": [ [ [ @@ -14805,7 +14805,7 @@ false, "" ], - "bbox": 1705, + "bbox": 1709, "dccaspects": [ [ [ @@ -15675,7 +15675,7 @@ false, "" ], - "bbox": 1722, + "bbox": 1726, "dccaspects": [ [ [ @@ -16545,7 +16545,7 @@ false, "" ], - "bbox": 1740, + "bbox": 1744, "dccaspects": [ [ [ @@ -17415,7 +17415,7 @@ false, "" ], - "bbox": 1757, + "bbox": 1761, "dccaspects": [ [ [ @@ -18285,7 +18285,7 @@ false, "" ], - "bbox": 1774, + "bbox": 1778, "dccaspects": [ [ [ @@ -19155,7 +19155,7 @@ false, "" ], - "bbox": 1792, + "bbox": 1796, "dccaspects": [ [ [ @@ -20025,7 +20025,7 @@ false, "" ], - "bbox": 1810, + "bbox": 1814, "dccaspects": [ [ [ @@ -20895,7 +20895,7 @@ false, "" ], - "bbox": 1828, + "bbox": 1832, "dccaspects": [ [ [ @@ -21765,7 +21765,7 @@ false, "" ], - "bbox": 1845, + "bbox": 1849, "dccaspects": [ [ [ @@ -22635,7 +22635,7 @@ false, "" ], - "bbox": 1862, + "bbox": 1866, "dccaspects": [ [ [ @@ -23505,7 +23505,7 @@ false, "" ], - "bbox": 1880, + "bbox": 1884, "dccaspects": [ [ [ @@ -24375,7 +24375,7 @@ false, "" ], - "bbox": 1897, + "bbox": 1901, "dccaspects": [ [ [ @@ -25245,7 +25245,7 @@ false, "" ], - "bbox": 1908, + "bbox": 1912, "dccaspects": [ [ [ @@ -26115,7 +26115,7 @@ false, "" ], - "bbox": 1925, + "bbox": 1929, "dccaspects": [ [ [ @@ -26985,7 +26985,7 @@ false, "" ], - "bbox": 1936, + "bbox": 1940, "dccaspects": [ [ [ @@ -27855,7 +27855,7 @@ false, "" ], - "bbox": 1953, + "bbox": 1957, "dccaspects": [ [ [ @@ -28725,7 +28725,7 @@ false, "" ], - "bbox": 1964, + "bbox": 1968, "dccaspects": [ [ [ @@ -29595,7 +29595,7 @@ false, "" ], - "bbox": 1975, + "bbox": 1979, "dccaspects": [ [ [ @@ -30465,7 +30465,7 @@ false, "" ], - "bbox": 1992, + "bbox": 1996, "dccaspects": [ [ [ @@ -31335,7 +31335,7 @@ false, "" ], - "bbox": 2011, + "bbox": 2015, "dccaspects": [ [ [ @@ -32205,7 +32205,7 @@ false, "" ], - "bbox": 2029, + "bbox": 2033, "dccaspects": [ [ [ @@ -33075,7 +33075,7 @@ false, "" ], - "bbox": 2046, + "bbox": 2050, "dccaspects": [ [ [ @@ -33945,7 +33945,7 @@ false, "" ], - "bbox": 2063, + "bbox": 2067, "dccaspects": [ [ [ @@ -34815,7 +34815,7 @@ false, "" ], - "bbox": 2080, + "bbox": 2084, "dccaspects": [ [ [ @@ -35685,7 +35685,7 @@ false, "" ], - "bbox": 2099, + "bbox": 2103, "dccaspects": [ [ [ @@ -36555,7 +36555,7 @@ false, "" ], - "bbox": 2110, + "bbox": 2114, "dccaspects": [ [ [ @@ -37425,7 +37425,7 @@ false, "" ], - "bbox": 2127, + "bbox": 2131, "dccaspects": [ [ [ @@ -38295,7 +38295,7 @@ false, "" ], - "bbox": 2144, + "bbox": 2148, "dccaspects": [ [ [ @@ -39165,7 +39165,7 @@ false, "" ], - "bbox": 2161, + "bbox": 2165, "dccaspects": [ [ [ @@ -40035,7 +40035,7 @@ false, "" ], - "bbox": 2178, + "bbox": 2182, "dccaspects": [ [ [ @@ -40905,7 +40905,7 @@ false, "" ], - "bbox": 2196, + "bbox": 2200, "dccaspects": [ [ [ @@ -41775,7 +41775,7 @@ false, "" ], - "bbox": 2213, + "bbox": 2217, "dccaspects": [ [ [ @@ -42645,7 +42645,7 @@ false, "" ], - "bbox": 2233, + "bbox": 2237, "dccaspects": [ [ [ @@ -43515,7 +43515,7 @@ false, "" ], - "bbox": 2250, + "bbox": 2254, "dccaspects": [ [ [ @@ -44385,7 +44385,7 @@ false, "" ], - "bbox": 2267, + "bbox": 2271, "dccaspects": [ [ [ @@ -45255,7 +45255,7 @@ false, "" ], - "bbox": 2284, + "bbox": 2288, "dccaspects": [ [ [ @@ -46125,7 +46125,7 @@ false, "" ], - "bbox": 2301, + "bbox": 2305, "dccaspects": [ [ [ @@ -46995,7 +46995,7 @@ false, "" ], - "bbox": 2318, + "bbox": 2322, "dccaspects": [ [ [ @@ -47865,7 +47865,7 @@ false, "" ], - "bbox": 2335, + "bbox": 2339, "dccaspects": [ [ [ @@ -48735,7 +48735,7 @@ false, "" ], - "bbox": 2352, + "bbox": 2356, "dccaspects": [ [ [ @@ -49605,7 +49605,7 @@ false, "" ], - "bbox": 2369, + "bbox": 2373, "dccaspects": [ [ [ @@ -50475,7 +50475,7 @@ false, "" ], - "bbox": 2386, + "bbox": 2390, "dccaspects": [ [ [ @@ -51345,7 +51345,7 @@ false, "" ], - "bbox": 2403, + "bbox": 2407, "dccaspects": [ [ [ @@ -52215,11 +52215,12 @@ }, "general": { "automation": true, + "buttonsize": 8, "editmode": true, "filename": "/home/john/model-railway-signalling/configuration_examples/colour_light_signals.sig", "info": "Document your layout here", "spadpopups": false, - "version": "Version 4.6.1" + "version": "Version 4.7.0" }, "gpio": { "portmappings": [], diff --git a/configuration_examples/complex_trackwork.sig b/configuration_examples/complex_trackwork.sig index 9c81212a..51f85d22 100644 --- a/configuration_examples/complex_trackwork.sig +++ b/configuration_examples/complex_trackwork.sig @@ -71,7 +71,8 @@ "instruments": {}, "objects": { "012e8acd-9407-4338-971e-12d19f98ab9e": { - "bbox": 2492, + "bbox": 2496, + "buttoncolour": "SeaGreen3", "buttonwidth": 15, "item": "route", "itemid": 7, @@ -94,6 +95,7 @@ "routecolour": "#0000ff", "routedescription": "Route description (Run Mode tooltip)", "routename": "Route 3", + "setupsensor": 0, "signalsonroute": [], "subsidariesonroute": [], "switchdelay": 0, @@ -107,7 +109,7 @@ 0, 0 ], - "bbox": 2498, + "bbox": 2502, "colour": "black", "endx": 100.0, "endy": 300.0, @@ -125,7 +127,7 @@ 0, 0 ], - "bbox": 2504, + "bbox": 2508, "colour": "black", "endx": 900.0, "endy": 125.0, @@ -143,7 +145,7 @@ 0, 0 ], - "bbox": 2510, + "bbox": 2514, "colour": "black", "endx": 800.0, "endy": 350.0, @@ -161,7 +163,7 @@ 0, 0 ], - "bbox": 2516, + "bbox": 2520, "colour": "black", "endx": 50.0, "endy": 500.0, @@ -173,7 +175,8 @@ "tags": "line65" }, "08bd7aa3-6172-4418-b4fb-c5f8d13a3a63": { - "bbox": 2520, + "bbox": 2524, + "buttoncolour": "SeaGreen3", "buttonwidth": 15, "item": "route", "itemid": 13, @@ -198,6 +201,7 @@ "routecolour": "#0000ff", "routedescription": "Route description (Run Mode tooltip)", "routename": "Route 2", + "setupsensor": 0, "signalsonroute": [], "subsidariesonroute": [], "switchdelay": 0, @@ -211,7 +215,7 @@ 0, 0 ], - "bbox": 2526, + "bbox": 2530, "colour": "black", "endx": 525.0, "endy": 300.0, @@ -229,7 +233,7 @@ 0, 0 ], - "bbox": 2532, + "bbox": 2536, "colour": "black", "endx": 825.0, "endy": 300.0, @@ -247,7 +251,7 @@ 0, 0 ], - "bbox": 2538, + "bbox": 2542, "colour": "black", "endx": 1250.0, "endy": 125.0, @@ -265,7 +269,7 @@ 0, 0 ], - "bbox": 2544, + "bbox": 2548, "colour": "black", "endx": 225.0, "endy": 75.0, @@ -283,7 +287,7 @@ 0, 0 ], - "bbox": 2550, + "bbox": 2554, "colour": "black", "endx": 200.0, "endy": 300.0, @@ -295,7 +299,8 @@ "tags": "line26" }, "0fea6334-0b93-4fcd-bcbc-c580a6c2cc8e": { - "bbox": 2554, + "bbox": 2558, + "buttoncolour": "SeaGreen3", "buttonwidth": 15, "item": "route", "itemid": 4, @@ -320,6 +325,7 @@ "routecolour": "#0000ff", "routedescription": "Route description (Run Mode tooltip)", "routename": "Route 6", + "setupsensor": 0, "signalsonroute": [], "subsidariesonroute": [], "switchdelay": 0, @@ -333,7 +339,7 @@ 0, 0 ], - "bbox": 2560, + "bbox": 2564, "colour": "black", "endx": 1250.0, "endy": 75.0, @@ -351,7 +357,7 @@ 0, 0 ], - "bbox": 2566, + "bbox": 2570, "colour": "black", "endx": 150.0, "endy": 500.0, @@ -365,7 +371,7 @@ "131f1c29-65f4-4af6-8420-271462af5623": { "alsoswitch": 0, "automatic": false, - "bbox": 2573, + "bbox": 2577, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -384,7 +390,8 @@ "ybuttonoffset": 0 }, "13c86fba-96d2-4c67-94bd-f91a8a5dcd96": { - "bbox": 2577, + "bbox": 2581, + "buttoncolour": "SeaGreen3", "buttonwidth": 15, "item": "route", "itemid": 8, @@ -407,6 +414,7 @@ "routecolour": "#0000ff", "routedescription": "Route description (Run Mode tooltip)", "routename": "Route 2", + "setupsensor": 0, "signalsonroute": [], "subsidariesonroute": [], "switchdelay": 0, @@ -420,7 +428,7 @@ 0, 0 ], - "bbox": 2583, + "bbox": 2587, "colour": "black", "endx": 825.0, "endy": 350.0, @@ -434,7 +442,7 @@ "16f6d440-2829-401f-916d-24f2a8fd74d8": { "alsoswitch": 0, "automatic": false, - "bbox": 2591, + "bbox": 2595, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -453,7 +461,8 @@ "ybuttonoffset": 0 }, "18878158-2b7c-4982-9e44-834de20f824b": { - "bbox": 2595, + "bbox": 2599, + "buttoncolour": "SeaGreen3", "buttonwidth": 15, "item": "route", "itemid": 21, @@ -483,6 +492,7 @@ "routecolour": "#0000ff", "routedescription": "Route description (Run Mode tooltip)", "routename": "Route 3", + "setupsensor": 0, "signalsonroute": [], "subsidariesonroute": [], "switchdelay": 0, @@ -492,7 +502,7 @@ "18e60c3e-4c95-4c3d-98a8-54c5c016a005": { "alsoswitch": 32, "automatic": false, - "bbox": 2602, + "bbox": 2606, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -512,18 +522,19 @@ }, "1b80533d-389d-4d7a-92b3-0dc7d58abc45": { "background": "grey85", - "bbox": 2605, + "bbox": 2609, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "bold ", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 1, "justify": 2, "posx": 200.0, "posy": 425.0, - "tags": "1b80533d-389d-4d7a-92b3-0dc7d58abc45", + "tags": "textbox1", "text": "Left Hand Double Slip points" }, "1dd4b981-d3ed-4283-91e3-7790a443d478": { @@ -533,7 +544,7 @@ 0, 0 ], - "bbox": 2611, + "bbox": 2615, "colour": "black", "endx": 175.0, "endy": 450.0, @@ -547,7 +558,7 @@ "1f215078-299d-4950-903b-9a76228e19a8": { "alsoswitch": 0, "automatic": false, - "bbox": 2618, + "bbox": 2622, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -572,7 +583,7 @@ 0, 0 ], - "bbox": 2624, + "bbox": 2628, "colour": "black", "endx": 500.0, "endy": 250.0, @@ -586,7 +597,7 @@ "21bd347b-1210-4a91-802f-8be0c711650a": { "alsoswitch": 0, "automatic": false, - "bbox": 2631, + "bbox": 2635, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -607,7 +618,7 @@ "235c4b99-057e-4029-aea5-b01b599fcccb": { "alsoswitch": 0, "automatic": false, - "bbox": 2639, + "bbox": 2643, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -627,34 +638,36 @@ }, "247cf23e-6255-425e-8fc9-4117033c362a": { "background": "grey85", - "bbox": 2642, + "bbox": 2646, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "bold ", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 2, "justify": 2, "posx": 1200.0, "posy": 300.0, - "tags": "247cf23e-6255-425e-8fc9-4117033c362a", + "tags": "textbox2", "text": "'Trap' Points (LH and RH)" }, "273d3962-26c9-4909-b389-7131962b5a57": { "background": "grey85", - "bbox": 2645, + "bbox": 2649, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "bold ", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 3, "justify": 2, "posx": 550.0, "posy": 425.0, - "tags": "273d3962-26c9-4909-b389-7131962b5a57", + "tags": "textbox3", "text": "Right Hand Double Slip points" }, "274d5608-1dfa-40a0-b7dd-002a7244d7db": { @@ -664,7 +677,7 @@ 0, 0 ], - "bbox": 2651, + "bbox": 2655, "colour": "black", "endx": 700.0, "endy": 125.0, @@ -682,7 +695,7 @@ 0, 0 ], - "bbox": 2657, + "bbox": 2661, "colour": "black", "endx": 1100.0, "endy": 125.0, @@ -695,24 +708,25 @@ }, "29137e31-f6b7-4819-87da-ba4508d19a77": { "background": "grey85", - "bbox": 2660, + "bbox": 2664, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "bold ", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 4, "justify": 2, "posx": 650.0, "posy": 225.0, - "tags": "29137e31-f6b7-4819-87da-ba4508d19a77", + "tags": "textbox4", "text": "Right Hand Single Slip points" }, "292247f0-8cc8-4dd2-b45b-0ef0c26e3e33": { "alsoswitch": 0, "automatic": true, - "bbox": 2665, + "bbox": 2669, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -733,7 +747,7 @@ "2b8bde9c-7637-4299-a205-31ad6a46e75d": { "alsoswitch": 0, "automatic": false, - "bbox": 2675, + "bbox": 2679, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -758,7 +772,7 @@ 0, 0 ], - "bbox": 2681, + "bbox": 2685, "colour": "black", "endx": 75.0, "endy": 550.0, @@ -770,7 +784,8 @@ "tags": "line55" }, "2e3d2b1b-b596-4be4-a7fc-d5ef8aaf2cde": { - "bbox": 2685, + "bbox": 2689, + "buttoncolour": "SeaGreen3", "buttonwidth": 15, "item": "route", "itemid": 17, @@ -810,6 +825,7 @@ "routecolour": "#0000ff", "routedescription": "Route description (Run Mode tooltip)", "routename": "Route 1", + "setupsensor": 0, "signalsonroute": [], "subsidariesonroute": [], "switchdelay": 0, @@ -823,7 +839,7 @@ 0, 0 ], - "bbox": 2691, + "bbox": 2695, "colour": "black", "endx": 1150.0, "endy": 400.0, @@ -836,18 +852,19 @@ }, "32df5739-31c0-4811-a751-b3995f862dc5": { "background": "grey85", - "bbox": 2694, + "bbox": 2698, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 5, "justify": 2, "posx": 100.0, "posy": 375.0, - "tags": "32df5739-31c0-4811-a751-b3995f862dc5", + "tags": "textbox5", "text": "(Normal)" }, "34f16a7d-4eea-43eb-a8f5-562fa034d892": { @@ -857,7 +874,7 @@ 0, 0 ], - "bbox": 2700, + "bbox": 2704, "colour": "black", "endx": 225.0, "endy": 125.0, @@ -871,7 +888,7 @@ "35b02f65-7a77-4fea-9c09-a8385b0595e4": { "alsoswitch": 0, "automatic": true, - "bbox": 2705, + "bbox": 2709, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -896,7 +913,7 @@ 0, 0 ], - "bbox": 2711, + "bbox": 2715, "colour": "black", "endx": 1325.0, "endy": 525.0, @@ -914,7 +931,7 @@ 0, 0 ], - "bbox": 2717, + "bbox": 2721, "colour": "black", "endx": 500.0, "endy": 500.0, @@ -932,7 +949,7 @@ 0, 0 ], - "bbox": 2723, + "bbox": 2727, "colour": "black", "endx": 1200.0, "endy": 400.0, @@ -950,7 +967,7 @@ 0, 0 ], - "bbox": 2729, + "bbox": 2733, "colour": "black", "endx": 500.0, "endy": 250.0, @@ -968,7 +985,7 @@ 0, 0 ], - "bbox": 2735, + "bbox": 2739, "colour": "black", "endx": 725.0, "endy": 275.0, @@ -982,7 +999,7 @@ "41bd2ee1-f703-4536-8d60-8bfa38374130": { "alsoswitch": 26, "automatic": false, - "bbox": 2742, + "bbox": 2746, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -1007,7 +1024,7 @@ 0, 0 ], - "bbox": 2748, + "bbox": 2752, "colour": "black", "endx": 200.0, "endy": 250.0, @@ -1021,7 +1038,7 @@ "49fca724-fda3-44fa-b8e5-800443a78304": { "alsoswitch": 0, "automatic": false, - "bbox": 2756, + "bbox": 2760, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -1041,24 +1058,25 @@ }, "533ace8f-9229-475c-afc8-eb59c7ac9eb0": { "background": "grey85", - "bbox": 2759, + "bbox": 2763, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "bold ", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 6, "justify": 2, "posx": 150.0, "posy": 25.0, - "tags": "533ace8f-9229-475c-afc8-eb59c7ac9eb0", + "tags": "textbox6", "text": "Normal Points (LH and RH)" }, "535aef80-e3b7-4492-af6e-7831d8a5d1f9": { "alsoswitch": 0, "automatic": false, - "bbox": 2769, + "bbox": 2773, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -1083,7 +1101,7 @@ 0, 0 ], - "bbox": 2775, + "bbox": 2779, "colour": "black", "endx": 525.0, "endy": 550.0, @@ -1096,24 +1114,25 @@ }, "550bf579-44d3-4835-8ccf-3c0d4da10845": { "background": "grey85", - "bbox": 2778, + "bbox": 2782, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 7, "justify": 2, "posx": 475.0, "posy": 550.0, - "tags": "550bf579-44d3-4835-8ccf-3c0d4da10845", + "tags": "textbox7", "text": "(Normal)" }, "58bee3f2-f694-4f91-a93c-2d7ced3e6842": { "alsoswitch": 0, "automatic": false, - "bbox": 2785, + "bbox": 2789, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -1132,7 +1151,8 @@ "ybuttonoffset": 0 }, "59867085-8526-4cc9-ae06-77aa3e18e96c": { - "bbox": 2789, + "bbox": 2793, + "buttoncolour": "SeaGreen3", "buttonwidth": 15, "item": "route", "itemid": 3, @@ -1157,6 +1177,7 @@ "routecolour": "#0000ff", "routedescription": "Route description (Run Mode tooltip)", "routename": "Route 3", + "setupsensor": 0, "signalsonroute": [], "subsidariesonroute": [], "switchdelay": 0, @@ -1170,7 +1191,7 @@ 0, 0 ], - "bbox": 2795, + "bbox": 2799, "colour": "black", "endx": 300.0, "endy": 300.0, @@ -1182,7 +1203,8 @@ "tags": "line36" }, "5d2fcb58-4cc5-4ae3-9e2e-934713504a80": { - "bbox": 2799, + "bbox": 2803, + "buttoncolour": "SeaGreen3", "buttonwidth": 15, "item": "route", "itemid": 12, @@ -1205,6 +1227,7 @@ "routecolour": "#0000ff", "routedescription": "Route description (Run Mode tooltip)", "routename": "Route 3", + "setupsensor": 0, "signalsonroute": [], "subsidariesonroute": [], "switchdelay": 0, @@ -1212,7 +1235,8 @@ "tracksensor": 0 }, "5f1c6afa-1c7b-4fdb-9a95-b536e5c267b4": { - "bbox": 2803, + "bbox": 2807, + "buttoncolour": "SeaGreen3", "buttonwidth": 15, "item": "route", "itemid": 14, @@ -1235,6 +1259,7 @@ "routecolour": "#0000ff", "routedescription": "Route description (Run Mode tooltip)", "routename": "Route 1", + "setupsensor": 0, "signalsonroute": [], "subsidariesonroute": [], "switchdelay": 0, @@ -1244,7 +1269,7 @@ "5f52c741-4bf4-4517-a384-8864c71a82dc": { "alsoswitch": 0, "automatic": false, - "bbox": 2810, + "bbox": 2814, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -1264,24 +1289,25 @@ }, "614b0d05-91fd-4146-981f-e11e9cd47d1b": { "background": "grey85", - "bbox": 2813, + "bbox": 2817, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 8, "justify": 2, "posx": 550.0, "posy": 350.0, - "tags": "614b0d05-91fd-4146-981f-e11e9cd47d1b", + "tags": "textbox8", "text": "(Normal)" }, "62651d8c-eedf-452b-828a-83ea894559ad": { "alsoswitch": 0, "automatic": false, - "bbox": 2820, + "bbox": 2824, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -1302,7 +1328,7 @@ "63610068-f6e1-48d1-afbd-de1e97a2c3d8": { "alsoswitch": 0, "automatic": false, - "bbox": 2830, + "bbox": 2834, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -1327,7 +1353,7 @@ 0, 0 ], - "bbox": 2836, + "bbox": 2840, "colour": "black", "endx": 1250.0, "endy": 525.0, @@ -1345,7 +1371,7 @@ 0, 0 ], - "bbox": 2842, + "bbox": 2846, "colour": "black", "endx": 150.0, "endy": 125.0, @@ -1359,7 +1385,7 @@ "67811741-acf1-4677-b3cd-b2554e7a1419": { "alsoswitch": 0, "automatic": false, - "bbox": 2852, + "bbox": 2856, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -1384,7 +1410,7 @@ 0, 0 ], - "bbox": 2858, + "bbox": 2862, "colour": "black", "endx": 175.0, "endy": 250.0, @@ -1398,7 +1424,7 @@ "6cade9d1-7352-4c04-a373-b88315a99a08": { "alsoswitch": 0, "automatic": false, - "bbox": 2868, + "bbox": 2872, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -1419,7 +1445,7 @@ "6f16ec1f-d3a8-4cdf-9964-5e6d9ac67e8c": { "alsoswitch": 0, "automatic": false, - "bbox": 2875, + "bbox": 2879, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -1440,7 +1466,7 @@ "6f17c5e9-5121-4a3b-8daf-ca175d7763df": { "alsoswitch": 36, "automatic": false, - "bbox": 2882, + "bbox": 2886, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -1465,7 +1491,7 @@ 0, 0 ], - "bbox": 2888, + "bbox": 2892, "colour": "black", "endx": 1200.0, "endy": 350.0, @@ -1483,7 +1509,7 @@ 0, 0 ], - "bbox": 2894, + "bbox": 2898, "colour": "black", "endx": 625.0, "endy": 300.0, @@ -1496,24 +1522,25 @@ }, "71bab62a-8ea2-42bd-ae29-f0f5c6a99627": { "background": "grey85", - "bbox": 2897, + "bbox": 2901, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 9, "justify": 2, "posx": 775.0, "posy": 175.0, - "tags": "71bab62a-8ea2-42bd-ae29-f0f5c6a99627", + "tags": "textbox9", "text": "(With Button Position X offsets)" }, "75fd19ff-237d-4415-9fa0-fcb1c96f936b": { "alsoswitch": 0, "automatic": false, - "bbox": 2907, + "bbox": 2911, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -1538,7 +1565,7 @@ 0, 0 ], - "bbox": 2913, + "bbox": 2917, "colour": "black", "endx": 1175.0, "endy": 100.0, @@ -1556,7 +1583,7 @@ 0, 0 ], - "bbox": 2919, + "bbox": 2923, "colour": "black", "endx": 375.0, "endy": 250.0, @@ -1574,7 +1601,7 @@ 0, 0 ], - "bbox": 2925, + "bbox": 2929, "colour": "black", "endx": 400.0, "endy": 250.0, @@ -1586,7 +1613,8 @@ "tags": "line38" }, "813448e4-f519-44ee-9bb3-d805867459a0": { - "bbox": 2929, + "bbox": 2933, + "buttoncolour": "SeaGreen3", "buttonwidth": 15, "item": "route", "itemid": 2, @@ -1612,6 +1640,7 @@ "routecolour": "#0000ff", "routedescription": "Route description (Run Mode tooltip)", "routename": "Route 2", + "setupsensor": 0, "signalsonroute": [], "subsidariesonroute": [], "switchdelay": 0, @@ -1620,24 +1649,25 @@ }, "864b3e09-1ea8-4b12-90ee-48379e0b2788": { "background": "grey85", - "bbox": 2932, + "bbox": 2936, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 10, "justify": 2, "posx": 1200.0, "posy": 575.0, - "tags": "864b3e09-1ea8-4b12-90ee-48379e0b2788", + "tags": "textbox10", "text": "(With Button Position X offsets)" }, "86672a6f-f09b-4d1d-bef5-ac6624fd5c0a": { "alsoswitch": 0, "automatic": false, - "bbox": 2939, + "bbox": 2943, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -1662,7 +1692,7 @@ 0, 0 ], - "bbox": 2945, + "bbox": 2949, "colour": "black", "endx": 275.0, "endy": 350.0, @@ -1680,7 +1710,7 @@ 0, 0 ], - "bbox": 2951, + "bbox": 2955, "colour": "black", "endx": 200.0, "endy": 450.0, @@ -1692,7 +1722,8 @@ "tags": "line56" }, "885ae7a8-0963-4d2d-a0be-49442e59aafc": { - "bbox": 2955, + "bbox": 2959, + "buttoncolour": "SeaGreen3", "buttonwidth": 15, "item": "route", "itemid": 9, @@ -1715,6 +1746,7 @@ "routecolour": "#0000ff", "routedescription": "Route description (Run Mode tooltip)", "routename": "Route 1", + "setupsensor": 0, "signalsonroute": [], "subsidariesonroute": [], "switchdelay": 0, @@ -1728,7 +1760,7 @@ 0, 0 ], - "bbox": 2961, + "bbox": 2965, "colour": "black", "endx": 700.0, "endy": 250.0, @@ -1746,7 +1778,7 @@ 0, 0 ], - "bbox": 2967, + "bbox": 2971, "colour": "black", "endx": 50.0, "endy": 125.0, @@ -1764,7 +1796,7 @@ 0, 0 ], - "bbox": 2973, + "bbox": 2977, "colour": "black", "endx": 1250.0, "endy": 400.0, @@ -1778,7 +1810,7 @@ "8f1bd03a-ce27-48ed-b500-fa59b0a4d69d": { "alsoswitch": 28, "automatic": false, - "bbox": 2980, + "bbox": 2984, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -1803,7 +1835,7 @@ 0, 0 ], - "bbox": 2986, + "bbox": 2990, "colour": "black", "endx": 825.0, "endy": 75.0, @@ -1815,7 +1847,8 @@ "tags": "line15" }, "94071e83-94c1-4ea4-887c-e5379fba006f": { - "bbox": 2990, + "bbox": 2994, + "buttoncolour": "SeaGreen3", "buttonwidth": 15, "item": "route", "itemid": 1, @@ -1842,6 +1875,7 @@ "routecolour": "#0000ff", "routedescription": "Route description (Run Mode tooltip)", "routename": "Route 1", + "setupsensor": 0, "signalsonroute": [], "subsidariesonroute": [], "switchdelay": 0, @@ -1851,7 +1885,7 @@ "9974d0bb-2f63-40d6-a8bc-4863b4b0ccdc": { "alsoswitch": 0, "automatic": false, - "bbox": 2998, + "bbox": 3002, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -1872,7 +1906,7 @@ "9b856101-0edb-42db-a000-c556bb804dc0": { "alsoswitch": 0, "automatic": false, - "bbox": 3007, + "bbox": 3011, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -1893,7 +1927,7 @@ "9fd94ff2-5098-4cb6-9665-f019caa4a34d": { "alsoswitch": 0, "automatic": false, - "bbox": 3016, + "bbox": 3020, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -1914,7 +1948,7 @@ "a125c411-cd2a-45bc-8817-b3cc5add29f9": { "alsoswitch": 0, "automatic": false, - "bbox": 3025, + "bbox": 3029, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -1934,22 +1968,24 @@ }, "a1669f45-8007-4d80-a489-4a8a74b01b6e": { "background": "grey85", - "bbox": 3028, + "bbox": 3032, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "bold ", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 11, "justify": 2, "posx": 175.0, "posy": 225.0, - "tags": "a1669f45-8007-4d80-a489-4a8a74b01b6e", + "tags": "textbox11", "text": "Left Hand Single Slip points" }, "a1fc37d8-6fe2-4cf8-8e01-e5afe52c857d": { - "bbox": 3032, + "bbox": 3036, + "buttoncolour": "SeaGreen3", "buttonwidth": 15, "item": "route", "itemid": 20, @@ -1977,6 +2013,7 @@ "routecolour": "#0000ff", "routedescription": "Route description (Run Mode tooltip)", "routename": "Route 1", + "setupsensor": 0, "signalsonroute": [], "subsidariesonroute": [], "switchdelay": 0, @@ -1986,7 +2023,7 @@ "a289091f-866d-40be-a580-743c9799124d": { "alsoswitch": 0, "automatic": false, - "bbox": 3039, + "bbox": 3043, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -2006,18 +2043,19 @@ }, "a3051485-e8d8-47c0-ab4d-e555913fe104": { "background": "grey85", - "bbox": 3042, + "bbox": 3046, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 12, "justify": 2, "posx": 750.0, "posy": 350.0, - "tags": "a3051485-e8d8-47c0-ab4d-e555913fe104", + "tags": "textbox12", "text": "(Rotated)" }, "aa973b01-eb54-483e-8ed4-ef95ea7406b5": { @@ -2027,7 +2065,7 @@ 0, 0 ], - "bbox": 3048, + "bbox": 3052, "colour": "black", "endx": 1250.0, "endy": 350.0, @@ -2039,7 +2077,8 @@ "tags": "line35" }, "ac34e474-3ca8-449f-b037-9348d2b32ea2": { - "bbox": 3052, + "bbox": 3056, + "buttoncolour": "SeaGreen3", "buttonwidth": 15, "item": "route", "itemid": 19, @@ -2083,6 +2122,7 @@ "routecolour": "#0000ff", "routedescription": "Route description (Run Mode tooltip)", "routename": "Route 3", + "setupsensor": 0, "signalsonroute": [], "subsidariesonroute": [], "switchdelay": 0, @@ -2090,7 +2130,8 @@ "tracksensor": 0 }, "af5f77e2-11d7-4a63-b5d4-f089af00db2f": { - "bbox": 3056, + "bbox": 3060, + "buttoncolour": "SeaGreen3", "buttonwidth": 15, "item": "route", "itemid": 16, @@ -2115,6 +2156,7 @@ "routecolour": "#0000ff", "routedescription": "Route description (Run Mode tooltip)", "routename": "Route 4", + "setupsensor": 0, "signalsonroute": [], "subsidariesonroute": [], "switchdelay": 0, @@ -2124,7 +2166,7 @@ "b23e8471-c6a4-469c-8181-712d884a1d47": { "alsoswitch": 0, "automatic": false, - "bbox": 3064, + "bbox": 3068, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -2143,7 +2185,8 @@ "ybuttonoffset": 0 }, "b263fa34-c17b-411c-9356-5f42a7ff6af0": { - "bbox": 3068, + "bbox": 3072, + "buttoncolour": "SeaGreen3", "buttonwidth": 15, "item": "route", "itemid": 23, @@ -2173,6 +2216,7 @@ "routecolour": "#0000ff", "routedescription": "Route description (Run Mode tooltip)", "routename": "Route 4", + "setupsensor": 0, "signalsonroute": [], "subsidariesonroute": [], "switchdelay": 0, @@ -2182,7 +2226,7 @@ "b2f6dadf-506c-410f-8641-2028db0b41bb": { "alsoswitch": 0, "automatic": false, - "bbox": 3075, + "bbox": 3079, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -2203,7 +2247,7 @@ "b49bb4b9-eeed-4e02-a98b-3cf310a9177b": { "alsoswitch": 0, "automatic": true, - "bbox": 3080, + "bbox": 3084, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -2224,7 +2268,7 @@ "b4e40068-28a1-4058-86b1-4ed2c35c7a47": { "alsoswitch": 0, "automatic": true, - "bbox": 3085, + "bbox": 3089, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -2244,22 +2288,24 @@ }, "b4f56c10-201d-4e9b-894b-627d0d553070": { "background": "grey85", - "bbox": 3088, + "bbox": 3092, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "bold ", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 13, "justify": 2, "posx": 775.0, "posy": 25.0, - "tags": "b4f56c10-201d-4e9b-894b-627d0d553070", + "tags": "textbox13", "text": "Scissors Crossovers\n(using 'SX' Points)" }, "b7974a1e-49e9-4722-83ef-63b4683b0f9d": { - "bbox": 3092, + "bbox": 3096, + "buttoncolour": "SeaGreen3", "buttonwidth": 15, "item": "route", "itemid": 15, @@ -2286,6 +2332,7 @@ "routecolour": "#0000ff", "routedescription": "Route description (Run Mode tooltip)", "routename": "Route 5", + "setupsensor": 0, "signalsonroute": [], "subsidariesonroute": [], "switchdelay": 0, @@ -2299,7 +2346,7 @@ 0, 0 ], - "bbox": 3098, + "bbox": 3102, "colour": "black", "endx": 400.0, "endy": 500.0, @@ -2313,7 +2360,7 @@ "b9f1c77f-0226-4b52-81fd-fd3d2c5e5c78": { "alsoswitch": 0, "automatic": false, - "bbox": 3105, + "bbox": 3109, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -2334,7 +2381,7 @@ "bcf3e20c-4a84-41b7-9834-ad235f4410ea": { "alsoswitch": 0, "automatic": false, - "bbox": 3112, + "bbox": 3116, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -2359,7 +2406,7 @@ 0, 0 ], - "bbox": 3118, + "bbox": 3122, "colour": "black", "endx": 50.0, "endy": 75.0, @@ -2377,7 +2424,7 @@ 0, 0 ], - "bbox": 3124, + "bbox": 3128, "colour": "black", "endx": 900.0, "endy": 75.0, @@ -2395,7 +2442,7 @@ 0, 0 ], - "bbox": 3130, + "bbox": 3134, "colour": "black", "endx": 400.0, "endy": 450.0, @@ -2413,7 +2460,7 @@ 0, 0 ], - "bbox": 3136, + "bbox": 3140, "colour": "black", "endx": 150.0, "endy": 75.0, @@ -2426,22 +2473,24 @@ }, "cc28c313-5047-4d05-8340-8e7586b666c6": { "background": "grey85", - "bbox": 3139, + "bbox": 3143, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 14, "justify": 2, "posx": 125.0, "posy": 550.0, - "tags": "cc28c313-5047-4d05-8340-8e7586b666c6", + "tags": "textbox14", "text": "(Normal)" }, "cd265dde-6c1d-4fe6-b428-179f2906c3e4": { - "bbox": 3143, + "bbox": 3147, + "buttoncolour": "SeaGreen3", "buttonwidth": 15, "item": "route", "itemid": 5, @@ -2467,6 +2516,7 @@ "routecolour": "#0000ff", "routedescription": "Route description (Run Mode tooltip)", "routename": "Route 5", + "setupsensor": 0, "signalsonroute": [], "subsidariesonroute": [], "switchdelay": 0, @@ -2480,7 +2530,7 @@ 0, 0 ], - "bbox": 3149, + "bbox": 3153, "colour": "black", "endx": 1100.0, "endy": 75.0, @@ -2493,18 +2543,19 @@ }, "d3e48098-3f76-476b-a6b7-4021f7bd5637": { "background": "grey85", - "bbox": 3152, + "bbox": 3156, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 15, "justify": 2, "posx": 625.0, "posy": 550.0, - "tags": "d3e48098-3f76-476b-a6b7-4021f7bd5637", + "tags": "textbox15", "text": "(Rotated)" }, "d7828202-7880-4033-8f20-65ca0c9620b5": { @@ -2514,7 +2565,7 @@ 0, 0 ], - "bbox": 3158, + "bbox": 3162, "colour": "black", "endx": 425.0, "endy": 450.0, @@ -2532,7 +2583,7 @@ 0, 0 ], - "bbox": 3164, + "bbox": 3168, "colour": "black", "endx": 700.0, "endy": 75.0, @@ -2546,7 +2597,7 @@ "dae197b5-8a12-414d-b12f-5164d0a6a21d": { "alsoswitch": 0, "automatic": false, - "bbox": 3171, + "bbox": 3175, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -2571,7 +2622,7 @@ 0, 0 ], - "bbox": 3177, + "bbox": 3181, "colour": "black", "endx": 1175.0, "endy": 525.0, @@ -2585,7 +2636,7 @@ "e0148185-4fe9-4742-a5f8-0a98346691b6": { "alsoswitch": 0, "automatic": false, - "bbox": 3186, + "bbox": 3190, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -2605,18 +2656,19 @@ }, "e06f4afb-ebc7-4351-ba08-a417f1002da1": { "background": "grey85", - "bbox": 3189, + "bbox": 3193, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 16, "justify": 2, "posx": 275.0, "posy": 550.0, - "tags": "e06f4afb-ebc7-4351-ba08-a417f1002da1", + "tags": "textbox16", "text": "(Rotated)" }, "e19c0b1c-fe71-42e6-ac22-ad8fa216d45f": { @@ -2626,7 +2678,7 @@ 0, 0 ], - "bbox": 3195, + "bbox": 3199, "colour": "black", "endx": 575.0, "endy": 325.0, @@ -2638,7 +2690,8 @@ "tags": "line45" }, "e1fc346f-efb4-41c2-977f-609ebd4f90a1": { - "bbox": 3199, + "bbox": 3203, + "buttoncolour": "SeaGreen3", "buttonwidth": 15, "item": "route", "itemid": 10, @@ -2661,6 +2714,7 @@ "routecolour": "#0000ff", "routedescription": "Route description (Run Mode tooltip)", "routename": "Route 4", + "setupsensor": 0, "signalsonroute": [], "subsidariesonroute": [], "switchdelay": 0, @@ -2668,7 +2722,8 @@ "tracksensor": 0 }, "e3620645-ce1d-43f9-a00c-d325986c48e2": { - "bbox": 3203, + "bbox": 3207, + "buttoncolour": "SeaGreen3", "buttonwidth": 15, "item": "route", "itemid": 22, @@ -2700,6 +2755,7 @@ "routecolour": "#0000ff", "routedescription": "Route description (Run Mode tooltip)", "routename": "Route 2", + "setupsensor": 0, "signalsonroute": [], "subsidariesonroute": [], "switchdelay": 0, @@ -2707,7 +2763,8 @@ "tracksensor": 0 }, "e49bd002-0b47-4cd1-a6d8-77dcb30ecdb9": { - "bbox": 3207, + "bbox": 3211, + "buttoncolour": "SeaGreen3", "buttonwidth": 15, "item": "route", "itemid": 6, @@ -2734,6 +2791,7 @@ "routecolour": "#0000ff", "routedescription": "Route description (Run Mode tooltip)", "routename": "Route 4", + "setupsensor": 0, "signalsonroute": [], "subsidariesonroute": [], "switchdelay": 0, @@ -2743,7 +2801,7 @@ "e5435df4-5e78-4286-b779-f25844c24e52": { "alsoswitch": 0, "automatic": false, - "bbox": 3217, + "bbox": 3221, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -2768,7 +2826,7 @@ 0, 0 ], - "bbox": 3223, + "bbox": 3227, "colour": "black", "endx": 100.0, "endy": 525.0, @@ -2786,7 +2844,7 @@ 0, 0 ], - "bbox": 3229, + "bbox": 3233, "colour": "black", "endx": 1075.0, "endy": 525.0, @@ -2804,7 +2862,7 @@ 0, 0 ], - "bbox": 3235, + "bbox": 3239, "colour": "black", "endx": 725.0, "endy": 300.0, @@ -2817,18 +2875,19 @@ }, "e74d0328-d99f-4f04-bf57-8d356f6bed91": { "background": "grey85", - "bbox": 3238, + "bbox": 3242, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "bold ", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 17, "justify": 2, "posx": 1200.0, "posy": 475.0, - "tags": "e74d0328-d99f-4f04-bf57-8d356f6bed91", + "tags": "textbox17", "text": "Three Way Points \n(using 'SX' points)" }, "e83f31fa-3cd8-4cd3-a556-df59ccc4107b": { @@ -2838,7 +2897,7 @@ 0, 0 ], - "bbox": 3244, + "bbox": 3248, "colour": "black", "endx": 525.0, "endy": 550.0, @@ -2851,18 +2910,19 @@ }, "ea6b992c-2762-49ae-99c1-c7647ab675af": { "background": "grey85", - "bbox": 3247, + "bbox": 3251, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "bold ", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 18, "justify": 2, "posx": 1175.0, "posy": 50.0, - "tags": "ea6b992c-2762-49ae-99c1-c7647ab675af", + "tags": "textbox18", "text": "Normal 'Y' points" }, "eb2e45fe-554e-4352-9f00-e4d3a42227c1": { @@ -2872,7 +2932,7 @@ 0, 0 ], - "bbox": 3253, + "bbox": 3257, "colour": "black", "endx": 300.0, "endy": 325.0, @@ -2884,7 +2944,8 @@ "tags": "line41" }, "ed453b53-bdf4-4520-a145-4cd615ed6c3d": { - "bbox": 3257, + "bbox": 3261, + "buttoncolour": "SeaGreen3", "buttonwidth": 15, "item": "route", "itemid": 18, @@ -2932,6 +2993,7 @@ "routecolour": "#0000ff", "routedescription": "Route description (Run Mode tooltip)", "routename": "Route 2", + "setupsensor": 0, "signalsonroute": [], "subsidariesonroute": [], "switchdelay": 0, @@ -2945,7 +3007,7 @@ 0, 0 ], - "bbox": 3263, + "bbox": 3267, "colour": "black", "endx": 400.0, "endy": 300.0, @@ -2963,7 +3025,7 @@ 0, 0 ], - "bbox": 3269, + "bbox": 3273, "colour": "black", "endx": 625.0, "endy": 350.0, @@ -2981,7 +3043,7 @@ 0, 0 ], - "bbox": 3275, + "bbox": 3279, "colour": "black", "endx": 825.0, "endy": 125.0, @@ -2994,18 +3056,19 @@ }, "f8186a69-c498-4d68-82f2-02346f09c972": { "background": "grey85", - "bbox": 3278, + "bbox": 3282, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 19, "justify": 2, "posx": 325.0, "posy": 350.0, - "tags": "f8186a69-c498-4d68-82f2-02346f09c972", + "tags": "textbox19", "text": "(Rotated)" }, "f8d3502f-60d3-4b7d-b499-47fd43117879": { @@ -3015,7 +3078,7 @@ 0, 0 ], - "bbox": 3284, + "bbox": 3288, "colour": "black", "endx": 1150.0, "endy": 350.0, @@ -3033,7 +3096,7 @@ 0, 0 ], - "bbox": 3290, + "bbox": 3294, "colour": "black", "endx": 100.0, "endy": 325.0, @@ -3051,7 +3114,7 @@ 0, 0 ], - "bbox": 3296, + "bbox": 3300, "colour": "black", "endx": 75.0, "endy": 350.0, @@ -3065,7 +3128,7 @@ "fe787384-3810-4c9d-864d-8d258e6ee74e": { "alsoswitch": 0, "automatic": false, - "bbox": 3306, + "bbox": 3310, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -3286,11 +3349,12 @@ }, "general": { "automation": true, + "buttonsize": 8, "editmode": false, "filename": "/home/john/model-railway-signalling/configuration_examples/complex_trackwork.sig", "info": "Document your layout here", "spadpopups": false, - "version": "Version 4.6.1" + "version": "Version 4.7.0" }, "gpio": { "portmappings": [], diff --git a/configuration_examples/interlocking_colour_light_example.sig b/configuration_examples/interlocking_colour_light_example.sig index d6293935..aa3c9dff 100644 --- a/configuration_examples/interlocking_colour_light_example.sig +++ b/configuration_examples/interlocking_colour_light_example.sig @@ -15,7 +15,7 @@ false, "" ], - "bbox": 3418, + "bbox": 3422, "dccaspects": [ [ [ @@ -902,7 +902,7 @@ 0, 0 ], - "bbox": 3424, + "bbox": 3428, "colour": "black", "endx": 1800, "endy": 275, @@ -920,7 +920,7 @@ 0, 0 ], - "bbox": 3430, + "bbox": 3434, "colour": "black", "endx": 0, "endy": 275, @@ -934,7 +934,7 @@ "36c92467-cd2d-4608-bd3d-1ae2fe2fb9b0": { "alsoswitch": 6, "automatic": false, - "bbox": 3436, + "bbox": 3440, "colour": "black", "dccaddress": 1005, "dccreversed": false, @@ -1015,7 +1015,7 @@ false, "" ], - "bbox": 3453, + "bbox": 3457, "dccaspects": [ [ [ @@ -1885,7 +1885,7 @@ false, "" ], - "bbox": 3470, + "bbox": 3474, "dccaspects": [ [ [ @@ -2750,7 +2750,7 @@ 0, 0 ], - "bbox": 3476, + "bbox": 3480, "colour": "black", "endx": 1375, "endy": 225, @@ -2773,7 +2773,7 @@ false, "" ], - "bbox": 3487, + "bbox": 3491, "dccaspects": [ [ [ @@ -3675,7 +3675,7 @@ false, "" ], - "bbox": 3506, + "bbox": 3510, "dccaspects": [ [ [ @@ -4556,7 +4556,7 @@ false, "" ], - "bbox": 3523, + "bbox": 3527, "dccaspects": [ [ [ @@ -5426,7 +5426,7 @@ false, "" ], - "bbox": 3540, + "bbox": 3544, "dccaspects": [ [ [ @@ -6317,7 +6317,7 @@ false, "" ], - "bbox": 3557, + "bbox": 3561, "dccaspects": [ [ [ @@ -7187,7 +7187,7 @@ false, "" ], - "bbox": 3568, + "bbox": 3572, "dccaspects": [ [ [ @@ -8069,7 +8069,7 @@ "6c28ce24-e08c-4845-8e06-b21f418911eb": { "alsoswitch": 0, "automatic": true, - "bbox": 3573, + "bbox": 3577, "colour": "black", "dccaddress": 101, "dccreversed": false, @@ -8094,7 +8094,7 @@ 0, 0 ], - "bbox": 3579, + "bbox": 3583, "colour": "black", "endx": 975, "endy": 225, @@ -8117,7 +8117,7 @@ false, "" ], - "bbox": 3590, + "bbox": 3594, "dccaspects": [ [ [ @@ -8993,7 +8993,7 @@ 0, 0 ], - "bbox": 3596, + "bbox": 3600, "colour": "black", "endx": 1275, "endy": 175, @@ -9016,7 +9016,7 @@ false, "" ], - "bbox": 3613, + "bbox": 3617, "dccaspects": [ [ [ @@ -9886,7 +9886,7 @@ false, "" ], - "bbox": 3630, + "bbox": 3634, "dccaspects": [ [ [ @@ -10756,7 +10756,7 @@ false, "" ], - "bbox": 3647, + "bbox": 3651, "dccaspects": [ [ [ @@ -11621,7 +11621,7 @@ 1, 1 ], - "bbox": 3653, + "bbox": 3657, "colour": "black", "endx": 1325, "endy": 175, @@ -11644,7 +11644,7 @@ false, "" ], - "bbox": 3670, + "bbox": 3674, "dccaspects": [ [ [ @@ -12514,7 +12514,7 @@ false, "" ], - "bbox": 3687, + "bbox": 3691, "dccaspects": [ [ [ @@ -13375,7 +13375,7 @@ "b8c1ca15-569a-42d8-9692-c4634b06e70b": { "alsoswitch": 4, "automatic": false, - "bbox": 3694, + "bbox": 3698, "colour": "black", "dccaddress": 1003, "dccreversed": false, @@ -13441,7 +13441,7 @@ 0, 0 ], - "bbox": 3700, + "bbox": 3704, "colour": "black", "endx": 0, "endy": 225, @@ -13455,7 +13455,7 @@ "c52e3e22-22c7-4623-bd67-8cacac93ba06": { "alsoswitch": 1, "automatic": false, - "bbox": 3707, + "bbox": 3711, "colour": "black", "dccaddress": 1002, "dccreversed": false, @@ -13521,7 +13521,7 @@ 1, 1 ], - "bbox": 3713, + "bbox": 3717, "colour": "black", "endx": 725, "endy": 175, @@ -13544,7 +13544,7 @@ false, "" ], - "bbox": 3724, + "bbox": 3728, "dccaspects": [ [ [ @@ -14436,7 +14436,7 @@ "df337c59-d6c8-420a-9165-628b58e8be08": { "alsoswitch": 0, "automatic": true, - "bbox": 3729, + "bbox": 3733, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -14466,7 +14466,7 @@ false, "" ], - "bbox": 3740, + "bbox": 3744, "dccaspects": [ [ [ @@ -15367,7 +15367,7 @@ false, "" ], - "bbox": 3751, + "bbox": 3755, "dccaspects": [ [ [ @@ -16291,7 +16291,7 @@ "f2b7d2d9-eb4d-4e55-859f-100131262fe2": { "alsoswitch": 0, "automatic": true, - "bbox": 3756, + "bbox": 3760, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -16321,7 +16321,7 @@ false, "" ], - "bbox": 3775, + "bbox": 3779, "dccaspects": [ [ [ @@ -17274,11 +17274,12 @@ }, "general": { "automation": true, + "buttonsize": 8, "editmode": false, "filename": "/home/john/model-railway-signalling/configuration_examples/interlocking_colour_light_example.sig", "info": "This is a basic example of a layout without track sensors (and the associated signal automation).\n\nThe layout is signalled with a combination of 2-aspect home and distant signals, with the signals\nand points interlocked together - and the signals interlocked with opposing signals. \n\nFor example, signal 1 is interlocked with point 1 so can only be cleared if the facing point lock \n(the 'L' button) is active. When the main signal is cleared, it will show the appropriate route \nindication (a left-hand feather for the diverging loop and no feather for the main line). Note that\nin this example, the associated subsidary signal (controlled by the 'S' button) is similarly\ninterlocked, and can be cleared for either route (as it is a subsidary signal, it can control\nmultiple routes so no route indications are displayed).\n\nSignal 1 is also interlocked with any opposing signals that could allow a train onto the same\nsection of track - in this example, the signal is interlocked with ground signals 15, 13, 7 and 6\n(dependant on whether the route 'cleared' by these signals conflicts with the route for Signal 1).\n\nAll point interlocking is automatically configured for you via the signal interlocking, so in this\nexample, point 2 is locked if signal 1 (main or subsidary) is cleared for a train movement.\n\nNote that not all signals are configured for all possible routes - the only 'valid' shunting\nmovement allowed from signal 14 is via the crossover back onto the other main line. In 'edit' mode,\nclick on each signal or point and select the 'interlocking' tab to view the configuration.\n\nMost of the signals and points are configured with DCC Addresses. Signal 1 uses a single DCC address\nfor swiching the signal between 'danger' and 'proceed'. The single feather route indication uses\nuses a single DCC address to turn the route display on/off. Note that a DCC command is specified\nfor the 'dark' aspect - this is to turn off the route indication when the signal is at 'danger'. \nSimilarly, the subsidary aspect is controlled by a single DCC address.\n\nThis level of configuration is ideal if you want to be the signal man/woman for your layout. Other\nexamples add (semi) automation to this signalling scheme so the signals change automatically as\nyou watch the trains go round.\n\n", "spadpopups": false, - "version": "Version 4.6.1" + "version": "Version 4.7.0" }, "gpio": { "portmappings": [], diff --git a/configuration_examples/interlocking_semaphore_example.sig b/configuration_examples/interlocking_semaphore_example.sig index 99d2f661..e19ead5e 100644 --- a/configuration_examples/interlocking_semaphore_example.sig +++ b/configuration_examples/interlocking_semaphore_example.sig @@ -15,7 +15,7 @@ false, "" ], - "bbox": 3894, + "bbox": 3898, "dccaspects": [ [ [ @@ -885,7 +885,7 @@ false, "" ], - "bbox": 3923, + "bbox": 3927, "dccaspects": [ [ [ @@ -1782,7 +1782,7 @@ 0, 0 ], - "bbox": 3929, + "bbox": 3933, "colour": "black", "endx": 1275, "endy": 225, @@ -1805,7 +1805,7 @@ false, "" ], - "bbox": 3955, + "bbox": 3959, "dccaspects": [ [ [ @@ -2675,7 +2675,7 @@ false, "" ], - "bbox": 3981, + "bbox": 3985, "dccaspects": [ [ [ @@ -3545,7 +3545,7 @@ false, "" ], - "bbox": 4007, + "bbox": 4011, "dccaspects": [ [ [ @@ -4436,7 +4436,7 @@ false, "" ], - "bbox": 4033, + "bbox": 4037, "dccaspects": [ [ [ @@ -5301,7 +5301,7 @@ 0, 0 ], - "bbox": 4039, + "bbox": 4043, "colour": "black", "endx": 1375, "endy": 225, @@ -5324,7 +5324,7 @@ false, "" ], - "bbox": 4047, + "bbox": 4051, "dccaspects": [ [ [ @@ -6226,7 +6226,7 @@ false, "" ], - "bbox": 4073, + "bbox": 4077, "dccaspects": [ [ [ @@ -7096,7 +7096,7 @@ false, "" ], - "bbox": 4100, + "bbox": 4104, "dccaspects": [ [ [ @@ -7972,7 +7972,7 @@ 0, 0 ], - "bbox": 4106, + "bbox": 4110, "colour": "black", "endx": 1375, "endy": 275, @@ -7995,7 +7995,7 @@ false, "" ], - "bbox": 4154, + "bbox": 4158, "dccaspects": [ [ [ @@ -8865,7 +8865,7 @@ false, "" ], - "bbox": 4180, + "bbox": 4184, "dccaspects": [ [ [ @@ -9735,7 +9735,7 @@ false, "" ], - "bbox": 4188, + "bbox": 4192, "dccaspects": [ [ [ @@ -10617,7 +10617,7 @@ "6c28ce24-e08c-4845-8e06-b21f418911eb": { "alsoswitch": 4, "automatic": false, - "bbox": 4195, + "bbox": 4199, "colour": "black", "dccaddress": 101, "dccreversed": false, @@ -10688,7 +10688,7 @@ false, "" ], - "bbox": 4221, + "bbox": 4225, "dccaspects": [ [ [ @@ -11553,7 +11553,7 @@ 0, 0 ], - "bbox": 4227, + "bbox": 4231, "colour": "black", "endx": 1275, "endy": 175, @@ -11576,7 +11576,7 @@ false, "" ], - "bbox": 4235, + "bbox": 4239, "dccaspects": [ [ [ @@ -12452,7 +12452,7 @@ 0, 0 ], - "bbox": 4241, + "bbox": 4245, "colour": "black", "endx": 0, "endy": 275, @@ -12470,7 +12470,7 @@ 1, 1 ], - "bbox": 4247, + "bbox": 4251, "colour": "black", "endx": 1325, "endy": 175, @@ -12484,7 +12484,7 @@ "b8c1ca15-569a-42d8-9692-c4634b06e70b": { "alsoswitch": 0, "automatic": true, - "bbox": 4252, + "bbox": 4256, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -12514,7 +12514,7 @@ false, "" ], - "bbox": 4278, + "bbox": 4282, "dccaspects": [ [ [ @@ -13379,7 +13379,7 @@ 0, 0 ], - "bbox": 4284, + "bbox": 4288, "colour": "black", "endx": 0, "endy": 225, @@ -13393,7 +13393,7 @@ "c52e3e22-22c7-4623-bd67-8cacac93ba06": { "alsoswitch": 1, "automatic": false, - "bbox": 4291, + "bbox": 4295, "colour": "black", "dccaddress": 100, "dccreversed": false, @@ -13459,7 +13459,7 @@ 1, 1 ], - "bbox": 4297, + "bbox": 4301, "colour": "black", "endx": 725, "endy": 175, @@ -13482,7 +13482,7 @@ false, "" ], - "bbox": 4305, + "bbox": 4309, "dccaspects": [ [ [ @@ -14374,7 +14374,7 @@ "d9840a46-28fd-40dd-a059-df46b9a762e2": { "alsoswitch": 6, "automatic": false, - "bbox": 4311, + "bbox": 4315, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -14446,7 +14446,7 @@ "df337c59-d6c8-420a-9165-628b58e8be08": { "alsoswitch": 0, "automatic": true, - "bbox": 4316, + "bbox": 4320, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -14476,7 +14476,7 @@ false, "" ], - "bbox": 4324, + "bbox": 4328, "dccaspects": [ [ [ @@ -15368,7 +15368,7 @@ "f14ef6e8-7b7b-4568-b0e6-c105889caf10": { "alsoswitch": 0, "automatic": true, - "bbox": 4329, + "bbox": 4333, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -15398,7 +15398,7 @@ false, "" ], - "bbox": 4337, + "bbox": 4341, "dccaspects": [ [ [ @@ -16331,7 +16331,7 @@ false, "" ], - "bbox": 4366, + "bbox": 4370, "dccaspects": [ [ [ @@ -17284,11 +17284,12 @@ }, "general": { "automation": true, + "buttonsize": 8, "editmode": false, "filename": "/home/john/model-railway-signalling/configuration_examples/interlocking_semaphore_example.sig", "info": "This is a basic example of a layout without track sensors (and the associated signal automation).\n\nThe layout is signalled with a combination of semaphore home and distant signals. Note that\nSignal 16 is a home signal with a secondary distant arm (controled by the 'D' button). \n\nThe signals and points are interlocked together - and the signals interlocked with opposing signals\n(in 'edit' mode, double click on a signal or point and select the 'interlocking' tab to edit/view).\n\nIn this example, signal 1 is interlocked with point 2 so can only be cleared if the facing point \nlock (the 'L' button) is active. When the main signal is cleared, the appropriate route arm will \nbe cleared. Note that the associated subsidary signal (controlled by the 'S' button) is similarly\ninterlocked with point 2. When cleared, the appropriate subsidary arm will be cleared.\n\nSignal 1 is also interlocked with any opposing signals that could allow a train onto the same\nsection of track - in this example, the signal is interlocked with ground signals 15, 13, 7 and 6\n(dependant on whether the route 'cleared' by these signals conflicts with the route for Signal 1).\n\nNote that the distant signals are also configured to be interlocked with all home signals ahead. \nThis means that the signal cannot be cleared unless all home signals (up to the next distant signal)\nhave also been cleared (note that they can be returned to Caution at any time).\n\nAll point interlocking is automatically configured via the signal interlocking so, for example\npoint 2 is locked if signal 1 (main or subsidary) is cleared for a train movement.\n\nNote that not all signals are configured for all possible routes - the only 'valid' shunting\nmovement allowed from signal 14 is via the crossover back onto the other main line. \n\nSome of the signals and points are configured with DCC Addresses, with a single DCC address used\nfor each main or subsidary route arm. \n\nThis level of configuration is ideal if you want to be the signal man/woman for your layout. Other\nexamples add track sensors to allow semi-automation of the signalling scheme so the signals will \nchange automatically as you watch the trains go round.\n", "spadpopups": false, - "version": "Version 4.6.1" + "version": "Version 4.7.0" }, "gpio": { "portmappings": [], diff --git a/configuration_examples/mqtt_networked_example.sig b/configuration_examples/mqtt_networked_example.sig index 54ebdcb3..38562b18 100644 --- a/configuration_examples/mqtt_networked_example.sig +++ b/configuration_examples/mqtt_networked_example.sig @@ -32,7 +32,7 @@ false, "" ], - "bbox": 4448, + "bbox": 4452, "dccaspects": [ [ [ @@ -902,7 +902,7 @@ false, "" ], - "bbox": 4465, + "bbox": 4469, "dccaspects": [ [ [ @@ -1782,9 +1782,10 @@ ] }, "13da3a2d-77af-4de3-989d-b64f7412bed4": { - "bbox": 4469, + "bbox": 4473, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 21, "mirror": "Box1-33", @@ -1799,7 +1800,7 @@ 1, 1 ], - "bbox": 4475, + "bbox": 4479, "colour": "black", "endx": 725.0, "endy": 300.0, @@ -1817,7 +1818,7 @@ 20, 5 ], - "bbox": 4481, + "bbox": 4485, "colour": "black", "endx": 225.0, "endy": 475.0, @@ -1829,7 +1830,7 @@ "tags": "line15" }, "1673df65-f654-47c4-bfd6-4bfd169de38c": { - "bbox": 4496, + "bbox": 4500, "bellsound": "bell-ring-01.wav", "item": "instrument", "itemid": 11, @@ -1841,9 +1842,10 @@ "tags": "instrument11" }, "17b8ab25-9197-49d6-b624-54f7696202c5": { - "bbox": 4500, + "bbox": 4504, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 31, "mirror": "Box1-23", @@ -1852,9 +1854,10 @@ "tags": "section31" }, "19329487-2174-4d77-8f22-093a94fd6aa9": { - "bbox": 4504, + "bbox": 4508, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 4, "mirror": "Box1-14", @@ -1869,7 +1872,7 @@ 0, 0 ], - "bbox": 4510, + "bbox": 4514, "colour": "black", "endx": 775.0, "endy": 475.0, @@ -1892,7 +1895,7 @@ false, "" ], - "bbox": 4527, + "bbox": 4531, "dccaspects": [ [ [ @@ -2783,7 +2786,7 @@ false, "" ], - "bbox": 4544, + "bbox": 4548, "dccaspects": [ [ [ @@ -3653,7 +3656,7 @@ false, "" ], - "bbox": 4561, + "bbox": 4565, "dccaspects": [ [ [ @@ -4514,7 +4517,7 @@ "42bd0f07-f00e-4db9-98c0-1b1bf2254d6d": { "alsoswitch": 0, "automatic": false, - "bbox": 4568, + "bbox": 4572, "colour": "black", "dccaddress": 1011, "dccreversed": false, @@ -4574,9 +4577,10 @@ "ybuttonoffset": 0 }, "46a4dcc4-7246-4ea4-8f1f-5e939ed7c6f8": { - "bbox": 4572, + "bbox": 4576, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 11, "mirror": "", @@ -4596,7 +4600,7 @@ false, "" ], - "bbox": 4589, + "bbox": 4593, "dccaspects": [ [ [ @@ -5466,7 +5470,7 @@ false, "" ], - "bbox": 4606, + "bbox": 4610, "dccaspects": [ [ [ @@ -6325,9 +6329,10 @@ ] }, "4b175fed-abf6-4577-93bf-4f9d3e9e75c0": { - "bbox": 4610, + "bbox": 4614, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 1, "mirror": "", @@ -6347,7 +6352,7 @@ false, "" ], - "bbox": 4627, + "bbox": 4631, "dccaspects": [ [ [ @@ -7212,7 +7217,7 @@ 0, 0 ], - "bbox": 4633, + "bbox": 4637, "colour": "black", "endx": 750.0, "endy": 400.0, @@ -7230,7 +7235,7 @@ 20, 5 ], - "bbox": 4639, + "bbox": 4643, "colour": "black", "endx": 775.0, "endy": 450.0, @@ -7253,7 +7258,7 @@ false, "" ], - "bbox": 4656, + "bbox": 4660, "dccaspects": [ [ [ @@ -8112,9 +8117,10 @@ ] }, "69f67a39-c5b3-45bb-a6a0-d4b833292158": { - "bbox": 4660, + "bbox": 4664, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 23, "mirror": "Box1-31", @@ -8123,9 +8129,10 @@ "tags": "section23" }, "6aedd8b7-fc51-4c13-a564-4b0e34a6f036": { - "bbox": 4664, + "bbox": 4668, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 13, "mirror": "", @@ -8140,7 +8147,7 @@ 1, 1 ], - "bbox": 4670, + "bbox": 4674, "colour": "black", "endx": 250.0, "endy": 125.0, @@ -8152,9 +8159,10 @@ "tags": "line6" }, "76e53459-d4c9-4116-8d77-03fdba76cbaf": { - "bbox": 4674, + "bbox": 4678, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 32, "mirror": "", @@ -8169,7 +8177,7 @@ 0, 0 ], - "bbox": 4680, + "bbox": 4684, "colour": "black", "endx": 750.0, "endy": 525.0, @@ -8181,9 +8189,10 @@ "tags": "line10" }, "905280e4-a705-45d6-bb9d-c7fd3b26236c": { - "bbox": 4684, + "bbox": 4688, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 33, "mirror": "Box1-21", @@ -8203,7 +8212,7 @@ false, "" ], - "bbox": 4701, + "bbox": 4705, "dccaspects": [ [ [ @@ -9089,7 +9098,7 @@ 0, 0 ], - "bbox": 4707, + "bbox": 4711, "colour": "black", "endx": 725.0, "endy": 300.0, @@ -9112,7 +9121,7 @@ false, "" ], - "bbox": 4724, + "bbox": 4728, "dccaspects": [ [ [ @@ -9977,7 +9986,7 @@ 20, 5 ], - "bbox": 4730, + "bbox": 4734, "colour": "black", "endx": 300.0, "endy": 75.0, @@ -9995,7 +10004,7 @@ 1, 1 ], - "bbox": 4736, + "bbox": 4740, "colour": "black", "endx": 725.0, "endy": 250.0, @@ -10013,7 +10022,7 @@ 20, 5 ], - "bbox": 4742, + "bbox": 4746, "colour": "black", "endx": 675.0, "endy": 250.0, @@ -10025,9 +10034,10 @@ "tags": "line4" }, "b0f49fb4-18ad-4ab7-96c7-389aa0620a9b": { - "bbox": 4746, + "bbox": 4750, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 22, "mirror": "", @@ -10042,7 +10052,7 @@ 1, 1 ], - "bbox": 4752, + "bbox": 4756, "colour": "black", "endx": 250.0, "endy": 75.0, @@ -10065,7 +10075,7 @@ false, "" ], - "bbox": 4769, + "bbox": 4773, "dccaspects": [ [ [ @@ -10951,7 +10961,7 @@ 0, 0 ], - "bbox": 4775, + "bbox": 4779, "colour": "black", "endx": 750.0, "endy": 400.0, @@ -10963,9 +10973,10 @@ "tags": "line9" }, "bfed75b7-c5a8-4359-884e-64d60a15d756": { - "bbox": 4779, + "bbox": 4783, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 2, "mirror": "", @@ -10974,9 +10985,10 @@ "tags": "section2" }, "d0582275-d5f9-4560-8d34-3a7b41144ba3": { - "bbox": 4783, + "bbox": 4787, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 14, "mirror": "Box1-4", @@ -10985,9 +10997,10 @@ "tags": "section14" }, "d9e945b5-0996-45af-8f99-d685cf9b7b37": { - "bbox": 4787, + "bbox": 4791, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 12, "mirror": "", @@ -11002,7 +11015,7 @@ 0, 0 ], - "bbox": 4793, + "bbox": 4797, "colour": "black", "endx": 750.0, "endy": 525.0, @@ -11020,7 +11033,7 @@ 0, 0 ], - "bbox": 4799, + "bbox": 4803, "colour": "black", "endx": 225.0, "endy": 500.0, @@ -11032,9 +11045,10 @@ "tags": "line12" }, "e122f2ea-41d1-451a-9429-83086e3fb36d": { - "bbox": 4803, + "bbox": 4807, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 3, "mirror": "", @@ -11049,7 +11063,7 @@ 0, 0 ], - "bbox": 4809, + "bbox": 4813, "colour": "black", "endx": 250.0, "endy": 400.0, @@ -11061,7 +11075,7 @@ "tags": "line11" }, "ee36a4a7-221f-4528-9a14-67fd5c0fde1a": { - "bbox": 4824, + "bbox": 4828, "bellsound": "bell-ring-01.wav", "item": "instrument", "itemid": 1, @@ -11075,7 +11089,7 @@ "f174c280-de2b-400c-96cb-9e6dd664a0d1": { "alsoswitch": 0, "automatic": false, - "bbox": 4831, + "bbox": 4835, "colour": "black", "dccaddress": 1001, "dccreversed": false, @@ -11141,7 +11155,7 @@ 0, 0 ], - "bbox": 4837, + "bbox": 4841, "colour": "black", "endx": 225.0, "endy": 425.0, @@ -11159,7 +11173,7 @@ 0, 0 ], - "bbox": 4843, + "bbox": 4847, "colour": "black", "endx": 275.0, "endy": 100.0, @@ -11274,11 +11288,12 @@ }, "general": { "automation": true, + "buttonsize": 8, "editmode": false, "filename": "/home/john/model-railway-signalling/configuration_examples/mqtt_networked_example.sig", "info": "This example includes two 'layouts', where each layout is split into two signalling areas with MQTT \nnetworking configured to provide the communication between each area. To run this example you will need\nto either stand up your own MQTT broker (I installed a broker on the local machine with basic username \nand password authentication) or use a third-party broker (there are free-to-use test brokers out there).\n\nAlthough all the signalling areas appear on the same schematic, all communication between the two \n'halves' of each layout is via the MQTT broker. Select 'Settings' => 'MQTT' from the menubar to view\nthe broker configuration and list of published and subscribed signals, sensors, sections and instruments.\n\nThe top layout is an example of a single line between two 'block sections', with the points and signals \nin in each block section interlocked - e.g. signal 1 is interlocked with point 1 and opposing signals 3 \nand 4. The distant signals are configured as 'fixed distants' (see the signal's 'interlocking' tab for\ndetails of how this has been configured). The only communication between each block section is via MQTT \nnetworking - in this case linking the two block instruments and 'mirroring' track sections 4 and 14 \n(i.e. section 4 on the top half of the layout represents section 14 on the bottom half of the layout).\n\nThe block instruments are used to coordinate train movements between the two block sections and allow \ncommunication via bell codes. In this example, the 'starter' signals for each block section have been \ninterlocked with their respective instruments (and the instruments linked together via the MQTT network)\nThis means signals 1/2 can only be cleared when instrument 2 (on the bottom) has been set to 'line clear' \nand signals 11/12 can only be cleared when instrument 1 (on the top) has been set to 'line clear'.\n\nTrack sections have been included on the schematic to provide a mimic display of track occupancy (when\na train passes a signal (signal is 'off') it gets passed from the section behind to the section ahead).\nThe desired behavior is again configured via the 'automation' tab of each signal. For example, signal 3\ncontrols two 'routes' so when passed, section 3 will be cleared and either section 1 or 2 will be set \nto occupied, depending on which route the signal is cleared for. As sections 4 and 14 are 'mirrored' \nvia the MQTT network, trains are seamlessly passed between each each half of the layout\n\nThe bottom layout represents a simple oval of track, but split into two signalling areas. Here, MQTT\nnetworking is used to track the displayed aspect of the 'signal ahead' (in the next signalling area)\nto ensure all signals display the correct aspects as the train moves around the track. MQTT networking\nis also used to 'mirror' the track sections across signalling area boundaries to seamlessly pass trains\nfrom one half of the layout to the other (section 21 mirrors 33 and section 23 mirrors 31).\n\nTo see how the layout functions in its entirity, set all signals (21, 22, 31, 31) to 'off' and right-\nclick the top-middle track section to enter a train code of your choice (this will set the section to\n'occupied'. Then, pass the train around the loop by clicking the small button at the base of each \nsignal in turn (22 => 31 => 32 => 21 etc).\n\nAlternatively, if you are running on a Raspberry Pi, you can trigger the 'signal passed' events via\nthe GPIO ports. GPIO ports 4,5,6 and 7 have been allocated to \"track sensors\" 1,2,3 and 4 (Select \n'Settings' => 'Sensors' from the menubar to view the track sensor configuration). To trigger, simply\nmomentarily connect the appropriate GPIO pin to one of the R-Pi's 0V DC supply pins. \n\nFor this example, the track sensors are published (and subscribed back to) via the MQTT broker (select \n'Settings' => 'MQTT' from the menubar to see the configuration) with the signal configured to trigger \nthe 'signal passed' events from on the subscribed sensors. The DCC command feed is also published (and\nsubscribed back to) via the MQTT broker. This demonstrates how multiple application instances can be \nconfigured to use a single R-Pi acting as the interface to the layout.\n", "spadpopups": false, - "version": "Version 4.6.1" + "version": "Version 4.7.0" }, "gpio": { "portmappings": [ diff --git a/configuration_examples/my_layout.sig b/configuration_examples/my_layout.sig index 36638966..5a714427 100644 --- a/configuration_examples/my_layout.sig +++ b/configuration_examples/my_layout.sig @@ -71,7 +71,8 @@ "instruments": {}, "objects": { "010e5fa1-fd7a-4068-a751-d214c9950ae4": { - "bbox": 4940, + "bbox": 4944, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 3, @@ -96,6 +97,7 @@ "routecolour": "#ff6100", "routedescription": "Platform 1 departure to Down Main", "routename": "Platform 1 to Down", + "setupsensor": 0, "signalsonroute": [ 8 ], @@ -105,9 +107,10 @@ "tracksensor": 0 }, "0148c4d4-8a20-45c2-9a32-a1eb5e532d9f": { - "bbox": 4944, + "bbox": 4948, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 7, "mirror": "", @@ -122,7 +125,7 @@ 0, 0 ], - "bbox": 4950, + "bbox": 4954, "colour": "black", "endx": 900.0, "endy": 425.0, @@ -135,18 +138,19 @@ }, "02aaa6bf-c249-45ed-965f-047262cad8a5": { "background": "grey85", - "bbox": 4953, + "bbox": 4957, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "bold ", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 1, "justify": 2, "posx": 76.0, "posy": 275.0, - "tags": "02aaa6bf-c249-45ed-965f-047262cad8a5", + "tags": "textbox1", "text": "BRANCH" }, "031f9ebb-3dbb-4496-bd2e-da1f19292ffc": { @@ -156,7 +160,7 @@ 20, 5 ], - "bbox": 4959, + "bbox": 4963, "colour": "black", "endx": 161.0, "endy": 275.0, @@ -170,7 +174,7 @@ "058012a1-4cb8-40e0-9c67-55c0139c064a": { "alsoswitch": 2, "automatic": false, - "bbox": 4966, + "bbox": 4970, "colour": "black", "dccaddress": 105, "dccreversed": true, @@ -242,7 +246,7 @@ "05daf194-7943-4c0b-8540-b574716ce9ca": { "alsoswitch": 1, "automatic": false, - "bbox": 4973, + "bbox": 4977, "colour": "black", "dccaddress": 106, "dccreversed": true, @@ -308,7 +312,7 @@ 0, 0 ], - "bbox": 4979, + "bbox": 4983, "colour": "black", "endx": 1700.0, "endy": 325.0, @@ -331,7 +335,7 @@ false, "" ], - "bbox": 4996, + "bbox": 5000, "dccaspects": [ [ [ @@ -1252,7 +1256,8 @@ ] }, "0950163c-9b77-46c1-84c9-5ad56088c55a": { - "bbox": 5000, + "bbox": 5004, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 1, @@ -1277,6 +1282,7 @@ "routecolour": "#ff6100", "routedescription": "Down Main arrival into Platform 1", "routename": "Down to Platform 1", + "setupsensor": 0, "signalsonroute": [ 5 ], @@ -1287,24 +1293,26 @@ }, "0981c3fb-63d1-4cc4-bcdb-c89612f58ed6": { "background": "grey85", - "bbox": 5003, + "bbox": 5007, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "bold ", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 2, "justify": 2, "posx": 548.0, "posy": 181.0, - "tags": "0981c3fb-63d1-4cc4-bcdb-c89612f58ed6", + "tags": "textbox2", "text": "TMD" }, "0bcc8fee-02a4-485d-a1f4-b8b935e35504": { - "bbox": 5007, + "bbox": 5011, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 15, "mirror": "", @@ -1324,7 +1332,7 @@ false, "" ], - "bbox": 5018, + "bbox": 5022, "dccaspects": [ [ [ @@ -2214,7 +2222,8 @@ ] }, "1625ee65-272d-41ec-a9ec-2832f28fbbe7": { - "bbox": 5022, + "bbox": 5026, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 17, @@ -2246,6 +2255,7 @@ "routecolour": "#ff6100", "routedescription": "Goods loop departure to Down Main", "routename": "Goods to Down", + "setupsensor": 0, "signalsonroute": [ 1 ], @@ -2257,7 +2267,7 @@ "1ab8c048-bc7a-4a3b-a14c-b02afcafcaa4": { "alsoswitch": 0, "automatic": true, - "bbox": 5027, + "bbox": 5031, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -2287,7 +2297,7 @@ false, "" ], - "bbox": 5044, + "bbox": 5048, "dccaspects": [ [ [ @@ -3198,7 +3208,8 @@ ] }, "1f8a59e9-5c19-4a50-a9ec-c9fac51c71c1": { - "bbox": 5048, + "bbox": 5052, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 12, @@ -3222,6 +3233,7 @@ "routecolour": "#0000ff", "routedescription": "Platform 2 departure to branch", "routename": "Platform 3 to Branch", + "setupsensor": 0, "signalsonroute": [ 3, 22 @@ -3243,7 +3255,7 @@ false, "" ], - "bbox": 5067, + "bbox": 5071, "dccaspects": [ [ [ @@ -4192,7 +4204,7 @@ 1, 1 ], - "bbox": 5073, + "bbox": 5077, "colour": "black", "endx": 1325.0, "endy": 200.0, @@ -4204,9 +4216,10 @@ "tags": "line17" }, "22a63804-92ab-4d6a-89fb-c5b3eed7e475": { - "bbox": 5077, + "bbox": 5081, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 8, "mirror": "", @@ -4217,7 +4230,7 @@ "25bde8e4-dc58-4917-b214-70b236a0e316": { "alsoswitch": 4, "automatic": false, - "bbox": 5083, + "bbox": 5087, "colour": "black", "dccaddress": 104, "dccreversed": false, @@ -4289,7 +4302,7 @@ "2660e87b-743d-4bf5-b5c9-33eb079c533c": { "alsoswitch": 0, "automatic": true, - "bbox": 5088, + "bbox": 5092, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -4308,9 +4321,10 @@ "ybuttonoffset": 0 }, "280ffb82-b4eb-49d3-96d3-9de9160072b7": { - "bbox": 5092, + "bbox": 5096, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 6, "mirror": "", @@ -4325,7 +4339,7 @@ 1, 1 ], - "bbox": 5098, + "bbox": 5102, "colour": "black", "endx": 650.0, "endy": 200.0, @@ -4343,7 +4357,7 @@ 20, 5 ], - "bbox": 5104, + "bbox": 5108, "colour": "black", "endx": 125.0, "endy": 375.0, @@ -4355,7 +4369,8 @@ "tags": "line31" }, "2bead2aa-671c-4699-9f91-4f2ec453fe9f": { - "bbox": 5108, + "bbox": 5112, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 6, @@ -4383,6 +4398,7 @@ "routecolour": "#c800ff", "routedescription": "Goods loop departure to Up Main", "routename": "Goods to Up", + "setupsensor": 0, "signalsonroute": [ 2 ], @@ -4393,38 +4409,41 @@ }, "2ea41ff5-d1c7-4c38-bb25-53eeadc25735": { "background": "grey85", - "bbox": 5111, + "bbox": 5115, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "bold ", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 3, "justify": 2, "posx": 1726.0, "posy": 375.0, - "tags": "2ea41ff5-d1c7-4c38-bb25-53eeadc25735", + "tags": "textbox3", "text": "DOWN\nMAIN" }, "2fcbbfe6-7a94-4913-b572-8ddad96c76a3": { "background": "grey85", - "bbox": 5114, + "bbox": 5118, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "bold ", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 4, "justify": 2, "posx": 1272.0, "posy": 130.0, - "tags": "2fcbbfe6-7a94-4913-b572-8ddad96c76a3", + "tags": "textbox4", "text": "Goods Yard" }, "333315c4-06d2-4914-bfcb-48af3597e851": { - "bbox": 5118, + "bbox": 5122, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 7, @@ -4453,6 +4472,7 @@ "routecolour": "#ff6100", "routedescription": "Down Main arrival into goods loop", "routename": "Down to Goods", + "setupsensor": 0, "signalsonroute": [ 5 ], @@ -4463,18 +4483,19 @@ }, "359d1564-4a52-49ef-81e0-a72df0f02220": { "background": "grey85", - "bbox": 5121, + "bbox": 5125, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "bold ", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 5, "justify": 2, "posx": 550.0, "posy": 130.0, - "tags": "359d1564-4a52-49ef-81e0-a72df0f02220", + "tags": "textbox5", "text": "Headshunt" }, "36d05755-f972-442a-9139-8739e35dadea": { @@ -4484,7 +4505,7 @@ 0, 0 ], - "bbox": 5127, + "bbox": 5131, "colour": "black", "endx": 575.0, "endy": 325.0, @@ -4496,9 +4517,10 @@ "tags": "line10" }, "37716476-045e-4efc-a8d0-dc30b5f9ccbb": { - "bbox": 5131, + "bbox": 5135, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 5, "mirror": "", @@ -4509,7 +4531,7 @@ "37fbb878-e6fd-4629-96f8-5912ec2ab8d3": { "alsoswitch": 0, "automatic": false, - "bbox": 5138, + "bbox": 5142, "colour": "black", "dccaddress": 108, "dccreversed": false, @@ -4550,7 +4572,7 @@ false, "" ], - "bbox": 5157, + "bbox": 5161, "dccaspects": [ [ [ @@ -5457,7 +5479,7 @@ 20, 5 ], - "bbox": 5163, + "bbox": 5167, "colour": "black", "endx": 500.0, "endy": 375.0, @@ -5475,7 +5497,7 @@ 0, 0 ], - "bbox": 5169, + "bbox": 5173, "colour": "black", "endx": 1125.0, "endy": 425.0, @@ -5487,7 +5509,8 @@ "tags": "line13" }, "3b5c8ec9-898a-4724-8b45-fb33b6158df6": { - "bbox": 5173, + "bbox": 5177, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 20, @@ -5518,6 +5541,7 @@ "routecolour": "#c800ff", "routedescription": "Up Main arrival into goods loop", "routename": "Up to Goods", + "setupsensor": 0, "signalsonroute": [ 6 ], @@ -5527,9 +5551,10 @@ "tracksensor": 0 }, "3b9c9266-3dfe-4349-84a3-c7b9b9185cae": { - "bbox": 5177, + "bbox": 5181, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 17, "mirror": "", @@ -5538,7 +5563,8 @@ "tags": "section17" }, "3ed6eb67-18ea-4f66-b2f0-e12664a6d90f": { - "bbox": 5181, + "bbox": 5185, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 19, @@ -5562,6 +5588,7 @@ "routecolour": "#0000ff", "routedescription": "Branch Arrival into Platform 3", "routename": "Branch to Platform 3", + "setupsensor": 0, "signalsonroute": [ 11, 10 @@ -5578,7 +5605,7 @@ 20, 5 ], - "bbox": 5187, + "bbox": 5191, "colour": "black", "endx": 1650.0, "endy": 375.0, @@ -5596,7 +5623,7 @@ 0, 0 ], - "bbox": 5193, + "bbox": 5197, "colour": "black", "endx": 575.0, "endy": 275.0, @@ -5619,7 +5646,7 @@ false, "" ], - "bbox": 5212, + "bbox": 5216, "dccaspects": [ [ [ @@ -6526,7 +6553,7 @@ 0, 0 ], - "bbox": 5218, + "bbox": 5222, "colour": "black", "endx": 1075.0, "endy": 200.0, @@ -6540,7 +6567,7 @@ "47a9ea55-623c-48d0-af0d-6a4be22cc19f": { "alsoswitch": 6, "automatic": false, - "bbox": 5225, + "bbox": 5229, "colour": "black", "dccaddress": 102, "dccreversed": false, @@ -6616,7 +6643,7 @@ 0, 0 ], - "bbox": 5231, + "bbox": 5235, "colour": "black", "endx": 1125.0, "endy": 375.0, @@ -6634,7 +6661,7 @@ 0, 0 ], - "bbox": 5237, + "bbox": 5241, "colour": "black", "endx": 650.0, "endy": 375.0, @@ -6646,7 +6673,8 @@ "tags": "line14" }, "55da5108-9de2-4e68-ab11-d0dd6e32a222": { - "bbox": 5241, + "bbox": 5245, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 5, @@ -6674,6 +6702,7 @@ "routecolour": "#ff6100", "routedescription": "Down Main arrival into Platform 3", "routename": "Down to Platform 3", + "setupsensor": 0, "signalsonroute": [ 5 ], @@ -6689,7 +6718,7 @@ 0, 0 ], - "bbox": 5247, + "bbox": 5251, "colour": "black", "endx": 1150.0, "endy": 275.0, @@ -6702,22 +6731,24 @@ }, "58ab1e60-0f14-4c1a-b97c-63a46790c987": { "background": "#d9d9b1", - "bbox": 5250, + "bbox": 5254, "border": 2, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 6, "justify": 2, "posx": 900.0, "posy": 461.0, - "tags": "58ab1e60-0f14-4c1a-b97c-63a46790c987", + "tags": "textbox6", "text": " Platform 1 \n " }, "5b638f9c-8e40-4fa7-ae68-ff0b9c8d16bf": { - "bbox": 5254, + "bbox": 5258, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 21, @@ -6745,6 +6776,7 @@ "routecolour": "#c800ff", "routedescription": "Up Main arrival into platform 3", "routename": "Up to Platform 3", + "setupsensor": 0, "signalsonroute": [ 6 ], @@ -6756,7 +6788,7 @@ "5e8f3ed5-e412-4861-a3d8-499d7f467e80": { "alsoswitch": 0, "automatic": true, - "bbox": 5260, + "bbox": 5264, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -6786,7 +6818,7 @@ false, "" ], - "bbox": 5271, + "bbox": 5275, "dccaspects": [ [ [ @@ -7689,7 +7721,7 @@ "5fd84d01-3fc8-4592-923c-968e38f13c80": { "alsoswitch": 0, "automatic": true, - "bbox": 5276, + "bbox": 5280, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -7719,7 +7751,7 @@ false, "" ], - "bbox": 5293, + "bbox": 5297, "dccaspects": [ [ [ @@ -8584,7 +8616,7 @@ 1, 1 ], - "bbox": 5299, + "bbox": 5303, "colour": "black", "endx": 700.0, "endy": 150.0, @@ -8596,9 +8628,10 @@ "tags": "line23" }, "6175fcca-1121-4c31-b8f5-f6318d2b570e": { - "bbox": 5303, + "bbox": 5307, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 12, "mirror": "21", @@ -8607,7 +8640,8 @@ "tags": "section12" }, "67046282-f9b4-4d33-9c90-0a23531fc6b2": { - "bbox": 5307, + "bbox": 5311, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 11, @@ -8632,6 +8666,7 @@ "routecolour": "#0000ff", "routedescription": "Branch arrival into Goods loop", "routename": "Branch to Goods", + "setupsensor": 0, "signalsonroute": [ 14, 13 @@ -8648,7 +8683,7 @@ 0, 0 ], - "bbox": 5313, + "bbox": 5317, "colour": "black", "endx": 900.0, "endy": 200.0, @@ -8666,7 +8701,7 @@ 0, 0 ], - "bbox": 5319, + "bbox": 5323, "colour": "black", "endx": 1700.0, "endy": 375.0, @@ -8684,7 +8719,7 @@ 0, 0 ], - "bbox": 5325, + "bbox": 5329, "colour": "black", "endx": 1150.0, "endy": 400.0, @@ -8707,7 +8742,7 @@ false, "" ], - "bbox": 5344, + "bbox": 5348, "dccaspects": [ [ [ @@ -9654,7 +9689,7 @@ 20, 5 ], - "bbox": 5350, + "bbox": 5354, "colour": "black", "endx": 150.0, "endy": 325.0, @@ -9666,9 +9701,10 @@ "tags": "line30" }, "7189da3b-f611-493b-b453-4c18190325b3": { - "bbox": 5354, + "bbox": 5358, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 4, "mirror": "", @@ -9677,9 +9713,10 @@ "tags": "section4" }, "72d9a7ab-03e3-4862-9edc-e4b1b0669d8a": { - "bbox": 5358, + "bbox": 5362, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 19, "mirror": "1", @@ -9688,9 +9725,10 @@ "tags": "section19" }, "75c0f7e2-4c9c-4b9e-91f3-0046cea86d2d": { - "bbox": 5362, + "bbox": 5366, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 1, "mirror": "19", @@ -9705,7 +9743,7 @@ 0, 0 ], - "bbox": 5368, + "bbox": 5372, "colour": "black", "endx": 500.0, "endy": 325.0, @@ -9719,7 +9757,7 @@ "771fd5da-2c58-423c-9b29-d181501dca9b": { "alsoswitch": 0, "automatic": true, - "bbox": 5373, + "bbox": 5377, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -9744,7 +9782,7 @@ 20, 5 ], - "bbox": 5379, + "bbox": 5383, "colour": "black", "endx": 1675.0, "endy": 325.0, @@ -9756,9 +9794,10 @@ "tags": "line5" }, "78c8911f-a18a-41bf-961d-3a43893854d4": { - "bbox": 5383, + "bbox": 5387, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 11, "mirror": "", @@ -9768,24 +9807,25 @@ }, "7aa34fb1-3039-4be1-b71a-9d85fa38f112": { "background": "grey85", - "bbox": 5386, + "bbox": 5390, "border": 0, "colour": "black", "font": "Courier", "fontsize": 18, "fontstyle": "bold underline ", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 7, "justify": 2, "posx": 900.0, "posy": 50.0, - "tags": "7aa34fb1-3039-4be1-b71a-9d85fa38f112", + "tags": "textbox7", "text": "My Layout" }, "7f925596-94a5-43a6-b260-ce4bd63caaad": { "alsoswitch": 0, "automatic": true, - "bbox": 5391, + "bbox": 5395, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -9810,7 +9850,7 @@ 0, 0 ], - "bbox": 5397, + "bbox": 5401, "colour": "black", "endx": 900.0, "endy": 325.0, @@ -9823,34 +9863,36 @@ }, "846b5450-3bb8-443d-9552-644e650b10dd": { "background": "#d9d9b1", - "bbox": 5400, + "bbox": 5404, "border": 2, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 8, "justify": 2, "posx": 899.0, "posy": 288.0, - "tags": "846b5450-3bb8-443d-9552-644e650b10dd", + "tags": "textbox8", "text": " Platform 3 \n Platform 2 " }, "84a2cb6e-79ed-42c2-ae61-55e5c92be7db": { "background": "grey85", - "bbox": 5403, + "bbox": 5407, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "bold ", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 9, "justify": 2, "posx": 1724.0, "posy": 275.0, - "tags": "84a2cb6e-79ed-42c2-ae61-55e5c92be7db", + "tags": "textbox9", "text": "BRANCH" }, "84b2145a-5eb7-4b8c-881d-b1c18e146b65": { @@ -9865,7 +9907,7 @@ false, "" ], - "bbox": 5414, + "bbox": 5418, "dccaspects": [ [ [ @@ -10766,9 +10808,10 @@ ] }, "857bc7b3-6dd9-4fe8-a800-04b0845c77f2": { - "bbox": 5418, + "bbox": 5422, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 16, "mirror": "", @@ -10788,7 +10831,7 @@ false, "" ], - "bbox": 5435, + "bbox": 5439, "dccaspects": [ [ [ @@ -11664,7 +11707,7 @@ 0, 0 ], - "bbox": 5441, + "bbox": 5445, "colour": "black", "endx": 1700.0, "endy": 275.0, @@ -11682,7 +11725,7 @@ 20, 5 ], - "bbox": 5447, + "bbox": 5451, "colour": "black", "endx": 111.0, "endy": 275.0, @@ -11700,7 +11743,7 @@ 20, 5 ], - "bbox": 5453, + "bbox": 5457, "colour": "black", "endx": 1687.0, "endy": 275.0, @@ -11713,22 +11756,24 @@ }, "93d61cb6-a272-45a4-be73-7fcd8ae70932": { "background": "grey85", - "bbox": 5456, + "bbox": 5460, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "bold ", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 10, "justify": 2, "posx": 76.0, "posy": 375.0, - "tags": "93d61cb6-a272-45a4-be73-7fcd8ae70932", + "tags": "textbox10", "text": "DOWN\nMAIN" }, "99bd153e-9f0c-4134-9611-853fa6740f25": { - "bbox": 5460, + "bbox": 5464, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 2, @@ -11752,6 +11797,7 @@ "routecolour": "#ff6100", "routedescription": "Down Main arrival into Down Avoiding loop", "routename": "Down to Avoiding", + "setupsensor": 0, "signalsonroute": [ 5 ], @@ -11761,7 +11807,8 @@ "tracksensor": 0 }, "9aec57b5-85f1-4118-b3e2-4c145e6ffaa8": { - "bbox": 5464, + "bbox": 5468, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 18, @@ -11788,6 +11835,7 @@ "routecolour": "#0000ff", "routedescription": "Branch arrival into goods loop", "routename": "Branch to Goods", + "setupsensor": 0, "signalsonroute": [ 11, 10 @@ -11800,7 +11848,7 @@ "9dcab729-9273-4ede-9329-f6adc2ff05fb": { "alsoswitch": 17, "automatic": false, - "bbox": 5471, + "bbox": 5475, "colour": "black", "dccaddress": 103, "dccreversed": true, @@ -11840,7 +11888,8 @@ "ybuttonoffset": 0 }, "9f2c0ce4-5515-4591-944c-b719fe662513": { - "bbox": 5475, + "bbox": 5479, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 15, @@ -11864,6 +11913,7 @@ "routecolour": "#0000ff", "routedescription": "Platform 3 departure to branch", "routename": "Platform 3 to Branch", + "setupsensor": 0, "signalsonroute": [ 7, 21 @@ -11880,7 +11930,7 @@ 1, 1 ], - "bbox": 5481, + "bbox": 5485, "colour": "black", "endx": 650.0, "endy": 425.0, @@ -11893,24 +11943,25 @@ }, "a824704b-6fde-49ba-ba05-933b6f7fe189": { "background": "grey85", - "bbox": 5484, + "bbox": 5488, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "bold ", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 11, "justify": 2, "posx": 75.0, "posy": 327.0, - "tags": "a824704b-6fde-49ba-ba05-933b6f7fe189", + "tags": "textbox11", "text": "UP\nMAIN" }, "b0f29657-f391-457d-85b7-6a5ec41b0d13": { "alsoswitch": 12, "automatic": false, - "bbox": 5491, + "bbox": 5495, "colour": "black", "dccaddress": 100, "dccreversed": false, @@ -11980,7 +12031,8 @@ "ybuttonoffset": 0 }, "b118ca35-20db-4e0e-881a-e22db97c1b66": { - "bbox": 5495, + "bbox": 5499, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 14, @@ -12007,6 +12059,7 @@ "routecolour": "#0000ff", "routedescription": "Goods Loop departure to branch", "routename": "Goods to Branch", + "setupsensor": 0, "signalsonroute": [ 1, 21 @@ -12028,7 +12081,7 @@ false, "" ], - "bbox": 5512, + "bbox": 5516, "dccaspects": [ [ [ @@ -12898,9 +12951,10 @@ ] }, "b17fb551-2bfe-4d2a-9429-d2d94005c0c5": { - "bbox": 5516, + "bbox": 5520, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 20, "mirror": "", @@ -12911,7 +12965,7 @@ "b34426dc-7315-4c73-b36a-b2c97b9c94c7": { "alsoswitch": 0, "automatic": true, - "bbox": 5523, + "bbox": 5527, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -12941,7 +12995,7 @@ false, "" ], - "bbox": 5540, + "bbox": 5544, "dccaspects": [ [ [ @@ -13811,7 +13865,7 @@ false, "" ], - "bbox": 5557, + "bbox": 5561, "dccaspects": [ [ [ @@ -14670,7 +14724,8 @@ ] }, "b7d81f2a-7d03-42f7-9125-a3559433f934": { - "bbox": 5561, + "bbox": 5565, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 22, @@ -14694,6 +14749,7 @@ "routecolour": "#c800ff", "routedescription": "Up Main arrival into Platform 2", "routename": "Up to Platform 2", + "setupsensor": 0, "signalsonroute": [ 6 ], @@ -14703,7 +14759,8 @@ "tracksensor": 0 }, "b7f5f250-60f7-4678-95b8-0c8e50417a04": { - "bbox": 5565, + "bbox": 5569, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 13, @@ -14728,6 +14785,7 @@ "routecolour": "#0000ff", "routedescription": "Branch line arrival into Platform 2", "routename": "Branch to Platform 3", + "setupsensor": 0, "signalsonroute": [ 14, 13 @@ -14738,7 +14796,8 @@ "tracksensor": 0 }, "b9e52658-031c-49c8-a7c1-c18c6bf6d8e1": { - "bbox": 5569, + "bbox": 5573, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 4, @@ -14762,6 +14821,7 @@ "routecolour": "#ff6100", "routedescription": "Avoiding Loop departure to Down Main", "routename": "Avoiding to Down", + "setupsensor": 0, "signalsonroute": [ 9 ], @@ -14771,7 +14831,8 @@ "tracksensor": 0 }, "bc306663-e528-4e0e-b65b-a32754ee91d5": { - "bbox": 5573, + "bbox": 5577, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 10, @@ -14796,6 +14857,7 @@ "routecolour": "#0000ff", "routedescription": "Goods Loop departure to branch", "routename": "Goods to Branch", + "setupsensor": 0, "signalsonroute": [ 2, 22 @@ -14806,9 +14868,10 @@ "tracksensor": 0 }, "bc6fbf64-cd5a-418a-98b7-2e0beeb7c64e": { - "bbox": 5577, + "bbox": 5581, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 18, "mirror": "", @@ -14828,7 +14891,7 @@ false, "" ], - "bbox": 5594, + "bbox": 5598, "dccaspects": [ [ [ @@ -15687,9 +15750,10 @@ ] }, "bf63c705-72aa-4cc3-8d20-bbb1c69bb297": { - "bbox": 5598, + "bbox": 5602, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 14, "mirror": "", @@ -15704,7 +15768,7 @@ 0, 0 ], - "bbox": 5604, + "bbox": 5608, "colour": "black", "endx": 900.0, "endy": 250.0, @@ -15718,7 +15782,7 @@ "c867d2f6-3d89-490c-a3f9-b8cdd535ae99": { "alsoswitch": 18, "automatic": false, - "bbox": 5610, + "bbox": 5614, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -15758,9 +15822,10 @@ "ybuttonoffset": 0 }, "c9d70646-364e-454c-95de-978d995c6c8e": { - "bbox": 5614, + "bbox": 5618, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 3, "mirror": "", @@ -15780,7 +15845,7 @@ false, "" ], - "bbox": 5631, + "bbox": 5635, "dccaspects": [ [ [ @@ -16676,7 +16741,7 @@ 0, 0 ], - "bbox": 5637, + "bbox": 5641, "colour": "black", "endx": 1175.0, "endy": 325.0, @@ -16694,7 +16759,7 @@ 20, 5 ], - "bbox": 5643, + "bbox": 5647, "colour": "black", "endx": 1637.0, "endy": 275.0, @@ -16707,18 +16772,19 @@ }, "cc0ad700-89e3-486c-8ad0-e9a05ea0556d": { "background": "grey85", - "bbox": 5646, + "bbox": 5650, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "bold ", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 12, "justify": 2, "posx": 1275.0, "posy": 180.0, - "tags": "cc0ad700-89e3-486c-8ad0-e9a05ea0556d", + "tags": "textbox12", "text": "Headshunt" }, "cd5c7c03-c9da-44fd-9909-24219cd7c59b": { @@ -16728,7 +16794,7 @@ 0, 0 ], - "bbox": 5652, + "bbox": 5656, "colour": "black", "endx": 650.0, "endy": 250.0, @@ -16740,9 +16806,10 @@ "tags": "line8" }, "ceebbc85-e329-4185-a57f-e9230865a45c": { - "bbox": 5656, + "bbox": 5660, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 13, "mirror": "", @@ -16753,7 +16820,7 @@ "d433fb55-7016-4bc9-ac60-9cf142605fa8": { "alsoswitch": 13, "automatic": false, - "bbox": 5663, + "bbox": 5667, "colour": "black", "dccaddress": 101, "dccreversed": false, @@ -16813,9 +16880,10 @@ "ybuttonoffset": 0 }, "d4a3a8dc-b0f1-4ada-b934-36b9646a17b7": { - "bbox": 5667, + "bbox": 5671, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 10, "mirror": "", @@ -16825,24 +16893,25 @@ }, "ddb09cbe-5c7d-4810-b27b-0b64e588e56b": { "background": "grey85", - "bbox": 5670, + "bbox": 5674, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "bold ", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 13, "justify": 2, "posx": 1725.0, "posy": 327.0, - "tags": "ddb09cbe-5c7d-4810-b27b-0b64e588e56b", + "tags": "textbox13", "text": "UP\nMAIN" }, "dec5f273-15d3-4ba7-a029-16028a9936e9": { "alsoswitch": 0, "automatic": true, - "bbox": 5675, + "bbox": 5679, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -16867,7 +16936,7 @@ 0, 0 ], - "bbox": 5681, + "bbox": 5685, "colour": "black", "endx": 1125.0, "endy": 150.0, @@ -16879,9 +16948,10 @@ "tags": "line21" }, "e234b7a0-f5c6-48bb-a9d3-cfeb92adf401": { - "bbox": 5685, + "bbox": 5689, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 21, "mirror": "12", @@ -16890,7 +16960,8 @@ "tags": "section21" }, "e2c437bb-ec9b-46d1-bf27-0a9b5467f77d": { - "bbox": 5689, + "bbox": 5693, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 16, @@ -16918,6 +16989,7 @@ "routecolour": "#ff6100", "routedescription": "Platform 3 departure to Down Main", "routename": "Platform 3 to Down", + "setupsensor": 0, "signalsonroute": [ 7 ], @@ -16933,7 +17005,7 @@ 1, 1 ], - "bbox": 5695, + "bbox": 5699, "colour": "black", "endx": 1325.0, "endy": 150.0, @@ -16947,7 +17019,7 @@ "e5eea518-6f34-47d9-8a17-ca314e06a52d": { "alsoswitch": 0, "automatic": true, - "bbox": 5700, + "bbox": 5704, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -16977,7 +17049,7 @@ false, "" ], - "bbox": 5719, + "bbox": 5723, "dccaspects": [ [ [ @@ -17909,7 +17981,7 @@ false, "" ], - "bbox": 5736, + "bbox": 5740, "dccaspects": [ [ [ @@ -18770,7 +18842,7 @@ "eaed2508-4646-46cc-83fa-b9ef8ee2848b": { "alsoswitch": 7, "automatic": false, - "bbox": 5743, + "bbox": 5747, "colour": "black", "dccaddress": 107, "dccreversed": false, @@ -18846,7 +18918,7 @@ 0, 0 ], - "bbox": 5749, + "bbox": 5753, "colour": "black", "endx": 900.0, "endy": 375.0, @@ -18864,7 +18936,7 @@ 0, 0 ], - "bbox": 5755, + "bbox": 5759, "colour": "black", "endx": 1075.0, "endy": 250.0, @@ -18876,7 +18948,8 @@ "tags": "line3" }, "f4875d39-1482-47d9-a37d-3b58035e94ad": { - "bbox": 5759, + "bbox": 5763, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 8, @@ -18903,6 +18976,7 @@ "routecolour": "#c800ff", "routedescription": "Platform 3 departure to Up Main", "routename": "Platform 3 to Up", + "setupsensor": 0, "signalsonroute": [ 3 ], @@ -18923,7 +18997,7 @@ false, "" ], - "bbox": 5776, + "bbox": 5780, "dccaspects": [ [ [ @@ -19813,7 +19887,8 @@ ] }, "f85bced4-b684-4738-a166-c8b978efeb6d": { - "bbox": 5780, + "bbox": 5784, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 9, @@ -19836,6 +19911,7 @@ "routecolour": "#c800ff", "routedescription": "Platform 2 departure to Up Main", "routename": "Platform 2 to Up", + "setupsensor": 0, "signalsonroute": [ 4 ], @@ -19856,7 +19932,7 @@ false, "" ], - "bbox": 5799, + "bbox": 5803, "dccaspects": [ [ [ @@ -20784,7 +20860,7 @@ 0, 0 ], - "bbox": 5805, + "bbox": 5809, "colour": "black", "endx": 1200.0, "endy": 375.0, @@ -20980,11 +21056,12 @@ }, "general": { "automation": true, + "buttonsize": 8, "editmode": false, "filename": "/home/john/model-railway-signalling/configuration_examples/my_layout.sig", "info": "Document your layout here", "spadpopups": false, - "version": "Version 4.6.1" + "version": "Version 4.7.0" }, "gpio": { "portmappings": [ diff --git a/configuration_examples/schematic_routes_example.sig b/configuration_examples/schematic_routes_example.sig index a4459202..25834588 100644 --- a/configuration_examples/schematic_routes_example.sig +++ b/configuration_examples/schematic_routes_example.sig @@ -71,7 +71,8 @@ "instruments": {}, "objects": { "010e5fa1-fd7a-4068-a751-d214c9950ae4": { - "bbox": 5904, + "bbox": 5908, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 3, @@ -96,6 +97,7 @@ "routecolour": "#ff6100", "routedescription": "Platform 1 departure to Down Main", "routename": "Platform 1 to Down", + "setupsensor": 0, "signalsonroute": [ 8 ], @@ -105,9 +107,10 @@ "tracksensor": 5 }, "0148c4d4-8a20-45c2-9a32-a1eb5e532d9f": { - "bbox": 5908, + "bbox": 5912, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 7, "mirror": "", @@ -122,7 +125,7 @@ 0, 0 ], - "bbox": 5914, + "bbox": 5918, "colour": "black", "endx": 900.0, "endy": 425.0, @@ -135,18 +138,19 @@ }, "02aaa6bf-c249-45ed-965f-047262cad8a5": { "background": "grey85", - "bbox": 5917, + "bbox": 5921, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "bold ", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 1, "justify": 2, "posx": 51.0, "posy": 275.0, - "tags": "02aaa6bf-c249-45ed-965f-047262cad8a5", + "tags": "textbox1", "text": "BRANCH" }, "031f9ebb-3dbb-4496-bd2e-da1f19292ffc": { @@ -156,7 +160,7 @@ 20, 5 ], - "bbox": 5923, + "bbox": 5927, "colour": "black", "endx": 150.0, "endy": 275.0, @@ -170,7 +174,7 @@ "058012a1-4cb8-40e0-9c67-55c0139c064a": { "alsoswitch": 2, "automatic": false, - "bbox": 5930, + "bbox": 5934, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -251,7 +255,7 @@ false, "" ], - "bbox": 5947, + "bbox": 5951, "dccaspects": [ [ [ @@ -1112,7 +1116,7 @@ "05daf194-7943-4c0b-8540-b574716ce9ca": { "alsoswitch": 1, "automatic": false, - "bbox": 5954, + "bbox": 5958, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -1178,7 +1182,7 @@ 0, 0 ], - "bbox": 5960, + "bbox": 5964, "colour": "black", "endx": 1725.0, "endy": 325.0, @@ -1201,7 +1205,7 @@ false, "" ], - "bbox": 5977, + "bbox": 5981, "dccaspects": [ [ [ @@ -2122,7 +2126,8 @@ ] }, "0950163c-9b77-46c1-84c9-5ad56088c55a": { - "bbox": 5981, + "bbox": 5985, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 1, @@ -2147,6 +2152,7 @@ "routecolour": "#ff6100", "routedescription": "Down Main arrival into Platform 1", "routename": "Down to Platform 1", + "setupsensor": 0, "signalsonroute": [ 5 ], @@ -2157,22 +2163,24 @@ }, "0981c3fb-63d1-4cc4-bcdb-c89612f58ed6": { "background": "grey85", - "bbox": 5984, + "bbox": 5988, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "bold ", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 2, "justify": 2, "posx": 548.0, "posy": 181.0, - "tags": "0981c3fb-63d1-4cc4-bcdb-c89612f58ed6", + "tags": "textbox2", "text": "TMD" }, "0add39ae-8bc2-44ca-adb4-d2a8794adcc4": { - "bbox": 5988, + "bbox": 5992, + "hidden": false, "item": "tracksensor", "itemid": 7, "passedsensor": "", @@ -2475,9 +2483,10 @@ "tags": "sensor7" }, "0bcc8fee-02a4-485d-a1f4-b8b935e35504": { - "bbox": 5992, + "bbox": 5996, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 15, "mirror": "", @@ -2486,9 +2495,10 @@ "tags": "section15" }, "0f2a2cb4-f289-4874-992d-4e53cc1498a7": { - "bbox": 5996, + "bbox": 6000, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 19, "mirror": "", @@ -2508,7 +2518,7 @@ false, "" ], - "bbox": 6007, + "bbox": 6011, "dccaspects": [ [ [ @@ -3398,7 +3408,8 @@ ] }, "1625ee65-272d-41ec-a9ec-2832f28fbbe7": { - "bbox": 6011, + "bbox": 6015, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 17, @@ -3430,6 +3441,7 @@ "routecolour": "#ff6100", "routedescription": "Goods loop departure to Down Main", "routename": "Goods to Down", + "setupsensor": 0, "signalsonroute": [ 1 ], @@ -3441,7 +3453,7 @@ "1ab8c048-bc7a-4a3b-a14c-b02afcafcaa4": { "alsoswitch": 0, "automatic": true, - "bbox": 6016, + "bbox": 6020, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -3471,7 +3483,7 @@ false, "" ], - "bbox": 6033, + "bbox": 6037, "dccaspects": [ [ [ @@ -4382,7 +4394,8 @@ ] }, "1f8a59e9-5c19-4a50-a9ec-c9fac51c71c1": { - "bbox": 6037, + "bbox": 6041, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 12, @@ -4406,6 +4419,7 @@ "routecolour": "#0000ff", "routedescription": "Platform 2 departure to branch", "routename": "Platform 3 to Branch", + "setupsensor": 0, "signalsonroute": [ 3, 22 @@ -4427,7 +4441,7 @@ false, "" ], - "bbox": 6056, + "bbox": 6060, "dccaspects": [ [ [ @@ -5376,7 +5390,7 @@ 1, 1 ], - "bbox": 6062, + "bbox": 6066, "colour": "black", "endx": 1325.0, "endy": 200.0, @@ -5388,9 +5402,10 @@ "tags": "line17" }, "204a676c-058b-43ed-bc79-46fd8ca770cb": { - "bbox": 6066, + "bbox": 6070, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 12, "mirror": "", @@ -5399,9 +5414,10 @@ "tags": "section12" }, "22a63804-92ab-4d6a-89fb-c5b3eed7e475": { - "bbox": 6070, + "bbox": 6074, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 8, "mirror": "", @@ -5412,7 +5428,7 @@ "25bde8e4-dc58-4917-b214-70b236a0e316": { "alsoswitch": 4, "automatic": false, - "bbox": 6076, + "bbox": 6080, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -5484,7 +5500,7 @@ "2660e87b-743d-4bf5-b5c9-33eb079c533c": { "alsoswitch": 0, "automatic": true, - "bbox": 6081, + "bbox": 6085, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -5503,9 +5519,10 @@ "ybuttonoffset": 0 }, "280ffb82-b4eb-49d3-96d3-9de9160072b7": { - "bbox": 6085, + "bbox": 6089, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 6, "mirror": "", @@ -5520,7 +5537,7 @@ 1, 1 ], - "bbox": 6091, + "bbox": 6095, "colour": "black", "endx": 650.0, "endy": 200.0, @@ -5538,7 +5555,7 @@ 20, 5 ], - "bbox": 6097, + "bbox": 6101, "colour": "black", "endx": 100.0, "endy": 375.0, @@ -5550,7 +5567,8 @@ "tags": "line31" }, "2bead2aa-671c-4699-9f91-4f2ec453fe9f": { - "bbox": 6101, + "bbox": 6105, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 6, @@ -5578,6 +5596,7 @@ "routecolour": "#c800ff", "routedescription": "Goods loop departure to Up Main", "routename": "Goods to Up", + "setupsensor": 0, "signalsonroute": [ 2 ], @@ -5588,38 +5607,41 @@ }, "2ea41ff5-d1c7-4c38-bb25-53eeadc25735": { "background": "grey85", - "bbox": 6104, + "bbox": 6108, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "bold ", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 3, "justify": 2, "posx": 1751.0, "posy": 375.0, - "tags": "2ea41ff5-d1c7-4c38-bb25-53eeadc25735", + "tags": "textbox3", "text": "DOWN\nMAIN" }, "2fcbbfe6-7a94-4913-b572-8ddad96c76a3": { "background": "grey85", - "bbox": 6107, + "bbox": 6111, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "bold ", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 4, "justify": 2, "posx": 1272.0, "posy": 130.0, - "tags": "2fcbbfe6-7a94-4913-b572-8ddad96c76a3", + "tags": "textbox4", "text": "Goods Yard" }, "333315c4-06d2-4914-bfcb-48af3597e851": { - "bbox": 6111, + "bbox": 6115, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 7, @@ -5648,6 +5670,7 @@ "routecolour": "#ff6100", "routedescription": "Down Main arrival into goods loop", "routename": "Down to Goods", + "setupsensor": 0, "signalsonroute": [ 5 ], @@ -5658,18 +5681,19 @@ }, "359d1564-4a52-49ef-81e0-a72df0f02220": { "background": "grey85", - "bbox": 6114, + "bbox": 6118, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "bold ", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 5, "justify": 2, "posx": 550.0, "posy": 130.0, - "tags": "359d1564-4a52-49ef-81e0-a72df0f02220", + "tags": "textbox5", "text": "Headshunt" }, "36d05755-f972-442a-9139-8739e35dadea": { @@ -5679,7 +5703,7 @@ 0, 0 ], - "bbox": 6120, + "bbox": 6124, "colour": "black", "endx": 575.0, "endy": 325.0, @@ -5691,9 +5715,10 @@ "tags": "line10" }, "37716476-045e-4efc-a8d0-dc30b5f9ccbb": { - "bbox": 6124, + "bbox": 6128, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 5, "mirror": "", @@ -5704,7 +5729,7 @@ "37fbb878-e6fd-4629-96f8-5912ec2ab8d3": { "alsoswitch": 0, "automatic": false, - "bbox": 6131, + "bbox": 6135, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -5745,7 +5770,7 @@ false, "" ], - "bbox": 6150, + "bbox": 6154, "dccaspects": [ [ [ @@ -6652,7 +6677,7 @@ 20, 5 ], - "bbox": 6156, + "bbox": 6160, "colour": "black", "endx": 500.0, "endy": 375.0, @@ -6670,7 +6695,7 @@ 0, 0 ], - "bbox": 6162, + "bbox": 6166, "colour": "black", "endx": 1125.0, "endy": 425.0, @@ -6682,7 +6707,8 @@ "tags": "line13" }, "3b5c8ec9-898a-4724-8b45-fb33b6158df6": { - "bbox": 6166, + "bbox": 6170, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 20, @@ -6713,6 +6739,7 @@ "routecolour": "#c800ff", "routedescription": "Up Main arrival into goods loop", "routename": "Up to Goods", + "setupsensor": 0, "signalsonroute": [ 6 ], @@ -6722,9 +6749,10 @@ "tracksensor": 7 }, "3b9c9266-3dfe-4349-84a3-c7b9b9185cae": { - "bbox": 6170, + "bbox": 6174, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 17, "mirror": "", @@ -6733,7 +6761,8 @@ "tags": "section17" }, "3ed6eb67-18ea-4f66-b2f0-e12664a6d90f": { - "bbox": 6174, + "bbox": 6178, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 19, @@ -6757,6 +6786,7 @@ "routecolour": "#0000ff", "routedescription": "Branch Arrival into Platform 3", "routename": "Branch to Platform 3", + "setupsensor": 0, "signalsonroute": [ 11, 10 @@ -6773,7 +6803,7 @@ 20, 5 ], - "bbox": 6180, + "bbox": 6184, "colour": "black", "endx": 1650.0, "endy": 375.0, @@ -6785,9 +6815,10 @@ "tags": "line25" }, "41798b7f-8f72-4a52-9aa0-8c13ac1f7292": { - "bbox": 6184, + "bbox": 6188, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 2, "mirror": "", @@ -6802,7 +6833,7 @@ 0, 0 ], - "bbox": 6190, + "bbox": 6194, "colour": "black", "endx": 575.0, "endy": 275.0, @@ -6825,7 +6856,7 @@ false, "" ], - "bbox": 6209, + "bbox": 6213, "dccaspects": [ [ [ @@ -7732,7 +7763,7 @@ 0, 0 ], - "bbox": 6215, + "bbox": 6219, "colour": "black", "endx": 1075.0, "endy": 200.0, @@ -7746,7 +7777,7 @@ "47a9ea55-623c-48d0-af0d-6a4be22cc19f": { "alsoswitch": 6, "automatic": false, - "bbox": 6222, + "bbox": 6226, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -7822,7 +7853,7 @@ 0, 0 ], - "bbox": 6228, + "bbox": 6232, "colour": "black", "endx": 1125.0, "endy": 375.0, @@ -7840,7 +7871,7 @@ 0, 0 ], - "bbox": 6234, + "bbox": 6238, "colour": "black", "endx": 650.0, "endy": 375.0, @@ -7852,7 +7883,8 @@ "tags": "line14" }, "5424f96f-2ac5-49c0-9ea4-f8c51e696e02": { - "bbox": 6238, + "bbox": 6242, + "hidden": false, "item": "tracksensor", "itemid": 4, "passedsensor": "", @@ -8155,9 +8187,10 @@ "tags": "sensor4" }, "550b7d1a-fb0a-47f1-8361-9699e60514e9": { - "bbox": 6242, + "bbox": 6246, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 22, "mirror": "", @@ -8166,7 +8199,8 @@ "tags": "section22" }, "55da5108-9de2-4e68-ab11-d0dd6e32a222": { - "bbox": 6246, + "bbox": 6250, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 5, @@ -8194,6 +8228,7 @@ "routecolour": "#ff6100", "routedescription": "Down Main arrival into Platform 3", "routename": "Down to Platform 3", + "setupsensor": 0, "signalsonroute": [ 5 ], @@ -8209,7 +8244,7 @@ 0, 0 ], - "bbox": 6252, + "bbox": 6256, "colour": "black", "endx": 1150.0, "endy": 275.0, @@ -8222,22 +8257,24 @@ }, "58ab1e60-0f14-4c1a-b97c-63a46790c987": { "background": "#d9d9b1", - "bbox": 6255, + "bbox": 6259, "border": 2, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 6, "justify": 2, "posx": 900.0, "posy": 461.0, - "tags": "58ab1e60-0f14-4c1a-b97c-63a46790c987", + "tags": "textbox6", "text": " Platform 1 \n " }, "5b638f9c-8e40-4fa7-ae68-ff0b9c8d16bf": { - "bbox": 6259, + "bbox": 6263, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 21, @@ -8265,6 +8302,7 @@ "routecolour": "#c800ff", "routedescription": "Up Main arrival into platform 3", "routename": "Up to Platform 3", + "setupsensor": 0, "signalsonroute": [ 6 ], @@ -8274,7 +8312,8 @@ "tracksensor": 9 }, "5c14b604-4602-4fd7-8128-449eba7dea46": { - "bbox": 6263, + "bbox": 6267, + "hidden": false, "item": "tracksensor", "itemid": 1, "passedsensor": "", @@ -8579,7 +8618,7 @@ "5e8f3ed5-e412-4861-a3d8-499d7f467e80": { "alsoswitch": 0, "automatic": true, - "bbox": 6269, + "bbox": 6273, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -8609,7 +8648,7 @@ false, "" ], - "bbox": 6280, + "bbox": 6284, "dccaspects": [ [ [ @@ -9512,7 +9551,7 @@ "5fd84d01-3fc8-4592-923c-968e38f13c80": { "alsoswitch": 0, "automatic": true, - "bbox": 6285, + "bbox": 6289, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -9542,7 +9581,7 @@ false, "" ], - "bbox": 6302, + "bbox": 6306, "dccaspects": [ [ [ @@ -10407,7 +10446,7 @@ 1, 1 ], - "bbox": 6308, + "bbox": 6312, "colour": "black", "endx": 700.0, "endy": 150.0, @@ -10419,7 +10458,8 @@ "tags": "line23" }, "67046282-f9b4-4d33-9c90-0a23531fc6b2": { - "bbox": 6312, + "bbox": 6316, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 11, @@ -10444,6 +10484,7 @@ "routecolour": "#0000ff", "routedescription": "Branch arrival into Goods loop", "routename": "Branch to Goods", + "setupsensor": 0, "signalsonroute": [ 14, 13 @@ -10460,7 +10501,7 @@ 0, 0 ], - "bbox": 6318, + "bbox": 6322, "colour": "black", "endx": 900.0, "endy": 200.0, @@ -10478,7 +10519,7 @@ 0, 0 ], - "bbox": 6324, + "bbox": 6328, "colour": "black", "endx": 1725.0, "endy": 375.0, @@ -10496,7 +10537,7 @@ 0, 0 ], - "bbox": 6330, + "bbox": 6334, "colour": "black", "endx": 1150.0, "endy": 400.0, @@ -10519,7 +10560,7 @@ false, "" ], - "bbox": 6349, + "bbox": 6353, "dccaspects": [ [ [ @@ -11466,7 +11507,7 @@ 20, 5 ], - "bbox": 6355, + "bbox": 6359, "colour": "black", "endx": 125.0, "endy": 325.0, @@ -11478,9 +11519,10 @@ "tags": "line30" }, "7189da3b-f611-493b-b453-4c18190325b3": { - "bbox": 6359, + "bbox": 6363, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 4, "mirror": "", @@ -11489,7 +11531,8 @@ "tags": "section4" }, "71aa37e6-dd5f-402d-a676-bc25644633b1": { - "bbox": 6363, + "bbox": 6367, + "hidden": false, "item": "tracksensor", "itemid": 9, "passedsensor": "", @@ -11798,7 +11841,7 @@ 0, 0 ], - "bbox": 6369, + "bbox": 6373, "colour": "black", "endx": 500.0, "endy": 325.0, @@ -11812,7 +11855,7 @@ "771fd5da-2c58-423c-9b29-d181501dca9b": { "alsoswitch": 0, "automatic": true, - "bbox": 6374, + "bbox": 6378, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -11837,7 +11880,7 @@ 20, 5 ], - "bbox": 6380, + "bbox": 6384, "colour": "black", "endx": 1675.0, "endy": 325.0, @@ -11849,9 +11892,10 @@ "tags": "line5" }, "78c8911f-a18a-41bf-961d-3a43893854d4": { - "bbox": 6384, + "bbox": 6388, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 11, "mirror": "", @@ -11860,7 +11904,8 @@ "tags": "section11" }, "7a30db34-8614-4de1-b459-1440f11ef0b7": { - "bbox": 6388, + "bbox": 6392, + "hidden": false, "item": "tracksensor", "itemid": 11, "passedsensor": "", @@ -12164,24 +12209,26 @@ }, "7aa34fb1-3039-4be1-b71a-9d85fa38f112": { "background": "grey85", - "bbox": 6391, + "bbox": 6395, "border": 0, "colour": "black", "font": "Courier", "fontsize": 18, "fontstyle": "bold underline ", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 7, "justify": 2, "posx": 850.0, "posy": 50.0, - "tags": "7aa34fb1-3039-4be1-b71a-9d85fa38f112", + "tags": "textbox7", "text": "Example Layout with Schematic Route Selections" }, "7e4470b9-8e8f-4790-b607-298c970fee30": { - "bbox": 6395, + "bbox": 6399, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 9, "mirror": "", @@ -12192,7 +12239,7 @@ "7f925596-94a5-43a6-b260-ce4bd63caaad": { "alsoswitch": 0, "automatic": true, - "bbox": 6400, + "bbox": 6404, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -12211,7 +12258,8 @@ "ybuttonoffset": 0 }, "819aa2e8-aca4-4f1a-b08a-0c81429e32da": { - "bbox": 6404, + "bbox": 6408, + "hidden": false, "item": "tracksensor", "itemid": 10, "passedsensor": "", @@ -12520,7 +12568,7 @@ 0, 0 ], - "bbox": 6410, + "bbox": 6414, "colour": "black", "endx": 900.0, "endy": 325.0, @@ -12533,34 +12581,36 @@ }, "846b5450-3bb8-443d-9552-644e650b10dd": { "background": "#d9d9b1", - "bbox": 6413, + "bbox": 6417, "border": 2, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 8, "justify": 2, "posx": 899.0, "posy": 288.0, - "tags": "846b5450-3bb8-443d-9552-644e650b10dd", + "tags": "textbox8", "text": " Platform 3 \n Platform 2 " }, "84a2cb6e-79ed-42c2-ae61-55e5c92be7db": { "background": "grey85", - "bbox": 6416, + "bbox": 6420, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "bold ", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 9, "justify": 2, "posx": 1749.0, "posy": 275.0, - "tags": "84a2cb6e-79ed-42c2-ae61-55e5c92be7db", + "tags": "textbox9", "text": "BRANCH" }, "84b2145a-5eb7-4b8c-881d-b1c18e146b65": { @@ -12575,7 +12625,7 @@ false, "" ], - "bbox": 6427, + "bbox": 6431, "dccaspects": [ [ [ @@ -13476,9 +13526,10 @@ ] }, "857bc7b3-6dd9-4fe8-a800-04b0845c77f2": { - "bbox": 6431, + "bbox": 6435, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 16, "mirror": "", @@ -13498,7 +13549,7 @@ false, "" ], - "bbox": 6448, + "bbox": 6452, "dccaspects": [ [ [ @@ -14374,7 +14425,7 @@ 0, 0 ], - "bbox": 6454, + "bbox": 6458, "colour": "black", "endx": 1725.0, "endy": 275.0, @@ -14392,7 +14443,7 @@ 20, 5 ], - "bbox": 6460, + "bbox": 6464, "colour": "black", "endx": 100.0, "endy": 275.0, @@ -14404,7 +14455,8 @@ "tags": "line27" }, "91d508bf-88f0-4324-a6f6-0a704e20fdb5": { - "bbox": 6464, + "bbox": 6468, + "hidden": false, "item": "tracksensor", "itemid": 2, "passedsensor": "", @@ -14713,7 +14765,7 @@ 20, 5 ], - "bbox": 6470, + "bbox": 6474, "colour": "black", "endx": 1700.0, "endy": 275.0, @@ -14726,22 +14778,24 @@ }, "93d61cb6-a272-45a4-be73-7fcd8ae70932": { "background": "grey85", - "bbox": 6473, + "bbox": 6477, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "bold ", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 10, "justify": 2, "posx": 51.0, "posy": 375.0, - "tags": "93d61cb6-a272-45a4-be73-7fcd8ae70932", + "tags": "textbox10", "text": "DOWN\nMAIN" }, "946fe314-a71f-404a-86a8-98d27a10966d": { - "bbox": 6477, + "bbox": 6481, + "hidden": false, "item": "tracksensor", "itemid": 12, "passedsensor": "", @@ -15044,7 +15098,8 @@ "tags": "sensor12" }, "98f3e500-24e3-477b-b8de-8d0625da443d": { - "bbox": 6481, + "bbox": 6485, + "hidden": false, "item": "tracksensor", "itemid": 3, "passedsensor": "", @@ -15347,7 +15402,8 @@ "tags": "sensor3" }, "99bd153e-9f0c-4134-9611-853fa6740f25": { - "bbox": 6485, + "bbox": 6489, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 2, @@ -15371,6 +15427,7 @@ "routecolour": "#ff6100", "routedescription": "Down Main arrival into Down Avoiding loop", "routename": "Down to Avoiding", + "setupsensor": 0, "signalsonroute": [ 5 ], @@ -15380,7 +15437,8 @@ "tracksensor": 11 }, "9aec57b5-85f1-4118-b3e2-4c145e6ffaa8": { - "bbox": 6489, + "bbox": 6493, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 18, @@ -15407,6 +15465,7 @@ "routecolour": "#0000ff", "routedescription": "Branch arrival into goods loop", "routename": "Branch to Goods", + "setupsensor": 0, "signalsonroute": [ 11, 10 @@ -15419,7 +15478,7 @@ "9dcab729-9273-4ede-9329-f6adc2ff05fb": { "alsoswitch": 17, "automatic": false, - "bbox": 6496, + "bbox": 6500, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -15459,7 +15518,8 @@ "ybuttonoffset": 0 }, "9f2c0ce4-5515-4591-944c-b719fe662513": { - "bbox": 6500, + "bbox": 6504, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 15, @@ -15483,6 +15543,7 @@ "routecolour": "#0000ff", "routedescription": "Platform 3 departure to branch", "routename": "Platform 3 to Branch", + "setupsensor": 0, "signalsonroute": [ 7, 21 @@ -15499,7 +15560,7 @@ 1, 1 ], - "bbox": 6506, + "bbox": 6510, "colour": "black", "endx": 650.0, "endy": 425.0, @@ -15512,22 +15573,24 @@ }, "a824704b-6fde-49ba-ba05-933b6f7fe189": { "background": "grey85", - "bbox": 6509, + "bbox": 6513, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "bold ", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 11, "justify": 2, "posx": 50.0, "posy": 327.0, - "tags": "a824704b-6fde-49ba-ba05-933b6f7fe189", + "tags": "textbox11", "text": "UP\nMAIN" }, "abc9a055-2106-4b89-8de5-f4ce4e1df22f": { - "bbox": 6513, + "bbox": 6517, + "hidden": false, "item": "tracksensor", "itemid": 5, "passedsensor": "", @@ -15832,7 +15895,7 @@ "b0f29657-f391-457d-85b7-6a5ec41b0d13": { "alsoswitch": 12, "automatic": false, - "bbox": 6520, + "bbox": 6524, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -15902,7 +15965,8 @@ "ybuttonoffset": 0 }, "b118ca35-20db-4e0e-881a-e22db97c1b66": { - "bbox": 6524, + "bbox": 6528, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 14, @@ -15929,6 +15993,7 @@ "routecolour": "#0000ff", "routedescription": "Goods Loop departure to branch", "routename": "Goods to Branch", + "setupsensor": 0, "signalsonroute": [ 1, 21 @@ -15950,7 +16015,7 @@ false, "" ], - "bbox": 6541, + "bbox": 6545, "dccaspects": [ [ [ @@ -16820,9 +16885,10 @@ ] }, "b17fb551-2bfe-4d2a-9429-d2d94005c0c5": { - "bbox": 6545, + "bbox": 6549, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 20, "mirror": "", @@ -16831,7 +16897,8 @@ "tags": "section20" }, "b20e0671-cd3e-440f-b7de-2af556846bc7": { - "bbox": 6549, + "bbox": 6553, + "hidden": false, "item": "tracksensor", "itemid": 8, "passedsensor": "", @@ -17136,7 +17203,7 @@ "b34426dc-7315-4c73-b36a-b2c97b9c94c7": { "alsoswitch": 0, "automatic": true, - "bbox": 6556, + "bbox": 6560, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -17166,7 +17233,7 @@ false, "" ], - "bbox": 6573, + "bbox": 6577, "dccaspects": [ [ [ @@ -18036,7 +18103,7 @@ false, "" ], - "bbox": 6590, + "bbox": 6594, "dccaspects": [ [ [ @@ -18895,7 +18962,8 @@ ] }, "b7d81f2a-7d03-42f7-9125-a3559433f934": { - "bbox": 6594, + "bbox": 6598, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 22, @@ -18919,6 +18987,7 @@ "routecolour": "#c800ff", "routedescription": "Up Main arrival into Platform 2", "routename": "Up to Platform 2", + "setupsensor": 0, "signalsonroute": [ 6 ], @@ -18928,7 +18997,8 @@ "tracksensor": 10 }, "b7f5f250-60f7-4678-95b8-0c8e50417a04": { - "bbox": 6598, + "bbox": 6602, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 13, @@ -18953,6 +19023,7 @@ "routecolour": "#0000ff", "routedescription": "Branch line arrival into Platform 2", "routename": "Branch to Platform 3", + "setupsensor": 0, "signalsonroute": [ 14, 13 @@ -18963,7 +19034,8 @@ "tracksensor": 8 }, "b9e52658-031c-49c8-a7c1-c18c6bf6d8e1": { - "bbox": 6602, + "bbox": 6606, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 4, @@ -18987,6 +19059,7 @@ "routecolour": "#ff6100", "routedescription": "Avoiding Loop departure to Down Main", "routename": "Avoiding to Down", + "setupsensor": 0, "signalsonroute": [ 9 ], @@ -18996,7 +19069,8 @@ "tracksensor": 5 }, "bc306663-e528-4e0e-b65b-a32754ee91d5": { - "bbox": 6606, + "bbox": 6610, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 10, @@ -19021,6 +19095,7 @@ "routecolour": "#0000ff", "routedescription": "Goods Loop departure to branch", "routename": "Goods to Branch", + "setupsensor": 0, "signalsonroute": [ 2, 22 @@ -19031,9 +19106,10 @@ "tracksensor": 2 }, "bc6fbf64-cd5a-418a-98b7-2e0beeb7c64e": { - "bbox": 6610, + "bbox": 6614, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 18, "mirror": "", @@ -19042,9 +19118,10 @@ "tags": "section18" }, "bf63c705-72aa-4cc3-8d20-bbb1c69bb297": { - "bbox": 6614, + "bbox": 6618, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 14, "mirror": "", @@ -19059,7 +19136,7 @@ 0, 0 ], - "bbox": 6620, + "bbox": 6624, "colour": "black", "endx": 900.0, "endy": 250.0, @@ -19073,7 +19150,7 @@ "c867d2f6-3d89-490c-a3f9-b8cdd535ae99": { "alsoswitch": 18, "automatic": false, - "bbox": 6626, + "bbox": 6630, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -19113,9 +19190,10 @@ "ybuttonoffset": 0 }, "c9d70646-364e-454c-95de-978d995c6c8e": { - "bbox": 6630, + "bbox": 6634, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 3, "mirror": "", @@ -19135,7 +19213,7 @@ false, "" ], - "bbox": 6647, + "bbox": 6651, "dccaspects": [ [ [ @@ -20031,7 +20109,7 @@ 0, 0 ], - "bbox": 6653, + "bbox": 6657, "colour": "black", "endx": 1150.0, "endy": 325.0, @@ -20049,7 +20127,7 @@ 20, 5 ], - "bbox": 6659, + "bbox": 6663, "colour": "black", "endx": 1650.0, "endy": 275.0, @@ -20062,18 +20140,19 @@ }, "cc0ad700-89e3-486c-8ad0-e9a05ea0556d": { "background": "grey85", - "bbox": 6662, + "bbox": 6666, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "bold ", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 12, "justify": 2, "posx": 1275.0, "posy": 180.0, - "tags": "cc0ad700-89e3-486c-8ad0-e9a05ea0556d", + "tags": "textbox12", "text": "Headshunt" }, "cd5c7c03-c9da-44fd-9909-24219cd7c59b": { @@ -20083,7 +20162,7 @@ 0, 0 ], - "bbox": 6668, + "bbox": 6672, "colour": "black", "endx": 650.0, "endy": 250.0, @@ -20095,9 +20174,10 @@ "tags": "line8" }, "ce380375-14c3-464d-a468-bf9a0b7a0f10": { - "bbox": 6672, + "bbox": 6676, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 21, "mirror": "", @@ -20106,9 +20186,10 @@ "tags": "section21" }, "ceebbc85-e329-4185-a57f-e9230865a45c": { - "bbox": 6676, + "bbox": 6680, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 13, "mirror": "", @@ -20117,7 +20198,8 @@ "tags": "section13" }, "d3df1397-1978-4d8d-9999-a561d4d321de": { - "bbox": 6680, + "bbox": 6684, + "hidden": false, "item": "tracksensor", "itemid": 6, "passedsensor": "", @@ -20422,7 +20504,7 @@ "d433fb55-7016-4bc9-ac60-9cf142605fa8": { "alsoswitch": 13, "automatic": false, - "bbox": 6687, + "bbox": 6691, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -20482,9 +20564,10 @@ "ybuttonoffset": 0 }, "d4a3a8dc-b0f1-4ada-b934-36b9646a17b7": { - "bbox": 6691, + "bbox": 6695, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 10, "mirror": "", @@ -20504,7 +20587,7 @@ false, "" ], - "bbox": 6708, + "bbox": 6712, "dccaspects": [ [ [ @@ -21363,9 +21446,10 @@ ] }, "d93793ac-8990-4f68-ae7a-7a17c80043bc": { - "bbox": 6712, + "bbox": 6716, "defaultlabel": "XXXXXxxx", "editable": true, + "hidden": false, "item": "section", "itemid": 1, "mirror": "", @@ -21375,24 +21459,25 @@ }, "ddb09cbe-5c7d-4810-b27b-0b64e588e56b": { "background": "grey85", - "bbox": 6715, + "bbox": 6719, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "bold ", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 13, "justify": 2, "posx": 1750.0, "posy": 327.0, - "tags": "ddb09cbe-5c7d-4810-b27b-0b64e588e56b", + "tags": "textbox13", "text": "UP\nMAIN" }, "dec5f273-15d3-4ba7-a029-16028a9936e9": { "alsoswitch": 0, "automatic": true, - "bbox": 6720, + "bbox": 6724, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -21417,7 +21502,7 @@ 0, 0 ], - "bbox": 6726, + "bbox": 6730, "colour": "black", "endx": 1125.0, "endy": 150.0, @@ -21429,7 +21514,8 @@ "tags": "line21" }, "e2c437bb-ec9b-46d1-bf27-0a9b5467f77d": { - "bbox": 6730, + "bbox": 6734, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 16, @@ -21457,6 +21543,7 @@ "routecolour": "#ff6100", "routedescription": "Platform 3 departure to Down Main", "routename": "Platform 3 to Down", + "setupsensor": 0, "signalsonroute": [ 7 ], @@ -21472,7 +21559,7 @@ 1, 1 ], - "bbox": 6736, + "bbox": 6740, "colour": "black", "endx": 1325.0, "endy": 150.0, @@ -21486,7 +21573,7 @@ "e5eea518-6f34-47d9-8a17-ca314e06a52d": { "alsoswitch": 0, "automatic": true, - "bbox": 6741, + "bbox": 6745, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -21516,7 +21603,7 @@ false, "" ], - "bbox": 6760, + "bbox": 6764, "dccaspects": [ [ [ @@ -22448,7 +22535,7 @@ false, "" ], - "bbox": 6777, + "bbox": 6781, "dccaspects": [ [ [ @@ -23309,7 +23396,7 @@ "eaed2508-4646-46cc-83fa-b9ef8ee2848b": { "alsoswitch": 7, "automatic": false, - "bbox": 6784, + "bbox": 6788, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -23390,7 +23477,7 @@ false, "" ], - "bbox": 6801, + "bbox": 6805, "dccaspects": [ [ [ @@ -24255,7 +24342,7 @@ 0, 0 ], - "bbox": 6807, + "bbox": 6811, "colour": "black", "endx": 900.0, "endy": 375.0, @@ -24273,7 +24360,7 @@ 0, 0 ], - "bbox": 6813, + "bbox": 6817, "colour": "black", "endx": 1075.0, "endy": 250.0, @@ -24285,7 +24372,8 @@ "tags": "line3" }, "f4875d39-1482-47d9-a37d-3b58035e94ad": { - "bbox": 6817, + "bbox": 6821, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 8, @@ -24312,6 +24400,7 @@ "routecolour": "#c800ff", "routedescription": "Platform 3 departure to Up Main", "routename": "Platform 3 to Up", + "setupsensor": 0, "signalsonroute": [ 3 ], @@ -24332,7 +24421,7 @@ false, "" ], - "bbox": 6834, + "bbox": 6838, "dccaspects": [ [ [ @@ -25222,7 +25311,8 @@ ] }, "f85bced4-b684-4738-a166-c8b978efeb6d": { - "bbox": 6838, + "bbox": 6842, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 9, @@ -25245,6 +25335,7 @@ "routecolour": "#c800ff", "routedescription": "Platform 2 departure to Up Main", "routename": "Platform 2 to Up", + "setupsensor": 0, "signalsonroute": [ 4 ], @@ -25265,7 +25356,7 @@ false, "" ], - "bbox": 6857, + "bbox": 6861, "dccaspects": [ [ [ @@ -26193,7 +26284,7 @@ 0, 0 ], - "bbox": 6863, + "bbox": 6867, "colour": "black", "endx": 1200.0, "endy": 375.0, @@ -26401,11 +26492,12 @@ }, "general": { "automation": true, + "buttonsize": 8, "editmode": false, "filename": "/home/john/model-railway-signalling/configuration_examples/schematic_routes_example.sig", "info": "Document your layout here", "spadpopups": false, - "version": "Version 4.6.1" + "version": "Version 4.7.0" }, "gpio": { "portmappings": [], diff --git a/configuration_examples/semaphore_signals.sig b/configuration_examples/semaphore_signals.sig index 53e6c7f0..7db7c865 100644 --- a/configuration_examples/semaphore_signals.sig +++ b/configuration_examples/semaphore_signals.sig @@ -15,7 +15,7 @@ false, "" ], - "bbox": 7009, + "bbox": 7013, "dccaspects": [ [ [ @@ -885,7 +885,7 @@ false, "" ], - "bbox": 7039, + "bbox": 7043, "dccaspects": [ [ [ @@ -1755,7 +1755,7 @@ false, "" ], - "bbox": 7067, + "bbox": 7071, "dccaspects": [ [ [ @@ -2625,7 +2625,7 @@ false, "" ], - "bbox": 7095, + "bbox": 7099, "dccaspects": [ [ [ @@ -3495,7 +3495,7 @@ false, "" ], - "bbox": 7145, + "bbox": 7149, "dccaspects": [ [ [ @@ -4365,7 +4365,7 @@ false, "" ], - "bbox": 7175, + "bbox": 7179, "dccaspects": [ [ [ @@ -5235,7 +5235,7 @@ false, "" ], - "bbox": 7203, + "bbox": 7207, "dccaspects": [ [ [ @@ -6105,7 +6105,7 @@ false, "" ], - "bbox": 7232, + "bbox": 7236, "dccaspects": [ [ [ @@ -6975,7 +6975,7 @@ false, "" ], - "bbox": 7261, + "bbox": 7265, "dccaspects": [ [ [ @@ -7845,7 +7845,7 @@ false, "" ], - "bbox": 7287, + "bbox": 7291, "dccaspects": [ [ [ @@ -8715,7 +8715,7 @@ false, "" ], - "bbox": 7318, + "bbox": 7322, "dccaspects": [ [ [ @@ -9585,7 +9585,7 @@ false, "" ], - "bbox": 7352, + "bbox": 7356, "dccaspects": [ [ [ @@ -10455,7 +10455,7 @@ false, "" ], - "bbox": 7405, + "bbox": 7409, "dccaspects": [ [ [ @@ -11325,7 +11325,7 @@ false, "" ], - "bbox": 7436, + "bbox": 7440, "dccaspects": [ [ [ @@ -12195,7 +12195,7 @@ false, "" ], - "bbox": 7465, + "bbox": 7469, "dccaspects": [ [ [ @@ -13065,7 +13065,7 @@ false, "" ], - "bbox": 7496, + "bbox": 7500, "dccaspects": [ [ [ @@ -13935,7 +13935,7 @@ false, "" ], - "bbox": 7546, + "bbox": 7550, "dccaspects": [ [ [ @@ -14805,7 +14805,7 @@ false, "" ], - "bbox": 7581, + "bbox": 7585, "dccaspects": [ [ [ @@ -15675,7 +15675,7 @@ false, "" ], - "bbox": 7637, + "bbox": 7641, "dccaspects": [ [ [ @@ -16545,7 +16545,7 @@ false, "" ], - "bbox": 7668, + "bbox": 7672, "dccaspects": [ [ [ @@ -17415,7 +17415,7 @@ false, "" ], - "bbox": 7698, + "bbox": 7702, "dccaspects": [ [ [ @@ -18285,7 +18285,7 @@ false, "" ], - "bbox": 7726, + "bbox": 7730, "dccaspects": [ [ [ @@ -19155,7 +19155,7 @@ false, "" ], - "bbox": 7756, + "bbox": 7760, "dccaspects": [ [ [ @@ -20025,7 +20025,7 @@ false, "" ], - "bbox": 7808, + "bbox": 7812, "dccaspects": [ [ [ @@ -20895,7 +20895,7 @@ false, "" ], - "bbox": 7834, + "bbox": 7838, "dccaspects": [ [ [ @@ -21765,7 +21765,7 @@ false, "" ], - "bbox": 7863, + "bbox": 7867, "dccaspects": [ [ [ @@ -22635,7 +22635,7 @@ false, "" ], - "bbox": 7897, + "bbox": 7901, "dccaspects": [ [ [ @@ -23505,7 +23505,7 @@ false, "" ], - "bbox": 7948, + "bbox": 7952, "dccaspects": [ [ [ @@ -24375,7 +24375,7 @@ false, "" ], - "bbox": 7979, + "bbox": 7983, "dccaspects": [ [ [ @@ -25245,7 +25245,7 @@ false, "" ], - "bbox": 8009, + "bbox": 8013, "dccaspects": [ [ [ @@ -26115,7 +26115,7 @@ false, "" ], - "bbox": 8038, + "bbox": 8042, "dccaspects": [ [ [ @@ -26985,7 +26985,7 @@ false, "" ], - "bbox": 8067, + "bbox": 8071, "dccaspects": [ [ [ @@ -27855,7 +27855,7 @@ false, "" ], - "bbox": 8096, + "bbox": 8100, "dccaspects": [ [ [ @@ -28725,7 +28725,7 @@ false, "" ], - "bbox": 8126, + "bbox": 8130, "dccaspects": [ [ [ @@ -29595,7 +29595,7 @@ false, "" ], - "bbox": 8155, + "bbox": 8159, "dccaspects": [ [ [ @@ -30465,7 +30465,7 @@ false, "" ], - "bbox": 8208, + "bbox": 8212, "dccaspects": [ [ [ @@ -31335,7 +31335,7 @@ false, "" ], - "bbox": 8235, + "bbox": 8239, "dccaspects": [ [ [ @@ -32205,7 +32205,7 @@ false, "" ], - "bbox": 8262, + "bbox": 8266, "dccaspects": [ [ [ @@ -33075,7 +33075,7 @@ false, "" ], - "bbox": 8297, + "bbox": 8301, "dccaspects": [ [ [ @@ -33945,7 +33945,7 @@ false, "" ], - "bbox": 8325, + "bbox": 8329, "dccaspects": [ [ [ @@ -34815,7 +34815,7 @@ false, "" ], - "bbox": 8377, + "bbox": 8381, "dccaspects": [ [ [ @@ -35685,7 +35685,7 @@ false, "" ], - "bbox": 8427, + "bbox": 8431, "dccaspects": [ [ [ @@ -36555,7 +36555,7 @@ false, "" ], - "bbox": 8476, + "bbox": 8480, "dccaspects": [ [ [ @@ -37425,7 +37425,7 @@ false, "" ], - "bbox": 8532, + "bbox": 8536, "dccaspects": [ [ [ @@ -38295,7 +38295,7 @@ false, "" ], - "bbox": 8567, + "bbox": 8571, "dccaspects": [ [ [ @@ -39165,7 +39165,7 @@ false, "" ], - "bbox": 8617, + "bbox": 8621, "dccaspects": [ [ [ @@ -40035,7 +40035,7 @@ false, "" ], - "bbox": 8644, + "bbox": 8648, "dccaspects": [ [ [ @@ -40905,7 +40905,7 @@ false, "" ], - "bbox": 8678, + "bbox": 8682, "dccaspects": [ [ [ @@ -41775,7 +41775,7 @@ false, "" ], - "bbox": 8709, + "bbox": 8713, "dccaspects": [ [ [ @@ -42645,7 +42645,7 @@ false, "" ], - "bbox": 8762, + "bbox": 8766, "dccaspects": [ [ [ @@ -43515,7 +43515,7 @@ false, "" ], - "bbox": 8815, + "bbox": 8819, "dccaspects": [ [ [ @@ -44385,7 +44385,7 @@ false, "" ], - "bbox": 8823, + "bbox": 8827, "dccaspects": [ [ [ @@ -45255,7 +45255,7 @@ false, "" ], - "bbox": 8851, + "bbox": 8855, "dccaspects": [ [ [ @@ -46125,7 +46125,7 @@ false, "" ], - "bbox": 8901, + "bbox": 8905, "dccaspects": [ [ [ @@ -46995,7 +46995,7 @@ false, "" ], - "bbox": 8949, + "bbox": 8953, "dccaspects": [ [ [ @@ -47865,7 +47865,7 @@ false, "" ], - "bbox": 8979, + "bbox": 8983, "dccaspects": [ [ [ @@ -48735,7 +48735,7 @@ false, "" ], - "bbox": 9031, + "bbox": 9035, "dccaspects": [ [ [ @@ -49605,7 +49605,7 @@ false, "" ], - "bbox": 9062, + "bbox": 9066, "dccaspects": [ [ [ @@ -50475,7 +50475,7 @@ false, "" ], - "bbox": 9090, + "bbox": 9094, "dccaspects": [ [ [ @@ -51345,7 +51345,7 @@ false, "" ], - "bbox": 9143, + "bbox": 9147, "dccaspects": [ [ [ @@ -52215,7 +52215,7 @@ false, "" ], - "bbox": 9195, + "bbox": 9199, "dccaspects": [ [ [ @@ -53085,7 +53085,7 @@ false, "" ], - "bbox": 9248, + "bbox": 9252, "dccaspects": [ [ [ @@ -53955,7 +53955,7 @@ false, "" ], - "bbox": 9256, + "bbox": 9260, "dccaspects": [ [ [ @@ -54825,7 +54825,7 @@ false, "" ], - "bbox": 9313, + "bbox": 9317, "dccaspects": [ [ [ @@ -55695,7 +55695,7 @@ false, "" ], - "bbox": 9339, + "bbox": 9343, "dccaspects": [ [ [ @@ -56565,7 +56565,7 @@ false, "" ], - "bbox": 9389, + "bbox": 9393, "dccaspects": [ [ [ @@ -57435,7 +57435,7 @@ false, "" ], - "bbox": 9446, + "bbox": 9450, "dccaspects": [ [ [ @@ -58305,7 +58305,7 @@ false, "" ], - "bbox": 9477, + "bbox": 9481, "dccaspects": [ [ [ @@ -59175,7 +59175,7 @@ false, "" ], - "bbox": 9508, + "bbox": 9512, "dccaspects": [ [ [ @@ -60045,7 +60045,7 @@ false, "" ], - "bbox": 9557, + "bbox": 9561, "dccaspects": [ [ [ @@ -60915,7 +60915,7 @@ false, "" ], - "bbox": 9608, + "bbox": 9612, "dccaspects": [ [ [ @@ -61785,7 +61785,7 @@ false, "" ], - "bbox": 9638, + "bbox": 9642, "dccaspects": [ [ [ @@ -62655,7 +62655,7 @@ false, "" ], - "bbox": 9666, + "bbox": 9670, "dccaspects": [ [ [ @@ -63525,7 +63525,7 @@ false, "" ], - "bbox": 9697, + "bbox": 9701, "dccaspects": [ [ [ @@ -64395,7 +64395,7 @@ false, "" ], - "bbox": 9749, + "bbox": 9753, "dccaspects": [ [ [ @@ -65265,7 +65265,7 @@ false, "" ], - "bbox": 9757, + "bbox": 9761, "dccaspects": [ [ [ @@ -66135,7 +66135,7 @@ false, "" ], - "bbox": 9809, + "bbox": 9813, "dccaspects": [ [ [ @@ -67005,7 +67005,7 @@ false, "" ], - "bbox": 9837, + "bbox": 9841, "dccaspects": [ [ [ @@ -67875,7 +67875,7 @@ false, "" ], - "bbox": 9886, + "bbox": 9890, "dccaspects": [ [ [ @@ -68745,7 +68745,7 @@ false, "" ], - "bbox": 9935, + "bbox": 9939, "dccaspects": [ [ [ @@ -69615,7 +69615,7 @@ false, "" ], - "bbox": 9986, + "bbox": 9990, "dccaspects": [ [ [ @@ -70485,7 +70485,7 @@ false, "" ], - "bbox": 10020, + "bbox": 10024, "dccaspects": [ [ [ @@ -71355,7 +71355,7 @@ false, "" ], - "bbox": 10048, + "bbox": 10052, "dccaspects": [ [ [ @@ -72225,7 +72225,7 @@ false, "" ], - "bbox": 10078, + "bbox": 10082, "dccaspects": [ [ [ @@ -73095,7 +73095,7 @@ false, "" ], - "bbox": 10105, + "bbox": 10109, "dccaspects": [ [ [ @@ -73965,7 +73965,7 @@ false, "" ], - "bbox": 10135, + "bbox": 10139, "dccaspects": [ [ [ @@ -74835,7 +74835,7 @@ false, "" ], - "bbox": 10166, + "bbox": 10170, "dccaspects": [ [ [ @@ -75705,7 +75705,7 @@ false, "" ], - "bbox": 10217, + "bbox": 10221, "dccaspects": [ [ [ @@ -76575,7 +76575,7 @@ false, "" ], - "bbox": 10248, + "bbox": 10252, "dccaspects": [ [ [ @@ -77445,7 +77445,7 @@ false, "" ], - "bbox": 10278, + "bbox": 10282, "dccaspects": [ [ [ @@ -78315,7 +78315,7 @@ false, "" ], - "bbox": 10304, + "bbox": 10308, "dccaspects": [ [ [ @@ -79185,7 +79185,7 @@ false, "" ], - "bbox": 10312, + "bbox": 10316, "dccaspects": [ [ [ @@ -80055,7 +80055,7 @@ false, "" ], - "bbox": 10347, + "bbox": 10351, "dccaspects": [ [ [ @@ -80925,7 +80925,7 @@ false, "" ], - "bbox": 10377, + "bbox": 10381, "dccaspects": [ [ [ @@ -81795,11 +81795,12 @@ }, "general": { "automation": true, + "buttonsize": 8, "editmode": true, "filename": "/home/john/model-railway-signalling/configuration_examples/semaphore_signals.sig", "info": "Document your layout here", "spadpopups": false, - "version": "Version 4.6.1" + "version": "Version 4.7.0" }, "gpio": { "portmappings": [], diff --git a/configuration_examples/sensor_node.sig b/configuration_examples/sensor_node.sig new file mode 100644 index 00000000..dd46a1f4 --- /dev/null +++ b/configuration_examples/sensor_node.sig @@ -0,0 +1,183 @@ +{ + "buttons": {}, + "information": "Model Railway Signalling Configuration File", + "instruments": {}, + "objects": { + "11572d7d-c95f-47e0-a3fa-400d86e6f3f0": { + "background": "grey85", + "bbox": 10445, + "border": 2, + "colour": "#0000ff", + "font": "Courier", + "fontsize": 10, + "fontstyle": "bold ", + "hidden": false, + "item": "textbox", + "itemid": 1, + "justify": 1, + "posx": 500.0, + "posy": 250.0, + "tags": "textbox1", + "text": "This is the default layout file loaded on start-up and is configured\nto publish all GPIO sensor inputs via the MQTT network. \n\nTo complete the configuration:\n- Select Settings => MQTT\n - Under Broker Configuration:\n - Specify the IP address of your main signalling node\n - Under Network Configuration:\n - Specify the name you have assigned to your signalling network\n - Specify a unique name (on the network) for this signallling node. \n\nIf you want to run this node 'headless' (without a monitor, keyboard or \nmouse connected), then you will also need to:\n - On this node - under Network Configuration:\n - Select 'Connect to broker on layout load'\n - Select 'Quit application on reciept of shutdown'\n - On your main signalling node - under Network Configuration:\n - Select 'Publish shutdown on application exit'\n\nThis will cause this signalling node to initiate a shutdown when the\napplication instance running on your main signalling node is closed.\nThe Raspberry Pi will perform an orderly shutdown 10 seconds after \nthe application exits.\n\nIf required, you can also run a full layout file on this node\n(simulating another signal box on the layout for example) but\nnote that the Raspberry Pi-zero is not as powerful as the Pi-4B\nand so this will really only be suitable for simple layouts." + } + }, + "points": {}, + "sections": {}, + "settings": { + "canvas": { + "grid": 25, + "height": 500, + "snaptogrid": true, + "width": 1000 + }, + "general": { + "automation": true, + "buttonsize": 8, + "editmode": true, + "filename": "/home/john/model-railway-signalling/configuration_examples/sensor_node.sig", + "info": "Document your layout here", + "spadpopups": false, + "version": "Version 4.7.0" + }, + "gpio": { + "portmappings": [ + [ + 4, + 4 + ], + [ + 5, + 5 + ], + [ + 6, + 6 + ], + [ + 7, + 7 + ], + [ + 8, + 8 + ], + [ + 9, + 9 + ], + [ + 10, + 10 + ], + [ + 11, + 11 + ], + [ + 12, + 12 + ], + [ + 13, + 13 + ], + [ + 18, + 18 + ], + [ + 19, + 19 + ], + [ + 20, + 20 + ], + [ + 21, + 21 + ], + [ + 22, + 22 + ], + [ + 23, + 23 + ], + [ + 24, + 24 + ], + [ + 25, + 25 + ], + [ + 26, + 26 + ], + [ + 27, + 27 + ] + ], + "timeoutperiod": 1.0, + "triggerdelay": 0.02 + }, + "logging": { + "level": 2 + }, + "mqtt": { + "debug": false, + "network": "network", + "node": "node", + "password": "", + "port": 1883, + "pubdcc": false, + "pubinstruments": [], + "pubsections": [], + "pubsensors": [ + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27 + ], + "pubshutdown": false, + "pubsignals": [], + "startup": false, + "subdccnodes": [], + "subinstruments": [], + "subsections": [], + "subsensors": [], + "subshutdown": false, + "subsignals": [], + "url": "127.0.0.1", + "username": "" + }, + "sprog": { + "baud": 460800, + "debug": false, + "port": "/dev/serial0", + "power": false, + "startup": false + } + }, + "signals": {} +} \ No newline at end of file diff --git a/configuration_examples/sidings_example.sig b/configuration_examples/sidings_example.sig index 08d7fdfd..6980998e 100644 --- a/configuration_examples/sidings_example.sig +++ b/configuration_examples/sidings_example.sig @@ -10,7 +10,7 @@ 0, 0 ], - "bbox": 10444, + "bbox": 10512, "colour": "black", "endx": 300.0, "endy": 275.0, @@ -24,7 +24,7 @@ "05083e4d-786d-421e-af32-90cf02b69524": { "alsoswitch": 0, "automatic": false, - "bbox": 10450, + "bbox": 10518, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -49,7 +49,7 @@ 0, 0 ], - "bbox": 10456, + "bbox": 10524, "colour": "black", "endx": 575.0, "endy": 325.0, @@ -63,7 +63,7 @@ "096de21f-50bd-4997-b78c-259af84a518a": { "alsoswitch": 0, "automatic": false, - "bbox": 10462, + "bbox": 10530, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -93,7 +93,7 @@ false, "" ], - "bbox": 10473, + "bbox": 10541, "dccaspects": [ [ [ @@ -963,9 +963,10 @@ ] }, "0fe13115-2591-4c50-b1e8-944937058e27": { - "bbox": 10477, + "bbox": 10545, "defaultlabel": "XXXXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 10, "mirror": "", @@ -976,7 +977,7 @@ "164f0397-7c2a-428c-baaa-beaa2fa30004": { "alsoswitch": 0, "automatic": false, - "bbox": 10483, + "bbox": 10551, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -1001,7 +1002,7 @@ 0, 0 ], - "bbox": 10489, + "bbox": 10557, "colour": "black", "endx": 975.0, "endy": 375.0, @@ -1013,9 +1014,10 @@ "tags": "line15" }, "1f224d72-58aa-4d11-bc79-8fc6bb41d91b": { - "bbox": 10493, + "bbox": 10561, "defaultlabel": "XXXXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 4, "mirror": "", @@ -1026,7 +1028,7 @@ "25438172-e456-40c5-b75e-1a1ba9ebddd0": { "alsoswitch": 6, "automatic": false, - "bbox": 10499, + "bbox": 10567, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -1078,7 +1080,7 @@ "272f2895-1e55-4a45-adf5-19c1fa1a5a85": { "alsoswitch": 0, "automatic": false, - "bbox": 10505, + "bbox": 10573, "colour": "black", "dccaddress": 1000, "dccreversed": false, @@ -1114,7 +1116,7 @@ 0, 0 ], - "bbox": 10511, + "bbox": 10579, "colour": "black", "endx": 650.0, "endy": 275.0, @@ -1126,9 +1128,10 @@ "tags": "line19" }, "2ae22ec0-ab06-4a07-941d-9ebd0b62920e": { - "bbox": 10515, + "bbox": 10583, "defaultlabel": "XXXXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 14, "mirror": "", @@ -1139,7 +1142,7 @@ "2da9f2b6-37f4-45bd-ac05-bd099c5415dc": { "alsoswitch": 0, "automatic": true, - "bbox": 10520, + "bbox": 10588, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -1164,7 +1167,7 @@ 0, 0 ], - "bbox": 10526, + "bbox": 10594, "colour": "black", "endx": 275.0, "endy": 250.0, @@ -1187,7 +1190,7 @@ false, "" ], - "bbox": 10543, + "bbox": 10611, "dccaspects": [ [ [ @@ -2046,7 +2049,8 @@ ] }, "3488e32c-5c4e-4d54-bf0f-798f2e4319a6": { - "bbox": 10547, + "bbox": 10615, + "hidden": false, "item": "tracksensor", "itemid": 2, "passedsensor": "12", @@ -2349,7 +2353,8 @@ "tags": "sensor2" }, "3d5a6124-111a-4765-b09e-84236b082aeb": { - "bbox": 10551, + "bbox": 10619, + "hidden": false, "item": "tracksensor", "itemid": 1, "passedsensor": "11", @@ -2652,7 +2657,8 @@ "tags": "sensor1" }, "42f78e7b-e26c-4c11-9be5-4e86210ff479": { - "bbox": 10555, + "bbox": 10623, + "hidden": false, "item": "tracksensor", "itemid": 4, "passedsensor": "14", @@ -2966,7 +2972,7 @@ false, "" ], - "bbox": 10572, + "bbox": 10640, "dccaspects": [ [ [ @@ -3827,7 +3833,7 @@ "4677ea0e-2946-4798-8d9f-5a71fa913c84": { "alsoswitch": 7, "automatic": false, - "bbox": 10578, + "bbox": 10646, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -3903,7 +3909,7 @@ 0, 0 ], - "bbox": 10584, + "bbox": 10652, "colour": "black", "endx": 350.0, "endy": 225.0, @@ -3915,9 +3921,10 @@ "tags": "line7" }, "4a657a16-f262-4413-a41b-08b1e5bb9448": { - "bbox": 10588, + "bbox": 10656, "defaultlabel": "XXXXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 5, "mirror": "", @@ -3937,7 +3944,7 @@ false, "" ], - "bbox": 10599, + "bbox": 10667, "dccaspects": [ [ [ @@ -4813,7 +4820,7 @@ 0, 0 ], - "bbox": 10605, + "bbox": 10673, "colour": "black", "endx": 275.0, "endy": 150.0, @@ -4825,9 +4832,10 @@ "tags": "line10" }, "4ee8acda-44a1-45dd-8572-3ce33d9b6f8e": { - "bbox": 10609, + "bbox": 10677, "defaultlabel": "XXXXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 9, "mirror": "", @@ -4842,7 +4850,7 @@ 1, 1 ], - "bbox": 10615, + "bbox": 10683, "colour": "black", "endx": 825.0, "endy": 275.0, @@ -4865,7 +4873,7 @@ false, "" ], - "bbox": 10632, + "bbox": 10700, "dccaspects": [ [ [ @@ -5724,9 +5732,10 @@ ] }, "5c0bd890-f69f-4f6b-b604-2dee9c53ee9f": { - "bbox": 10636, + "bbox": 10704, "defaultlabel": "XXXXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 11, "mirror": "", @@ -5735,9 +5744,10 @@ "tags": "section11" }, "5c437fb2-93a1-47a7-8558-9d5ab4f79ca5": { - "bbox": 10640, + "bbox": 10708, "defaultlabel": "XXXXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 12, "mirror": "", @@ -5752,7 +5762,7 @@ 1, 1 ], - "bbox": 10646, + "bbox": 10714, "colour": "black", "endx": 200.0, "endy": 75.0, @@ -5770,7 +5780,7 @@ 0, 0 ], - "bbox": 10652, + "bbox": 10720, "colour": "black", "endx": 575.0, "endy": 275.0, @@ -5788,7 +5798,7 @@ 1, 1 ], - "bbox": 10658, + "bbox": 10726, "colour": "black", "endx": 200.0, "endy": 175.0, @@ -5806,7 +5816,7 @@ 0, 0 ], - "bbox": 10664, + "bbox": 10732, "colour": "black", "endx": 675.0, "endy": 250.0, @@ -5818,9 +5828,10 @@ "tags": "line18" }, "81104548-f84d-4544-8225-dd143a85086b": { - "bbox": 10668, + "bbox": 10736, "defaultlabel": "XXXXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 13, "mirror": "", @@ -5829,7 +5840,8 @@ "tags": "section13" }, "8fb91b2a-f5db-46d8-be1e-6159187f8919": { - "bbox": 10672, + "bbox": 10740, + "hidden": false, "item": "tracksensor", "itemid": 3, "passedsensor": "13", @@ -6132,9 +6144,10 @@ "tags": "sensor3" }, "9540b948-4815-4767-a6cb-dfe3388301ef": { - "bbox": 10676, + "bbox": 10744, "defaultlabel": "XXXXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 6, "mirror": "", @@ -6143,9 +6156,10 @@ "tags": "section6" }, "99d9b4ef-9e56-4fb4-8539-066142c94e2f": { - "bbox": 10680, + "bbox": 10748, "defaultlabel": "XXXXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 8, "mirror": "", @@ -6160,7 +6174,7 @@ 0, 0 ], - "bbox": 10686, + "bbox": 10754, "colour": "black", "endx": 650.0, "endy": 225.0, @@ -6178,7 +6192,7 @@ 0, 0 ], - "bbox": 10692, + "bbox": 10760, "colour": "black", "endx": 225.0, "endy": 150.0, @@ -6201,7 +6215,7 @@ false, "" ], - "bbox": 10703, + "bbox": 10771, "dccaspects": [ [ [ @@ -7088,7 +7102,7 @@ 20, 5 ], - "bbox": 10709, + "bbox": 10777, "colour": "black", "endx": 625.0, "endy": 375.0, @@ -7106,7 +7120,7 @@ 1, 1 ], - "bbox": 10715, + "bbox": 10783, "colour": "black", "endx": 250.0, "endy": 275.0, @@ -7118,9 +7132,10 @@ "tags": "line1" }, "b0391ce1-ec72-4971-9b9b-6f629d6b7ef0": { - "bbox": 10719, + "bbox": 10787, "defaultlabel": "XXXXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 1, "mirror": "", @@ -7135,7 +7150,7 @@ 1, 1 ], - "bbox": 10725, + "bbox": 10793, "colour": "black", "endx": 250.0, "endy": 225.0, @@ -7147,9 +7162,10 @@ "tags": "line2" }, "b6dcf075-cc98-4a5e-ae70-e19b4e18ed54": { - "bbox": 10729, + "bbox": 10797, "defaultlabel": "XXXXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 7, "mirror": "", @@ -7164,7 +7180,7 @@ 1, 1 ], - "bbox": 10735, + "bbox": 10803, "colour": "black", "endx": 200.0, "endy": 125.0, @@ -7178,7 +7194,7 @@ "c34d8895-30b6-4d70-8cb7-bd06fb39ae82": { "alsoswitch": 0, "automatic": false, - "bbox": 10741, + "bbox": 10809, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -7197,9 +7213,10 @@ "ybuttonoffset": 0 }, "c89b6e58-0a68-41f7-bfbb-666119b0f0ea": { - "bbox": 10745, + "bbox": 10813, "defaultlabel": "XXXXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 3, "mirror": "", @@ -7214,7 +7231,7 @@ 20, 5 ], - "bbox": 10751, + "bbox": 10819, "colour": "black", "endx": 975.0, "endy": 325.0, @@ -7226,9 +7243,10 @@ "tags": "line16" }, "d795ff87-0401-4125-8978-5e03ff90bae0": { - "bbox": 10755, + "bbox": 10823, "defaultlabel": "XXXXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 2, "mirror": "", @@ -7239,7 +7257,7 @@ "e374c174-df8c-4cbf-b2ef-8ee59101f1a0": { "alsoswitch": 0, "automatic": false, - "bbox": 10761, + "bbox": 10829, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -7260,7 +7278,7 @@ "e709491b-e065-46cf-a3ce-d893cab6cb8d": { "alsoswitch": 0, "automatic": true, - "bbox": 10766, + "bbox": 10834, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -7290,7 +7308,7 @@ false, "" ], - "bbox": 10783, + "bbox": 10851, "dccaspects": [ [ [ @@ -8160,7 +8178,7 @@ false, "" ], - "bbox": 10800, + "bbox": 10868, "dccaspects": [ [ [ @@ -9138,11 +9156,12 @@ }, "general": { "automation": true, + "buttonsize": 8, "editmode": true, "filename": "/home/john/model-railway-signalling/configuration_examples/sidings_example.sig", "info": "Document your layout here", "spadpopups": false, - "version": "Version 4.6.1" + "version": "Version 4.7.0" }, "gpio": { "portmappings": [ diff --git a/configuration_examples/single_line_semaphore_example.sig b/configuration_examples/single_line_semaphore_example.sig index b6ac01f8..c16986f1 100644 --- a/configuration_examples/single_line_semaphore_example.sig +++ b/configuration_examples/single_line_semaphore_example.sig @@ -24,7 +24,7 @@ false, "" ], - "bbox": 10903, + "bbox": 10971, "dccaspects": [ [ [ @@ -915,7 +915,7 @@ false, "" ], - "bbox": 10929, + "bbox": 10997, "dccaspects": [ [ [ @@ -1795,9 +1795,10 @@ ] }, "0b251a10-3b3a-44be-89e2-f6954d641b29": { - "bbox": 10933, + "bbox": 11001, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 7, "mirror": "", @@ -1817,7 +1818,7 @@ false, "" ], - "bbox": 10961, + "bbox": 11029, "dccaspects": [ [ [ @@ -2709,7 +2710,7 @@ false, "" ], - "bbox": 10987, + "bbox": 11055, "dccaspects": [ [ [ @@ -3600,7 +3601,7 @@ false, "" ], - "bbox": 11015, + "bbox": 11083, "dccaspects": [ [ [ @@ -4492,7 +4493,7 @@ false, "" ], - "bbox": 11043, + "bbox": 11111, "dccaspects": [ [ [ @@ -5384,7 +5385,7 @@ false, "" ], - "bbox": 11069, + "bbox": 11137, "dccaspects": [ [ [ @@ -6270,7 +6271,7 @@ 0, 0 ], - "bbox": 11075, + "bbox": 11143, "colour": "black", "endx": 1400, "endy": 100, @@ -6282,9 +6283,10 @@ "tags": "line1" }, "8b2daea0-c49c-4dc2-880e-73ec00bb4a7d": { - "bbox": 11079, + "bbox": 11147, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 3, "mirror": "", @@ -6293,7 +6295,7 @@ "tags": "section3" }, "8cd209c0-9f73-42bd-ac04-e555e25372e5": { - "bbox": 11094, + "bbox": 11162, "bellsound": "bell-ring-01.wav", "item": "instrument", "itemid": 1, @@ -6311,7 +6313,7 @@ 0, 0 ], - "bbox": 11100, + "bbox": 11168, "colour": "black", "endx": 200, "endy": 150, @@ -6334,7 +6336,7 @@ false, "" ], - "bbox": 11128, + "bbox": 11196, "dccaspects": [ [ [ @@ -7215,7 +7217,7 @@ ] }, "9f0084b0-536d-4ba4-8429-d507dfe749f3": { - "bbox": 11143, + "bbox": 11211, "bellsound": "bell-ring-04.wav", "item": "instrument", "itemid": 2, @@ -7227,9 +7229,10 @@ "tags": "instrument2" }, "a00e5871-328c-4d71-a1df-a464efe5cce4": { - "bbox": 11147, + "bbox": 11215, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 4, "mirror": "", @@ -7240,7 +7243,7 @@ "a26a7440-68cc-4d3a-8186-dda77da27e7a": { "alsoswitch": 0, "automatic": false, - "bbox": 11154, + "bbox": 11222, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -7300,9 +7303,10 @@ "ybuttonoffset": 0 }, "a6aaaf9c-ad0e-40dc-adda-60b9daabc3a9": { - "bbox": 11158, + "bbox": 11226, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 2, "mirror": "", @@ -7317,7 +7321,7 @@ 0, 0 ], - "bbox": 11164, + "bbox": 11232, "colour": "black", "endx": 1200, "endy": 100, @@ -7329,9 +7333,10 @@ "tags": "line3" }, "b9d8ab0e-cd28-4590-ba49-36393b716aee": { - "bbox": 11168, + "bbox": 11236, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 1, "mirror": "", @@ -7346,7 +7351,7 @@ 0, 0 ], - "bbox": 11174, + "bbox": 11242, "colour": "black", "endx": 200, "endy": 100, @@ -7364,7 +7369,7 @@ 0, 0 ], - "bbox": 11180, + "bbox": 11248, "colour": "black", "endx": 225, "endy": 125, @@ -7382,7 +7387,7 @@ 0, 0 ], - "bbox": 11186, + "bbox": 11254, "colour": "black", "endx": 1400, "endy": 150, @@ -7394,9 +7399,10 @@ "tags": "line6" }, "cd47274b-172c-4b41-b8be-6c3d73d17084": { - "bbox": 11190, + "bbox": 11258, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 6, "mirror": "", @@ -7407,7 +7413,7 @@ "d79d5363-5db2-49c7-bb7a-197d8f798b69": { "alsoswitch": 0, "automatic": false, - "bbox": 11197, + "bbox": 11265, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -7467,9 +7473,10 @@ "ybuttonoffset": 0 }, "e1580065-8556-4f74-924e-9a88db52729d": { - "bbox": 11201, + "bbox": 11269, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 5, "mirror": "", @@ -7484,7 +7491,7 @@ 0, 0 ], - "bbox": 11207, + "bbox": 11275, "colour": "black", "endx": 1150, "endy": 150, @@ -7547,11 +7554,12 @@ }, "general": { "automation": true, + "buttonsize": 8, "editmode": false, "filename": "/home/john/model-railway-signalling/configuration_examples/single_line_semaphore_example.sig", "info": "This layout is an example of a single line between two 'block sections', with the points and signals in\nin each block section interlocked - e.g. signal 1 is interlocked with point 2 and opposing signals 3 & 4.\nThe distant signals are also interlocked with the home signals ahead - e.g. signal 4 can only be cleared\nif signal 3 has already been cleared - note it can be returned to 'on' at any time.\n\nBlock instruments are used to coordinate train movements between the two block sections and allow \ncommunication via bell codes (right click on the 'Telegraph' button to bring up a list of common bell \ncodes). In this example, the 'starter' signals for each block section have been interlocked with their \nrespective instruments (and the instruments linked together) - so signals 1/2 can only be cleared when\ninstrument 2 (on the right hand side) has been set to 'line clear' and signals 5/6 can only be cleared \nwhen instrument 1 (on the left hand side) has been set to 'line clear'.\n\nTrack sensors (such as those from TrainTech, Heathcote Electronics, DCC Concepts etc) can be located \nwith each signal and connected in to the R-Pi's GPIO ports (via appropriate opto isolator circuits).\nEach GPIO port can then be mapped to a signal to generate 'signal passed' events - In 'edit' mode,\nclick on a signal and select the 'automation' tab to view the configuration. 'Signal passed' events\ncan also be triggered by clicking on the small button at the base of each signal (as an aid to \ndevelopment and testing of signalling schemes before going 'live' on the layout).\n\nTrack sections have bene included on the schematic to provide a mimic display of track occupancy (when\na train passes a signal (signal is 'off') it gets passed from the section behind to the section ahead).\nThe desired behavior is again configured via the 'automation' tab of each signal. For example, signal 3\ncontrols two 'routes' so when passed, section 3 will be cleared and either section 1 or 2 will be set \nto occupied, depending on which route the signal is cleared for.\n\nAll main signals are configured to be 'overridden' if the track section ahead is occupied. This means\nthat each signal will automatically change from 'off' to 'on' when passed (section ahead is occupied) \nand then revert to'off' as soon as the section ahead is cleared.\n\nTo demonstrate all the above in action, set 'run' mode and reset the layout to set all signals, points\nand track sections back to their default states. \n\nSignalman 1 - Right click the top left track section and enter a train designation code of your choice \n(this sets the track section to 'occupied'). Unlock, switch and re-lock point 2. On the LH instrument,\nuse the telegraph key to call attention and ask if the line is clear (just for a bit of fun). \n\nSignalman 2 - Set the RH instrument to 'clear' (this will finally unlock signal 1), set signal 7 to \n'off' and then set signal 8 to 'off' to set the route through the block section \n\nSignalman 1 - Set Signal 1 to 'off' and move the train through the schematic, clicking the 'signal \npassed' button at the base of each signal along the route in turn (don't forget to telegraph 'train \nentering section' so signalman 2 knows they need to set their instrument to 'occupied'). \n\nSignalman 2 - When the train has cleared the home signal, telegraph 'train arrived' and revert the \nRH instrument back to 'line blocked, in readiness for the next movement.\n\nThe use of block instruments in this example may seem like overkill (this is only a single application\nwith a single user after all) but with MQTT networking you can create and configure any number of linked\napplication instances. Only the application instance acting as the DCC command station needs to be run\nup on the R-Pi (other instances can be run up on Windows, iOS, linux or any other platform / operating\nsystem combiination that supports python). This provides the potential for splitting large layouts into \ndifferent signalling areas each with a signalman responsible for their area\nDocument your layout here\n\n", "spadpopups": false, - "version": "Version 4.6.1" + "version": "Version 4.7.0" }, "gpio": { "portmappings": [], diff --git a/model_railway_signals/editor/common.py b/model_railway_signals/editor/common.py index c9542d3c..ddd3156c 100644 --- a/model_railway_signals/editor/common.py +++ b/model_railway_signals/editor/common.py @@ -4,29 +4,36 @@ # Provides the following 'primitive' classes for use across the editor UI # CreateToolTip(widget,tool_tip) # check_box(Tk.Checkbutton) -# state_box(Tk.Checkbutton) +# state_box(check_box) # entry_box(Tk.Entry) # integer_entry_box(entry_box) # dcc_entry_box(integer_entry_box) +# validated_dcc_entry_box(dcc_entry_box) # int_item_id_entry_box (integer_entry_box) # str_item_id_entry_box(entry_box) # int_str_item_id_entry_box(entry_box) # scrollable_text_frame(Tk.Frame) # # Provides the following 'compound' UI elements for the application -# object_id_selection(Tk.integer_entry_box) +# object_id_selection(Tk.integer_entry_box) ######### TO REVIEW ######### +# validated_dcc_command_entry() - combines int_entry_box and state_box # point_interlocking_entry() - combines int_item_id_entry_box and state_box -# dcc_command_entry() - combines dcc_entry_box and state_box -# signal_route_selections() - combines int_item_id_entry_box and 5 state_boxes -# signal_route_frame() - read only list of signal_route_selections() -# selection_buttons() - combines multiple RadioButtons -# colour_selection() - Allows the colour of an item to be changed -# window_controls() - apply/ok/reset/cancel +# signal_route_selections() - combines int_item_id_entry_box and 5 state_boxes ######### TO REVIEW ######### +# signal_route_frame() - read only list of signal_route_selections() ######### TO REVIEW ######### +# selection_buttons() - combines multiple RadioButtons ######### TO REVIEW ######### +# colour_selection() - Allows the colour of an item to be changed ######### TO REVIEW ######### +# window_controls() - apply/ok/reset/cancel ######### TO REVIEW ######### +# row_of_widgets() - Pass in the base class to create a fixed length row of the base class +# row_of_validated_dcc_commands() - A fixed length (user specified) row of DCC commands +# entry_box_grid() - an expandable grid of widgets ######### TO REVIEW ######### #------------------------------------------------------------------------------------ import tkinter as Tk from tkinter import colorchooser +from ..library import dcc_control +from ..library import points + #------------------------------------------------------------------------------------ # Class to create a tooltip for a tkinter widget - Acknowledgements to Stack Overflow # https://stackoverflow.com/questions/3221956/how-do-i-display-tooltips-in-tkinter @@ -84,19 +91,23 @@ def hidetip(self): self.tw= None if tw: tw.destroy() +##################################################################################### +########################### COMMON BASIC UI ELEMENTS ################################ +##################################################################################### + #------------------------------------------------------------------------------------ -# Base class for a generic check_box - Builds on the tkinter checkbutton class. -# Note the responsibility of the instantiating func/class to 'pack' the check_box. +# Common class for a generic 'check_box' - Builds on the tkinter checkbutton class. # -# Public class methods provided are: +# Main class methods used by the editor are: # "set_value" - will set the check_box state (bool) # "get_value" - will return the state (False if disabled) (bool) # "disable/disable1/disable2" - disables/blanks the check_box -# "enable/enable1/enable2" enables/loads the check_box (with the last state) +# "enable/enable1/enable2" - enables/loads the check_box (with the last state) +# "pack" - for packing the UI element # -# Class methods/objects intended for use by child classes that inherit: +# Class methods/objects for use by child classes: # "TT.text" - The tooltip for the check_box (to change the tooltip text) -# "state" - is the current check_box value +# "state" - is the current check_box value (False if disabled) # # Note that check_box is created as 'enabled' - the individual functions provide # an AND function where all three flags need to be 'enabled' to enable the @@ -104,39 +115,41 @@ def hidetip(self): #------------------------------------------------------------------------------------ class check_box(Tk.Checkbutton): - def __init__(self, parent_frame, label:str, tool_tip:str, width:int=None, callback=None): + def __init__(self, parent_frame, label:str, tool_tip:str, callback=None, width:int=None, read_only:bool=False): # Create the local instance configuration variables - # 'selection' is the current CB state and 'state' is the last entered state - # 'enabled' is the flag to track whether the checkbox is enabled or not self.parent_frame = parent_frame self.callback = callback self.selection = Tk.BooleanVar(self.parent_frame, False) + self.read_only = read_only + # 'selection' is the current CB state and 'state' is the last entered state + # The 'enabled' flags track whether the checkbox is enabled or not self.state = False self.enabled0 = True self.enabled1 = True self.enabled2 = True - # Create the checkbox and associated tool tip - if width is None: - super().__init__(self.parent_frame, text=label, anchor="w", - variable=self.selection, command=self.cb_updated) - else: - super().__init__(self.parent_frame, width = width, text=label, anchor="w", - variable=self.selection, command=self.cb_updated) + # Create the checkbox (defaulted to the width of the label) and associated tool tip + super().__init__(self.parent_frame, text=label, anchor="w", + variable=self.selection, command=self.checkbox_updated) self.TT = CreateToolTip(self, tool_tip) + # Set the width of the checkbox if specified + if width is not None: self.configure(width=width) + # Disable the check box if it is read only (can never be subsequently enabled) + if self.read_only: self.configure(state="disabled") - def cb_updated(self): + def checkbox_updated(self): # Focus on the Checkbox to remove focus from other widgets (such as EBs) self.parent_frame.focus() self.state = self.selection.get() if self.callback is not None: self.callback() def enable_disable_checkbox(self): - if self.enabled0 and self.enabled1 and self.enabled2: - self.selection.set(self.state) - self.configure(state="normal") - else: - self.configure(state="disabled") - self.selection.set(False) + if not self.read_only: + if self.enabled0 and self.enabled1 and self.enabled2: + self.selection.set(self.state) + self.configure(state="normal") + else: + self.selection.set(False) + self.configure(state="disabled") def enable(self): self.enabled0 = True @@ -166,129 +179,87 @@ def set_value(self, new_value:bool): self.state = new_value if self.enabled0 and self.enabled1 and self.enabled2: self.selection.set(new_value) - + else: + self.selection.set(False) + def get_value(self): # Will always return False if disabled - return (self.selection.get()) + return(self.selection.get()) #------------------------------------------------------------------------------------ -# Base class for a generic state_box (like a check box but with labels for off/on -# and blank when disabled) - Builds on the tkinter checkbutton class. -# Note the responsibility of the instantiating func/class to 'pack' the state_box. +# Common class for a generic 'state_box' (like a check_box but with labels for off/on +# and blank when disabled) - Builds on the check_box class (defined above). # -# Public class methods provided are: +# Main class methods used by the editor are: # "set_value" - will set the state_box state (bool) # "get_value" - will return the current state (False if disabled) (bool) # "disable/disable1/disable2" - disables/blanks the state_box # "enable/enable1/enable2" enables/loads the state_box (with the last state) +# "pack" - for packing the UI element # -# Class methods/objects intended for use by child classes that inherit: +# Class methods/objects for use by child classes: # "TT.text" - The tooltip for the check_box (to change the tooltip text) -# "state" - is the current check_box value -# -# Note that state_box is created as 'enabled' - the individual functions provide -# an AND function where all three flags need to be 'enabled' to enable the -# state_box. Any of the 3 flags can be 'disabled' to disable the state_box. +# "state" - is the current check_box value (False if disabled) #------------------------------------------------------------------------------------ -class state_box(Tk.Checkbutton): +class state_box(check_box): def __init__(self, parent_frame, label_off:str, label_on:str, tool_tip:str, width:int=None, callback=None, read_only:bool=False): # Create the local instance configuration variables # 'selection' is the current CB state and 'state' is the last entered state # 'enabled' is the flag to track whether the checkbox is enabled or not - self.parent_frame = parent_frame - self.callback = callback self.labelon = label_on self.labeloff = label_off - self.read_only = read_only - self.selection = Tk.BooleanVar(self.parent_frame, False) - self.state = False - self.enabled0 = True - self.enabled1 = True - self.enabled2 = True - # Create the checkbox and associated tool tip - if width is None: - super().__init__(parent_frame, indicatoron = False, - text=self.labeloff, variable=self.selection, command=self.cb_updated) - else: - super().__init__(parent_frame, indicatoron = False, width=width, - text=self.labeloff, variable=self.selection, command=self.cb_updated) - if self.read_only: self.configure(state="disabled") - self.TT = CreateToolTip(self, tool_tip) + # Create the checkbox and associated tool tip (in the parent class) + super().__init__(parent_frame, label=self.labeloff, tool_tip=tool_tip, + callback=callback, width=width, read_only=read_only) + # Now make this a state box + self.configure(indicatoron=False, anchor="c") - def cb_updated(self): - # Focus on the Checkbox to remove focus from other widgets (such as EBs) - self.parent_frame.focus() - self.update_cb_state() - if self.callback is not None: self.callback() - - def update_cb_state(self): - if self.enabled0 and self.enabled1 and self.enabled2: - self.state = self.selection.get() - if self.state: self.configure(text=self.labelon) - else: self.configure(text=self.labeloff) - else: - self.configure(text="") - self.selection.set(False) + def checkbox_updated(self): + # Update the label to show the updated user selection + if self.selection.get(): self.configure(text=self.labelon) + else: self.configure(text=self.labeloff) + # Set the state of the checkbox and make the callback + super().checkbox_updated() def enable_disable_checkbox(self): + # Call the parent class to update the internal state + super().enable_disable_checkbox() + # Update the label to show the state if enabled (or blank if disabled) if not self.read_only: if self.enabled0 and self.enabled1 and self.enabled2: - self.selection.set(self.state) + if self.state: self.configure(text=self.labelon) + else: self.configure(text=self.labeloff) else: - self.selection.set(False) - self.update_cb_state() - - def enable(self): - self.enabled0 = True - self.enable_disable_checkbox() - - def disable(self): - self.enabled0 = False - self.enable_disable_checkbox() - - def enable1(self): - self.enabled1 = True - self.enable_disable_checkbox() - - def disable1(self): - self.enabled1 = False - self.enable_disable_checkbox() - - def enable2(self): - self.enabled2 = True - self.enable_disable_checkbox() - - def disable2(self): - self.enabled2 = False - self.enable_disable_checkbox() + self.configure(text="") def set_value(self, new_value:bool): - self.selection.set(new_value) - self.state = new_value - self.update_cb_state() - - def get_value(self,): - # Will always return False if disabled - return (self.selection.get()) + # Update the label to show the new value if enabled (or blank if disabled) + if self.enabled0 and self.enabled1 and self.enabled2: + if new_value: self.configure(text=self.labelon) + else: self.configure(text=self.labeloff) + else: + self.configure(text="") + # Call the parent class to finish processing the update + super().set_value(new_value) #------------------------------------------------------------------------------------ -# Common Base Class for a generic entry_box - Builds on the tkinter Entry class. +# Common Class for a generic 'entry_box' - Builds on the tkinter Entry class. # This will accept any string value to be entered/displayed with no validation. -# Note the responsibility of the instantiating func/class to 'pack' the entry_box. # -# Public class methods provided are: +# Main class methods used by the editor are: # "set_value" - set the initial value of the entry_box (string) -# "get_value" - get the last "validated" value of the entry_box (string) +# "get_value" - get the current value of the entry_box (string) # "validate" - This gets overridden by the child class function # "disable/disable1/disable2" - disables/blanks the entry_box # "enable/enable1/enable2" enables/loads the entry_box (with the last value) +# "pack" - for packing the UI element # -# Class methods/objects intended for use by child classes that inherit: +# Class methods/objects for use by child classes: # "set_validation_status" - to be called following external validation # "TT.text" - The tooltip for the entry_box (to change the tooltip text) -# "entry" - is the current entry_box value (may or may not be valid) +# "entry" - is the current entry_box value (string) # # Note that entry_box is created as 'enabled' - the individual functions provide # an AND function where all three flags need to be 'enabled' to enable the @@ -306,9 +277,9 @@ def __init__(self, parent_frame, width:int, tool_tip:str, callback=None): self.tool_tip = tool_tip self.entry = Tk.StringVar(self.parent_frame, "") self.value = "" - self.enabled0 = True - self.enabled1 = True - self.enabled2 = True + self.enabled0 = True + self.enabled1 = True + self.enabled2 = True # Create the entry box, event bindings and associated default tooltip super().__init__(self.parent_frame, width=width, textvariable=self.entry, justify='center') self.bind('', self.entry_box_updated) @@ -387,25 +358,19 @@ def get_value(self): #------------------------------------------------------------------------------------ # Common Class for an integer_entry_box - builds on the entry_box class (above). # This will only allow valid integers (within the defined range) to be entered. -# Note the responsibility of the instantiating func/class to 'pack' the entry_box. # -# Public class instance methods inherited from the base Entry Box class are: +# Main class methods used by the editor are: +# "set_value" - set the initial value of the entry_box (int) +# "get_value" - get the current value of the entry_box (int) +# "validate" - Validates the entry is an integer within the specified range # "disable/disable1/disable2" - disables/blanks the entry_box # "enable/enable1/enable2" enables/loads the entry_box (with the last value) +# "pack" - for packing the UI element # -# Public class instance methods provided/overridden by this class are -# "set_value" - set the initial value of the entry_box (int) -# "get_value" - get the last "validated" value of the entry_box (int) -# "validate" - Validates an integer, within range and whether empty -# -# Inherited class methods/objects intended for use by child classes that inherit: +# Class methods/objects for use by child classes: # "set_validation_status" - to be called following external validation # "TT.text" - The tooltip for the entry_box (to change the tooltip text) -# "entry" - is the current entry_box value (may or may not be valid) -# -# Note that entry_box is created as 'enabled' - the individual functions provide -# an AND function where all three flags need to be 'enabled' to enable the -# entry_box. Any of the 3 flags can be 'disabled' to disable the entry_box. +# "entry" - is the current entry_box value (string) #------------------------------------------------------------------------------------ class integer_entry_box(entry_box): @@ -458,20 +423,20 @@ def get_value(self): #------------------------------------------------------------------------------------ # Common class for a DCC address entry box - builds on the integer_entry_box class -# Adds additional validation to ensure the DCC Address is within the valid range. -# Note the responsibility of the instantiating func/class to 'pack' the entry_box. +# Adds additional validation to ensure the entry is within the DCC address range. # -# Public class instance methods inherited from the base entry_box class are: +# Main class methods used by the editor are: # "set_value" - set the initial value of the entry_box (int) -# "get_value" - get the last "validated" value of the entry_box (int) -# "validate" - Validates an integer, within range and whether empty +# "get_value" - get the current value of the entry_box (int) +# "validate" - Validates the entry is an integer between 1 and 2047 (or blank) # "disable/disable1/disable2" - disables/blanks the entry_box # "enable/enable1/enable2" enables/loads the entry_box (with the last value) +# "pack" - for packing the UI element # -# Inherited class methods/objects intended for use by child classes that inherit: +# Class methods/objects for use by child classes: # "set_validation_status" - to be called following external validation # "TT.text" - The tooltip for the entry_box (to change the tooltip text) -# "entry" - is the current entry_box value (may or may not be valid) +# "entry" - is the current entry_box value (string) #------------------------------------------------------------------------------------ class dcc_entry_box(integer_entry_box): @@ -481,6 +446,56 @@ def __init__(self, parent_frame, callback=None, super().__init__(parent_frame, width=4 , min_value=1, max_value=2047, tool_tip=tool_tip, callback=callback) +#------------------------------------------------------------------------------------ +# Common class for a validated_dcc_entry_box - builds on the common DCC Entry Box +# class and adds validation to ensure the DCC address is not used by anything else +# The function needs knowledge of the current item type (provided at initialisation +# time) and the current item ID (provided when the value is set). +# +# Main class methods used by the editor are: +# "set_value" - set the initial value of the entry_box (int) +# "get_value" - get the current value of the entry_box (int) +# "validate" - Validates entry is a DCC address and not assigned to anything else +# "disable/disable1/disable2" - disables/blanks the entry_box +# "enable/enable1/enable2" enables/loads the entry_box (with the last value) +# "pack" - for packing the UI element +# +# Class methods/objects for use by child classes: +# "set_validation_status" - to be called following external validation +# "TT.text" - The tooltip for the entry_box (to change the tooltip text) +# "entry" - is the current entry_box value (string) +#------------------------------------------------------------------------------------ + +class validated_dcc_entry_box(dcc_entry_box): + def __init__(self, parent_frame, item_type:str, callback=None): + # We need the current Item ID and Item Type to validate the DCC Address entry. The + # Item Type ("Signal", "Point" or "Switch" is supplied at class initialisation time. + # The item ID is supplied when the 'set_value' function is called (as this may change) + self.current_item_id = 0 + self.current_item_type = item_type + super().__init__(parent_frame, callback=callback) + + def validate(self): + # Do the basic item validation first (exists and not current item ID) + valid = super().validate(update_validation_status=False) + if valid and self.entry.get() != "": + # Ensure the address is not mapped to another signal or point. Note that to cater for Semaphore + # Signals with secondary distant arms we also need to check for Signal IDs + 1000 + dcc_address = int(self.entry.get()) + dcc_mapping = dcc_control.dcc_address_mapping(dcc_address) + if dcc_mapping is not None and (dcc_mapping[0] != self.current_item_type or + (dcc_mapping[1] != self.current_item_id and dcc_mapping[1] != self.current_item_id + 1000)): + # We need to correct the mapped signal ID for secondary distants + if dcc_mapping[0] == "Signal" and dcc_mapping[1] > 1000: dcc_mapping[1] = dcc_mapping[1] - 1000 + self.TT.text = ("DCC address is already mapped to "+dcc_mapping[0]+" "+str(dcc_mapping[1])) + valid = False + self.set_validation_status(valid) + return(valid) + + def set_value(self, value:int, item_id:int): + self.current_item_id = item_id + super().set_value(value) + #------------------------------------------------------------------------------------ # Common class for an int_item_id_entry_box - builds on the integer_entry_box # These classes are for entering local signal/point/instrument/section IDs (integers) @@ -488,22 +503,19 @@ def __init__(self, parent_frame, callback=None, # The class uses the 'exists_function' to check that the item exists on the schematic # If the the current item ID is specified (via the set_item_id function) then the class # also validates the entered value is not the same as the current item ID. -# Note the responsibility of the instantiating func/class to 'pack' the entry_box. # -# Public class instance methods inherited from the base integer_entry_box are: -# "get_value" - get the last "validated" value of the entry_box (int) +# Main class methods used by the editor are: +# "set_value" - set the initial value of the entry_box (int) and the current ID +# "get_value" - get the current value of the entry_box (int) +# "validate" - validates entry in range (1-999) - also see comments above # "disable/disable1/disable2" - disables/blanks the entry_box # "enable/enable1/enable2" enables/loads the entry_box (with the last value) +# "pack" - for packing the UI element # -# Public class instance methods provided/overridden by this class are -# "validate" - Validation as described above -# "set_value" - set the initial value of the entry_box (int) - Also takes the -# optional current item ID (int) for validation purposes (default=0) -# -# Inherited class methods/objects intended for use by child classes that inherit: +# Class methods/objects for use by child classes: # "set_validation_status" - to be called following external validation # "TT.text" - The tooltip for the entry_box (to change the tooltip text) -# "entry" - is the current entry_box value (may or may not be valid) +# "entry" - is the current entry_box value (string) # "current_item_id" - for any additional validation that may be required #------------------------------------------------------------------------------------ @@ -543,21 +555,19 @@ def set_value(self, value:int, item_id:int=0): # This class is for REMOTE item IDs (subscribed to via MQTT networking) where the ID # is a str in the format 'NODE-ID'. If the 'exists_function' is specified then the # validation function checks that the item exists (i.e. has been subscribed to). -# Note the responsibility of the instantiating func/class to 'pack' the entry_box. # -# Public class instance methods inherited from the base entry_box class are: +# Main class methods used by the editor are: # "set_value" - set the initial value of the entry_box (str) -# "get_value" - get the last "validated" value of the entry_box (str) +# "get_value" - get the current value of the entry_box (str) +# "validate" - Validation described in comments above # "disable/disable1/disable2" - disables/blanks the entry_box # "enable/enable1/enable2" enables/loads the entry_box (with the last value) +# "pack" - for packing the UI element # -# Public class instance methods provided/overridden by this class are -# "validate" - Validation as described above -# -# Inherited class methods/objects intended for use by child classes that inherit: +# Class methods/objects for use by child classes: # "set_validation_status" - to be called following external validation # "TT.text" - The tooltip for the entry_box (to change the tooltip text) -# "entry" - is the current entry_box value (may or may not be valid) +# "entry" - is the current entry_box value (string) #------------------------------------------------------------------------------------ class str_item_id_entry_box(entry_box): @@ -603,26 +613,23 @@ def validate(self, update_validation_status=True): # validation function checks that the item exists (i.e. has been subscribed to). # If the the current item ID is specified (via the set_item_id function) then the class # also validates the entered value is not the same as the current item ID. -# Note the responsibility of the instantiating func/class to 'pack' the entry_box. # -# Public class instance methods inherited from the base entry_box class are: -# "get_value" - get the last "validated" value of the entry_box (str) +# Main class methods used by the editor are: +# "set_value" - set the initial value of the entry_box (str) and the current ID +# "get_value" - get the current value of the entry_box (str) +# "validate" - Validation described in comments above # "disable/disable1/disable2" - disables/blanks the entry_box # "enable/enable1/enable2" enables/loads the entry_box (with the last value) +# "pack" - for packing the UI element # -# Public class instance methods provided/overridden by this class are -# "validate" - Validation as described above -# "set_value" - set the initial value of the entry_box (str) - Also takes the -# optional current item ID (int) for validation purposes (default=0) -# -# Inherited class methods/objects intended for use by child classes that inherit: +# Class methods/objects for use by child classes: # "set_validation_status" - to be called following external validation # "TT.text" - The tooltip for the entry_box (to change the tooltip text) -# "entry" - is the current entry_box value (may or may not be valid) +# "entry" - is the current entry_box value (string) # "current_item_id" - for any additional validation that may be required #------------------------------------------------------------------------------------ -class str_int_item_id_entry_box (entry_box): +class str_int_item_id_entry_box(entry_box): def __init__(self, parent_frame, tool_tip:str, width:int=8, callback=None, exists_function=None): # We need to know the current item ID for validation purposes self.current_item_id = 0 @@ -675,13 +682,13 @@ def set_value(self, value:str, item_id:int=0): # to re-size automatically (within the specified limits) as text is entered. # The text box will 'fit' to the content unless max or min dimentions are # specified for the width and/or height - then the scrollbars can be used. -# Note the responsibility of the instantiating func/class to 'pack' the entry_box. # -# Public class instance methods provided by this class are +# Main class methods used by the editor are: # "set_value" - will set the current value (str) # "get_value" - will return the current value (str) -# "set_justification" - set justification (int - 1=left, 2=center, 3=right) +# "set_justification" - set justification (int: 1=left, 2=center, 3=right) # "set_font" - set the font (font:str, font_size:int, font_style:str) +# "pack" - for packing the UI element #------------------------------------------------------------------------------------ class scrollable_text_frame(Tk.Frame): @@ -770,6 +777,10 @@ def set_justification(self, value:int): def set_font(self, font:str, font_size:int, font_style:str): self.text_box.configure(font=(font, font_size, font_style)) +##################################################################################### +######################## COMMON COMPOUND UI ELEMENTS ################################ +##################################################################################### + #------------------------------------------------------------------------------------ # Compound UI element for an object_id_selection LabelFrame - uses the integer_entry_box. # This is used across all object windows for displaying / changing the item ID. @@ -785,6 +796,7 @@ def set_font(self, font:str, font_size:int, font_style:str): # "set_value" - set the initial value of the entry_box (int) # "validate" - Validates that the entered Item ID is "free" (and can therefore be # assigned to this item) or is being changed back to the initial value. +######################## TO REVIEW AND POSSIBLY REFACTOR ############################ #------------------------------------------------------------------------------------ class object_id_selection(integer_entry_box): @@ -819,37 +831,100 @@ def validate(self): def set_value(self, value:int): self.current_item_id = value super().set_value(value) + +#------------------------------------------------------------------------------------ +# Compound UI element for a validated_dcc_command_entry (address + command logic). +# Uses the validated_dcc_entry_box and state_box classes, with the state_box only +# only enabled when a valid DCC address has been entered into the entry_box. +# +# Main class methods used by the editor are: +# "validate" - validate the current entry_box value and return True/false +# "set_value" - will set the current value [add:int, state:bool] and item ID (int) +# "get_value" - will return the last "valid" value [address:int, state:bool] +# "disable" - disables/blanks the entry_box (and associated state button) +# "enable" enables/loads the entry_box (and associated state button) +# "pack" for packing the compound UI element +# +# The validated_dcc_entry_box class needs the current Item ID and Item Type to validate +# the DCC Address entry. The Item Type ("Signal", "Point" or "Switch" is supplied at +# class initialisation time. The item ID is supplied by the 'set_value' function. +#------------------------------------------------------------------------------------ + +class validated_dcc_command_entry(Tk.Frame): + def __init__(self, parent_frame, item_type:str): + # Use the parent class frame to pack everything into + super().__init__(parent_frame) + # Create the address entry box and the associated dcc state box + self.EB = validated_dcc_entry_box(self, item_type=item_type, callback=self.eb_updated) + self.EB.pack(side=Tk.LEFT) + self.CB = state_box(self, label_off="OFF", label_on="ON", + width=4, tool_tip="Set the DCC logic for the command") + self.CB.pack(side=Tk.LEFT) + # Disable the checkbox (default state when no address is entered) + self.CB.disable() + + def eb_updated(self): + if self.EB.entry.get() == "": + self.CB.disable() + else: + self.CB.enable() + + def validate(self): + return (self.EB.validate()) + + def enable(self): + self.EB.enable() + self.eb_updated() + + def disable(self): + self.EB.disable() + self.eb_updated() + def set_value(self, dcc_command:list[int,bool], item_id:int): + # The dcc_command comprises a 2 element list of [DCC_Address, DCC_State] + self.EB.set_value(dcc_command[0], item_id) + self.CB.set_value(dcc_command[1]) + self.eb_updated() + + def get_value(self): + # Returns a 2 element list of [DCC_Address, DCC_State] + # When disabled (or empty) will always return [0, False] + # When invalid will return [last valid address, current state] + return([self.EB.get_value(), self.CB.get_value()]) + #------------------------------------------------------------------------------------ -# Class for a point interlocking entry element (point_id + point_state) -# Uses the common int_item_id_entry_box and state_box classes -# Note the responsibility of the instantiating func/class to 'pack' the UI element. +# Compound UI element for a point_interlocking_entry element (point_id + point_state). +# This is broadly similar to the validated_dcc_command_entry class (above) but +# the differences mean its more appropriate to make this a class in its own right # -# Public class instance methods provided are: +# Main class methods used by the editor are: # "validate" - validate the current entry box value and return True/false # "set_value" - will set the current value [point_id:int, state:bool] # "get_value" - will return the last "valid" value [point_id:int, state:bool] # "disable" - disables/blanks the entry box (and associated state button) # "enable" enables/loads the entry box (and associated state button) +# "pack" for packing the compound UI element #------------------------------------------------------------------------------------ class point_interlocking_entry(Tk.Frame): - def __init__(self, parent_frame, exists_function, tool_tip:str): - self.frame = parent_frame - # create a frame to pack everything into - super().__init__(self.frame) + def __init__(self, parent_frame, tool_tip:str): + # Use the parent class frame to pack everything into + super().__init__(parent_frame) # Create the point ID entry box and associated state box (packed in the parent frame) - self.EB = int_item_id_entry_box(self.frame, exists_function=exists_function, + self.EB = int_item_id_entry_box(self, exists_function=points.point_exists, tool_tip = tool_tip, callback=self.eb_updated) self.EB.pack(side=Tk.LEFT) - self.CB = state_box(self.frame, label_off=u"\u2192", label_on="\u2191", width=2, + self.CB = state_box(self, label_off=u"\u2192", label_on="\u2191", width=2, tool_tip="Select the required state for the point (normal or switched)") self.CB.pack(side=Tk.LEFT) + # Disable the checkbox (default state when no address is entered) + self.CB.disable() def eb_updated(self): if self.EB.entry.get() == "": self.CB.disable() - else: self.CB.enable() + else: + self.CB.enable() def validate(self): return (self.EB.validate()) @@ -857,7 +932,7 @@ def validate(self): def enable(self): self.EB.enable() self.eb_updated() - + def disable(self): self.EB.disable() self.eb_updated() @@ -870,6 +945,8 @@ def set_value(self, point:[int, bool]): def get_value(self): # Returns a 2 element list of [Point_id, Point_state] + # When disabled (or empty) will always return [0, False] + # When invalid will return [last valid id, current state] return([self.EB.get_value(), self.CB.get_value()]) #------------------------------------------------------------------------------------ @@ -882,6 +959,7 @@ def get_value(self): # "get_value" - will return the last "valid" value [signal_routes_entry] # "disable" - disables/blanks the entry box (and associated state buttons) # "enable" enables/loads the entry box (and associated state buttons) +######################## TO REVIEW AND POSSIBLY REFACTOR ############################ #------------------------------------------------------------------------------------ class signal_route_selections(Tk.Frame): @@ -981,6 +1059,7 @@ def get_value(self): # # Public class instance methods provided by this class are: # "set_values" - Populates the list of signals and their routes +######################## TO REVIEW AND POSSIBLY REFACTOR ############################ #------------------------------------------------------------------------------------ class signal_route_frame(): @@ -1027,6 +1106,7 @@ def set_values(self, sig_interlocking_frame:[[int,[bool,bool,bool,bool,bool]],]) # "get_value" - will return the last "valid" value (integer 1-5) # "enable" - enable all radio buttons # "disable" - disable all radio buttons +######################## TO REVIEW AND POSSIBLY REFACTOR ############################ #------------------------------------------------------------------------------------ class selection_buttons(): @@ -1106,7 +1186,8 @@ def disable(self): self.B7.configure(state="disabled") #------------------------------------------------------------------------------------ -# Compound UI Element for Colour selection +# Compound UI Element for Colour selection. Also has an option to select "transparent" +# if the 'transparent_option' is set to True (useful for 'fill' colours # Note the responsibility of the instantiating func/class to 'pack' the Frame of # the UI element - i.e. '.frame.pack()' # @@ -1114,44 +1195,202 @@ def disable(self): # "set_value" - will set the current value (colour code string) # "get_value" - will return the last "valid" value (colour code string) # "is_open" - Test if the colour chooser is still open +######################## TO REVIEW AND POSSIBLY REFACTOR ############################ #------------------------------------------------------------------------------------ class colour_selection(): - def __init__(self, parent_frame, label:str): + def __init__(self, parent_frame, label:str, transparent_option:bool=False): # Flag to test if a colour chooser window is open or not self.colour_chooser_open = False - # Variable to hold the currently selected colour: - self.colour ='black' - # Create a frame to hold the tkinter widgets - # The parent class is responsible for packing the frame + # Variable to hold the currently selected colour (the default background colour) + self.colour = 'Grey85' + # Create a Labelframe to hold all the tkinter widgets self.frame = Tk.LabelFrame(parent_frame,text=label) - # Create a sub frame for the UI elements to centre them - self.subframe = Tk.Frame(self.frame) - self.subframe.pack() - self.label2 = Tk.Label(self.subframe, width=3, bg=self.colour, borderwidth=1, relief="solid") - self.label2.pack(side=Tk.LEFT, padx=2, pady=2) - self.TT2 = CreateToolTip(self.label2, "Currently selected colour") - self.B1 = Tk.Button(self.subframe, text="Change", command=self.update) + # Create a sub frame for the selected colour and the colour chooser button + self.subframe1 = Tk.Frame(self.frame) + self.subframe1.pack() + self.label1 = Tk.Label(self.subframe1, width=3, bg=self.colour, borderwidth=1, relief="solid") + self.label1.pack(side=Tk.LEFT, padx=2, pady=2) + self.TT2 = CreateToolTip(self.label1, "Currently selected colour") + self.B1 = Tk.Button(self.subframe1, text="Change", command=self.colour_updated) self.B1.pack(side=Tk.LEFT, padx=2, pady=2) self.TT2 = CreateToolTip(self.B1, "Open colour chooser dialog") + # Create the checkbox for "transparent (only pack it if specified at creation time) + self.transparent = check_box(self.frame,label="Transparent ",callback=self.transparent_updated, + tool_tip= "Select to make transparent (no fill)") + if transparent_option: self.transparent.pack() - def update(self): + def colour_updated(self): self.colour_chooser_open = True colour_code = colorchooser.askcolor(self.colour, parent=self.frame, title ="Select Colour") self.colour = colour_code[1] - self.label2.config(bg=self.colour) + self.label1.config(bg=self.colour) self.colour_chooser_open = False - + + def transparent_updated(self): + if self.transparent.get_value(): + self.label1.config(text="X", bg='Grey85') + else: + self.label1.config(text="", bg=self.colour) + def get_value(self): - return(self.colour) + if self.transparent.get_value(): colour = "" + else: colour = self.colour + return(colour) def set_value(self,colour:str): - self.colour = colour - self.label2.config(bg=self.colour) + if colour == "": + self.transparent.set_value(True) + self.colour = 'Grey85' + else: + self.transparent.set_value(False) + self.colour = colour + self.transparent_updated() def is_open(self): return(self.colour_chooser_open) +#------------------------------------------------------------------------------------ +# Compound UI element for the Apply/OK/Reset/Cancel Buttons - will make callbacks +# to the specified "load_callback" and "save_callback" functions as appropriate +# Note the responsibility of the instantiating func/class to 'pack' the Frame of +# the UI element - i.e. '.frame.pack()' +######################## TO REVIEW AND POSSIBLY REFACTOR ############################ +#------------------------------------------------------------------------------------ + +class window_controls(): + def __init__(self, parent_window, load_callback, save_callback, cancel_callback): + # Create the class instance variables + self.window = parent_window + self.save_callback = save_callback + self.load_callback = load_callback + self.cancel_callback = cancel_callback + self.frame = Tk.Frame(self.window) + # Create the buttons and tooltips + self.B1 = Tk.Button (self.frame, text = "Ok",command=self.ok) + self.B1.pack(side=Tk.LEFT, padx=2, pady=2) + self.TT1 = CreateToolTip(self.B1, "Apply selections and close window") + self.B2 = Tk.Button (self.frame, text = "Apply",command=self.apply) + self.B2.pack(side=Tk.LEFT, padx=2, pady=2) + self.TT2 = CreateToolTip(self.B2, "Apply selections") + self.B3 = Tk.Button (self.frame, text = "Reset",command=self.reset) + self.B3.pack(side=Tk.LEFT, padx=2, pady=2) + self.TT3 = CreateToolTip(self.B3, "Abandon edit and reload original configuration") + self.B4 = Tk.Button (self.frame, text = "Cancel",command=self.cancel) + self.B4.pack(side=Tk.LEFT, padx=2, pady=2) + self.TT4 = CreateToolTip(self.B4, "Abandon edit and close window") + + def apply(self): + self.window.focus() + self.save_callback(False) + + def ok(self): + self.window.focus() + self.save_callback(True) + + def reset(self): + self.window.focus() + self.load_callback() + + def cancel(self): + self.cancel_callback() + +#------------------------------------------------------------------------------------ +# Base Class for a fixed row_of_widgets of the specified base class. +# All of the kwargs are passed through to the specified base class on creation +# Note the need to specify a 'default_value_to_set' to 'blank' any widgets +# beyond the provided 'list_of_values_to_set' for the 'set_values' function +# +# Main class methods used by the editor are: +# "set_values" - will set the intial values from the provided list +# "get_values" - will return the last "valid" values in a list +# "enable" - will enable all the widgets in the row +# "disable" - will disable all the widgets in the row +# "validate" - Will validate all entries +# "pack" - for packing the UI element +#------------------------------------------------------------------------------------ + +class row_of_widgets(Tk.Frame): + def __init__(self, parent_frame, base_class, columns:int, **kwargs): + # Use the parent class frame to pack everything into + super().__init__(parent_frame) + # Maintain a list of Widgets (to keep everything in scope) + self.list_of_widgets = [] + # Create the widgets for the row + for entry in range (columns): + self.list_of_widgets.append(base_class(self, **kwargs)) + self.list_of_widgets[-1].pack(side=Tk.LEFT) + + def set_values(self, list_of_values_to_set:list, default_value_to_set): + for index, widget_to_set in enumerate(self.list_of_widgets): + # Only set the value if we haven't reached the end of the list of values_to_set + # Otherwise we set the default value we have been given (to blank the widget) + # Note there may be multiple parameters so we have to unpack them + if index < len(list_of_values_to_set): + widget_to_set.set_value(*list_of_values_to_set[index]) + else: + widget_to_set.set_value(*default_value_to_set) + + def get_values(self): + # Validate all the entries to accept the current (as entered) values + self.validate() + # Compile a list of values to return (we don't remove any blanks here) + entered_values = [] + for widget in self.list_of_widgets: + entered_values.append(widget.get_value()) + return(entered_values) + + def validate(self): + valid = True + for widget in self.list_of_widgets: + if not widget.validate(): valid = False + return(valid) + + def enable(self): + for widget in self.list_of_widgets: + widget.enable() + + def disable(self): + for widget in self.list_of_widgets: + widget.disable() + +#------------------------------------------------------------------------------------ +# Class for a fixed row_of_validated_dcc_commands - builds on the row_of_widgets class +# The set_values and get_values functions are overridden to simplify the +# save and load interface of the calling editor functions +# +# Main class methods used by the editor are: +# "set_values" - will set the intial values from the provided list +# "get_values" - will return the last "valid" values in a list +# "enable" - will enable all the widgets in the row +# "disable" - will disable all the widgets in the row +# "validate" - Will validate all entries +# "pack" - for packing the UI element +#------------------------------------------------------------------------------------ + +class row_of_validated_dcc_commands(row_of_widgets): + def __init__(self, parent_frame, columns:int, item_type:str): + # Use the parent class frame to pack everything into + super().__init__(parent_frame, validated_dcc_command_entry, columns, item_type=item_type) + + def set_values(self, list_of_dcc_commands:list, item_id:int): + default_value_to_set = ([0, False], item_id) + list_of_values_to_set = [] + for dcc_command in list_of_dcc_commands: + list_of_values_to_set.append((dcc_command, item_id)) + super().set_values(list_of_values_to_set, default_value_to_set) + + def get_values(self): + # Validate all the entries to accept the current (as entered) values + self.validate() + # Compile a list of values to return (removing any blanks) + values_to_return = [] + entered_values = super().get_values() + for entered_value in entered_values: + if entered_value[0] > 0: + values_to_return.append(entered_value) + return(values_to_return) + #------------------------------------------------------------------------------------ # Base Class for a dynamic entry_box_grid of the specified base class. # All of the kwargs are passed through to the specified base class on creation @@ -1162,6 +1401,8 @@ def is_open(self): # "set_values" - will set the intial values from the provided list # "get_values" - will return the last "valid" values in a list # "validate" - Will validate all entries +######################## TO REVIEW AND POSSIBLY REFACTOR ############################ +#################### MAYBE MAKE USE OF THE ROW_OF_WIDGETS CLASS ##################### #------------------------------------------------------------------------------------ class entry_box_grid(): @@ -1254,48 +1495,4 @@ def disable(self): if entry_box.winfo_exists(): entry_box.disable() -#------------------------------------------------------------------------------------ -# Compound UI element for the Apply/OK/Reset/Cancel Buttons - will make callbacks -# to the specified "load_callback" and "save_callback" functions as appropriate -# Note the responsibility of the instantiating func/class to 'pack' the Frame of -# the UI element - i.e. '.frame.pack()' -#------------------------------------------------------------------------------------ - -class window_controls(): - def __init__(self, parent_window, load_callback, save_callback, cancel_callback): - # Create the class instance variables - self.window = parent_window - self.save_callback = save_callback - self.load_callback = load_callback - self.cancel_callback = cancel_callback - self.frame = Tk.Frame(self.window) - # Create the buttons and tooltips - self.B1 = Tk.Button (self.frame, text = "Ok",command=self.ok) - self.B1.pack(side=Tk.LEFT, padx=2, pady=2) - self.TT1 = CreateToolTip(self.B1, "Apply selections and close window") - self.B2 = Tk.Button (self.frame, text = "Apply",command=self.apply) - self.B2.pack(side=Tk.LEFT, padx=2, pady=2) - self.TT2 = CreateToolTip(self.B2, "Apply selections") - self.B3 = Tk.Button (self.frame, text = "Reset",command=self.reset) - self.B3.pack(side=Tk.LEFT, padx=2, pady=2) - self.TT3 = CreateToolTip(self.B3, "Abandon edit and reload original configuration") - self.B4 = Tk.Button (self.frame, text = "Cancel",command=self.cancel) - self.B4.pack(side=Tk.LEFT, padx=2, pady=2) - self.TT4 = CreateToolTip(self.B4, "Abandon edit and close window") - - def apply(self): - self.window.focus() - self.save_callback(False) - - def ok(self): - self.window.focus() - self.save_callback(True) - - def reset(self): - self.window.focus() - self.load_callback() - - def cancel(self): - self.cancel_callback() - ########################################################################################### diff --git a/model_railway_signals/editor/configure_route.py b/model_railway_signals/editor/configure_route.py index 94ed1a68..80860055 100644 --- a/model_railway_signals/editor/configure_route.py +++ b/model_railway_signals/editor/configure_route.py @@ -80,65 +80,67 @@ def __init__(self, root, object_id): self.main_frame = Tk.Frame(self.window) self.main_frame.pack() #------------------------------------------------------------------ - # Create a Frame to hold the Route ID and Route Colour Selections + # Create a Frame to hold the Route ID, Colour and Width Selections #------------------------------------------------------------------ self.frame = Tk.Frame(self.main_frame) - self.frame.pack(padx=2, pady=2, fill='x') + self.frame.pack(padx=2, pady=2, fill='x', expand=True) # Create the UI Element for Line ID selection - self.routeid = common.object_id_selection(self.frame, "Route ID", + self.routeid = common.object_id_selection(self.frame, "Button ID", exists_function = buttons.button_exists) self.routeid.frame.pack(side=Tk.LEFT, padx=2, pady=2, fill='y') - # Create the line colour selection element - self.colour = common.colour_selection(self.frame, label="Route Colour") - self.colour.frame.pack(padx=2, pady=2, fill='x') + # Create the line colour and button colour selection elements + self.labelframe1 = Tk.LabelFrame(self.frame, text="Button width") + self.labelframe1.pack(side=Tk.LEFT, padx=2, pady=2, fill='y' ) + self.label1 = Tk.Label(self.labelframe1, text="Chars:") + self.label1.pack(padx=2, pady=2, side=Tk.LEFT) + self.buttonwidth = common.integer_entry_box(self.labelframe1, width=3, min_value=5, max_value= 25, + tool_tip="Specify the width of the route selection button (5 to 25 characters)") + self.buttonwidth.pack(padx=2, pady=2, side=Tk.LEFT) + self.buttoncolour = common.colour_selection(self.frame, label="Button colour") + self.buttoncolour.frame.pack(side=Tk.LEFT, padx=2, pady=2, fill='x', expand=True) + self.routecolour = common.colour_selection(self.frame, label="Route highlighting") + self.routecolour.frame.pack(side=Tk.LEFT, padx=2, pady=2, fill='x', expand=True) #----------------------------------------------------------------- - # Create the route name/description/button width elements + # Create the route name and description elements #------------------------------------------------------------------ self.frame1 = Tk.LabelFrame(self.main_frame, text="Route information") self.frame1.pack(padx=2, pady=2, fill='x') self.subframe1 = Tk.Frame(self.frame1) self.subframe1.pack() - self.label1 = Tk.Label(self.subframe1, text="Name:") - self.label1.pack(padx=2, pady=2, side=Tk.LEFT) + self.label2 = Tk.Label(self.subframe1, text="Name:") + self.label2.pack(padx=2, pady=2, side=Tk.LEFT) self.name = common.entry_box(self.subframe1, width=25, tool_tip="Specify a name for the route "+ "(which will be displayed on the route selection button)") self.name.pack(padx=2, pady=2, side=Tk.LEFT) self.description = common.scrollable_text_frame(self.frame1, max_height=4, max_width=28, - min_height=4, min_width=28, editable=True, auto_resize=False) + min_height=2, min_width=28, editable=True, auto_resize=False) self.description.pack(padx=2, pady=2, fill='both', expand=True) - self.subframe2 = Tk.Frame(self.frame1) - self.subframe2.pack() - self.label2 = Tk.Label(self.subframe2, text="Route button width (chars):") - self.label2.pack(padx=2, pady=2, side=Tk.LEFT) - self.buttonwidth = common.integer_entry_box(self.subframe2, width=3, min_value=5, max_value= 25, - tool_tip="Specify the width of the route selection button (5 to 25 characters)") - self.buttonwidth.pack(padx=2, pady=2, side=Tk.LEFT) #----------------------------------------------------------------- # Create the point, signal and line entry lists #------------------------------------------------------------------ self.frame2 = Tk.LabelFrame(self.main_frame, text="Points to set") self.frame2.pack(padx=2, pady=2, fill='x') - self.points = common.entry_box_grid(self.frame2, base_class=common.point_interlocking_entry, columns=5, - exists_function = points.point_exists, tool_tip="Specify the points that need to be set and locked "+ - "for the route and their required configuration (normal/switched)") + self.points = common.entry_box_grid(self.frame2, base_class=common.point_interlocking_entry, columns=7, + tool_tip="Specify the points that need to be set and locked "+ + "for the route and their required configuration (normal/switched)") self.frame3 = Tk.LabelFrame(self.main_frame, text="Main signals to clear") self.frame3.pack(padx=2, pady=2, fill='x') - self.signals = common.entry_box_grid(self.frame3, base_class=common.int_item_id_entry_box, columns=9, + self.signals = common.entry_box_grid(self.frame3, base_class=common.int_item_id_entry_box, columns=12, width=3, exists_function = signals.signal_exists, tool_tip="Specify the main signals that need "+ "to be cleared for the route") self.frame4 = Tk.LabelFrame(self.main_frame, text="Subsidary signals to clear") self.frame4.pack(padx=2, pady=2, fill='x') - self.subsidaries = common.entry_box_grid(self.frame4, base_class=common.int_item_id_entry_box, columns=9, + self.subsidaries = common.entry_box_grid(self.frame4, base_class=common.int_item_id_entry_box, columns=12, width=3, exists_function = signals.signal_exists, tool_tip="Specify the subsidary signals "+ "(associated with a main signal) that need to be cleared for the route") self.frame5 = Tk.LabelFrame(self.main_frame, text="Route lines to highlight") self.frame5.pack(padx=2, pady=2, fill='x') - self.highlightlines = common.entry_box_grid(self.frame5, base_class=common.int_item_id_entry_box, columns=9, + self.highlightlines = common.entry_box_grid(self.frame5, base_class=common.int_item_id_entry_box, columns=12, width=3, exists_function = lines.line_exists, tool_tip="Specify the track lines comprising the route "+ "(these will be highlighted when the route is selected)") self.frame6 = Tk.LabelFrame(self.main_frame, text="Points to highlight") self.frame6.pack(padx=2, pady=2, fill='x') - self.highlightpoints = common.entry_box_grid(self.frame6, base_class=common.int_item_id_entry_box, columns=9, + self.highlightpoints = common.entry_box_grid(self.frame6, base_class=common.int_item_id_entry_box, columns=12, width=3, exists_function = points.point_exists, tool_tip="Specify the points (manual or automatic) that "+ "comprise the route (these will be highlighted when the route is selected)") #----------------------------------------------------------------- @@ -149,23 +151,31 @@ def __init__(self, root, object_id): self.subframe2 = Tk.Frame(self.frame7) self.subframe2.pack() self.label3 = Tk.Label(self.subframe2, text="Switching delay (ms):") - self.label3.pack(padx=2, pady=2, side=Tk.LEFT) + self.label3.pack(padx=2, side=Tk.LEFT) self.delay = common.integer_entry_box(self.subframe2, width=5, min_value=0, max_value= 5000, tool_tip="Specify the time delay between signal and/or point switching events when "+ "setting up and clearing down the route (0-5000ms)") - self.delay.pack(padx=2, pady=2, side=Tk.LEFT) + self.delay.pack(padx=2, side=Tk.LEFT) self.subframe3 = Tk.Frame(self.frame7) self.subframe3.pack() - self.label4 = Tk.Label(self.subframe3, text="Track Sensor for route reset:") - self.label4.pack(padx=2, pady=2, side=Tk.LEFT) - self.sensor = common.int_item_id_entry_box(self.subframe3, exists_function=track_sensors.track_sensor_exists, - tool_tip="Enter the ID of a track sensor to automatically clear down the route when the sensor is passed") - self.sensor.pack(padx=2, pady=2, side=Tk.LEFT) + self.label4 = Tk.Label(self.subframe3, text="Track Sensor to trigger route setup:") + self.label4.pack(padx=2, side=Tk.LEFT) + self.sensor1 = common.int_item_id_entry_box(self.subframe3, exists_function=track_sensors.track_sensor_exists, + tool_tip="Enter the ID of a track sensor to automatically set up the route when the sensor is passed "+ + "(if the route is 'unlocked' and selectable)") + self.sensor1.pack(padx=2, side=Tk.LEFT) self.subframe4 = Tk.Frame(self.frame7) self.subframe4.pack() - self.reset = common.check_box(self.subframe4, label="Reset points on deselection", + self.label5 = Tk.Label(self.subframe4, text="Track Sensor to trigger route reset:") + self.label5.pack(padx=2, side=Tk.LEFT) + self.sensor2 = common.int_item_id_entry_box(self.subframe4, exists_function=track_sensors.track_sensor_exists, + tool_tip="Enter the ID of a track sensor to automatically clear down the route when the sensor is passed") + self.sensor2.pack(padx=2, side=Tk.LEFT) + self.subframe5 = Tk.Frame(self.frame7) + self.subframe5.pack() + self.reset = common.check_box(self.subframe5, label="Reset points on deselection", tool_tip="Select to reset all points back to their default state when route is deselected") - self.reset.pack(padx=2, pady=2, side=Tk.LEFT) + self.reset.pack(padx=2, side=Tk.LEFT) #------------------------------------------------------------------ # Create the common Apply/OK/Reset/Cancel buttons for the window #------------------------------------------------------------------ @@ -191,7 +201,8 @@ def load_state(self): self.window.title("Route "+str(item_id)) # Set the Initial UI state from the current object settings self.routeid.set_value(item_id) - self.colour.set_value(objects.schematic_objects[self.object_id]["routecolour"]) + self.routecolour.set_value(objects.schematic_objects[self.object_id]["routecolour"]) + self.buttoncolour.set_value(objects.schematic_objects[self.object_id]["buttoncolour"]) self.name.set_value(objects.schematic_objects[self.object_id]["routename"]) self.description.set_value(objects.schematic_objects[self.object_id]["routedescription"]) self.buttonwidth.set_value(objects.schematic_objects[self.object_id]["buttonwidth"]) @@ -200,7 +211,8 @@ def load_state(self): self.highlightlines.set_values(objects.schematic_objects[self.object_id]["linestohighlight"]) self.highlightpoints.set_values(objects.schematic_objects[self.object_id]["pointstohighlight"]) self.delay.set_value(objects.schematic_objects[self.object_id]["switchdelay"]) - self.sensor.set_value(objects.schematic_objects[self.object_id]["tracksensor"]) + self.sensor1.set_value(objects.schematic_objects[self.object_id]["setupsensor"]) + self.sensor2.set_value(objects.schematic_objects[self.object_id]["tracksensor"]) self.reset.set_value(objects.schematic_objects[self.object_id]["resetpoints"]) # The "pointsonroute" element is a dict along the lines of {"1":True, "3":False}. A dict is uses # as it simplifies processing in run_layout. However, the UI element needs a list of lists along @@ -223,12 +235,13 @@ def save_state(self, close_window:bool): elif (self.routeid.validate() and self.name.validate() and self.buttonwidth.validate() and self.points.validate() and self.signals.validate() and self.subsidaries.validate() and self.highlightlines.validate() and self.highlightpoints.validate() and - self.delay.validate() and self.sensor.validate()): + self.delay.validate() and self.sensor1.validate() and self.sensor2.validate()): # Copy the original object Configuration (elements get overwritten as required) new_object_configuration = copy.deepcopy(objects.schematic_objects[self.object_id]) # Update the object coniguration elements from the current user selections new_object_configuration["itemid"] = self.routeid.get_value() - new_object_configuration["routecolour"] = self.colour.get_value() + new_object_configuration["routecolour"] = self.routecolour.get_value() + new_object_configuration["buttoncolour"] = self.buttoncolour.get_value() new_object_configuration["routename"] = self.name.get_value() new_object_configuration["routedescription"] = self.description.get_value() new_object_configuration["buttonwidth"] = self.buttonwidth.get_value() @@ -237,7 +250,8 @@ def save_state(self, close_window:bool): new_object_configuration["linestohighlight"] = self.highlightlines.get_values() new_object_configuration["pointstohighlight"] = self.highlightpoints.get_values() new_object_configuration["switchdelay"] = self.delay.get_value() - new_object_configuration["tracksensor"] = self.sensor.get_value() + new_object_configuration["setupsensor"] = self.sensor1.get_value() + new_object_configuration["tracksensor"] = self.sensor2.get_value() new_object_configuration["resetpoints"] = self.reset.get_value() # The "pointsonroute" element is a dict along the lines of {"1":True, "3":False}. A dict is uses # as it simplifies processing in run_layout. However, the UI element returns a list of lists along @@ -261,9 +275,8 @@ def save_state(self, close_window:bool): def close_window(self): # Prevent the dialog being closed if the colour chooser is still open as # for some reason this doesn't get destroyed when the parent is destroyed - if not self.colour.is_open(): + if not self.routecolour.is_open() and not self.buttoncolour.is_open(): self.window.destroy() del open_windows[self.object_id] - - + ############################################################################################# diff --git a/model_railway_signals/editor/configure_section.py b/model_railway_signals/editor/configure_section.py index e28a43f3..ff9fda0b 100644 --- a/model_railway_signals/editor/configure_section.py +++ b/model_railway_signals/editor/configure_section.py @@ -241,9 +241,12 @@ def __init__(self, parent_tab): # Create a labelframe for the General settings self.subframe1 = Tk.LabelFrame(self.frame1, text="General Settings") self.subframe1.pack(padx=2, pady=2, fill='x') - self.readonly = common.check_box(self.subframe1, label="Read only", + self.readonly = common.check_box(self.subframe1, width=12, label="Read only", tool_tip= "Select to make the Track Section non-editable") - self.readonly.pack(padx=2, pady=2) + self.readonly.pack(padx=2) + self.hidden = common.check_box(self.subframe1, width=12, label="Hidden", + tool_tip= "Select to hide the Track Section in Run Mode") + self.hidden.pack(padx=2) # Create a Label Frame to hold the "Mirror" section. Note that this needs a # reference to the parent object to access the current value of Section ID self.mirror = mirrored_section(parent_tab) @@ -344,6 +347,7 @@ def load_state(self): # Set the Initial UI state from the current object settings self.config.sectionid.set_value(item_id) self.config.readonly.set_value(not objects.schematic_objects[self.object_id]["editable"]) + self.config.hidden.set_value(objects.schematic_objects[self.object_id]["hidden"]) self.config.mirror.set_value(objects.schematic_objects[self.object_id]["mirror"], item_id) self.config.label.set_value(objects.schematic_objects[self.object_id]["defaultlabel"]) self.interlocking.signals.set_values(interlocked_signals(self.object_id)) @@ -372,6 +376,7 @@ def save_state(self, close_window:bool): # Update the section coniguration elements from the current user selections new_object_configuration["itemid"] = self.config.sectionid.get_value() new_object_configuration["editable"] = not self.config.readonly.get_value() + new_object_configuration["hidden"] = self.config.hidden.get_value() new_object_configuration["mirror"] = self.config.mirror.get_value() new_object_configuration["defaultlabel"] = self.config.label.get_value() # Save the updated configuration (and re-draw the object) diff --git a/model_railway_signals/editor/configure_signal_tab2.py b/model_railway_signals/editor/configure_signal_tab2.py index b7b1e05a..3eba6c72 100644 --- a/model_railway_signals/editor/configure_signal_tab2.py +++ b/model_railway_signals/editor/configure_signal_tab2.py @@ -5,7 +5,6 @@ # # Makes the following external API calls to library modules: # signals.signal_exists(id) - To see if the instrument exists (local or remote) -# points.point_exists(id) - To see if the point exists (local) # block_instruments.instrument_exists(id) - To see if the instrument exists (local or remote) # track_sections.section_exists(id) - To see if the track section exists # @@ -22,7 +21,6 @@ from . import common -from ..library import points from ..library import signals from ..library import block_instruments from ..library import track_sections @@ -48,7 +46,6 @@ def __init__(self, parent_frame, label:str): # These are the 'item exists' functions for validation signal_exists_function = signals.signal_exists instrument_exists_function = block_instruments.instrument_exists - point_exists_function = points.point_exists # Create a frame for this UI element (always packed into the parent frame) self.frame = Tk.Frame(parent_frame) self.frame.pack() @@ -57,12 +54,12 @@ def __init__(self, parent_frame, label:str): self.label = Tk.Label(self.frame, anchor='w', width=5, text=label) self.label.pack(side = Tk.LEFT) tool_tip = "Specify any points that need to be set and locked before the signal can be cleared for the route" - self.p1 = common.point_interlocking_entry(self.frame, point_exists_function, tool_tip) - self.p2 = common.point_interlocking_entry(self.frame, point_exists_function, tool_tip) - self.p3 = common.point_interlocking_entry(self.frame, point_exists_function, tool_tip) - self.p4 = common.point_interlocking_entry(self.frame, point_exists_function, tool_tip) - self.p5 = common.point_interlocking_entry(self.frame, point_exists_function, tool_tip) - self.p6 = common.point_interlocking_entry(self.frame, point_exists_function, tool_tip) + self.p1 = common.point_interlocking_entry(self.frame, tool_tip) + self.p2 = common.point_interlocking_entry(self.frame, tool_tip) + self.p3 = common.point_interlocking_entry(self.frame, tool_tip) + self.p4 = common.point_interlocking_entry(self.frame, tool_tip) + self.p5 = common.point_interlocking_entry(self.frame, tool_tip) + self.p6 = common.point_interlocking_entry(self.frame, tool_tip) self.p1.pack(side = Tk.LEFT) self.p2.pack(side = Tk.LEFT) self.p3.pack(side = Tk.LEFT) diff --git a/model_railway_signals/editor/configure_switch.py b/model_railway_signals/editor/configure_switch.py new file mode 100644 index 00000000..e3bf07e8 --- /dev/null +++ b/model_railway_signals/editor/configure_switch.py @@ -0,0 +1,223 @@ +#------------------------------------------------------------------------------------ +# This module contains all the ui functions for configuring DCC Switch objects +#------------------------------------------------------------------------------------ +# +# External API functions intended for use by other editor modules: +# edit_switch - Open the edit point top level window +# +# Makes the following external API calls to other editor modules: +# objects.update_object(obj_id,new_obj) - Update the configuration on save +# +# Accesses the following external editor objects directly: +# objects.schematic_objects - To load/save the object configuration +# +# Makes the following external API calls to library modules: +# buttons.button_exists(button_id) - To see if a specified (route) button ID exists +# +# Inherits the following common editor base classes (from common): +# common.object_id_selection +# common.integer_entry_box +# common.colour_selection +# common.check_box +# common.selection_buttons +# common.entry_box +# common.scrollable_text_frame +# common.row_of_validated_dcc_commands +# common.window_controls +# +#------------------------------------------------------------------------------------ + +import copy + +import tkinter as Tk + +from . import common +from . import objects + +from ..library import buttons + +#------------------------------------------------------------------------------------ +# We maintain a global dictionary of open edit windows (where the key is the UUID +# of the object being edited) to prevent duplicate windows being opened. If the user +# tries to edit an object which is already being edited, then we just bring the +# existing edit window to the front (expanding if necessary) and set focus on it +#------------------------------------------------------------------------------------ + +open_windows={} + +##################################################################################### +# Top level Class for the Edit DCC Switch window +# This window doesn't have any tabs (unlike other object configuration windows) +##################################################################################### + +class edit_switch(): + def __init__(self, root, object_id): + global open_windows + # If there is already a window open then we just make it jump to the top and exit + if object_id in open_windows.keys(): + open_windows[object_id].lift() + open_windows[object_id].state('normal') + open_windows[object_id].focus_force() + else: + # This is the UUID for the object being edited + self.object_id = object_id + # Create the (non-resizable) top level window + self.window = Tk.Toplevel(root) + self.window.protocol("WM_DELETE_WINDOW", self.close_window) + self.window.resizable(False, False) + open_windows[object_id] = self.window + # Create a frame to hold all UI elements (so they don't expand on window resize + # to provide consistent behavior with the other configure object popup windows) + self.main_frame = Tk.Frame(self.window) + self.main_frame.pack() + #---------------------------------------------------------------------------------- + # Create a Frame to hold the ID, Colour, Width and general settings (frame1) + #---------------------------------------------------------------------------------- + self.frame1 = Tk.Frame(self.main_frame) + self.frame1.pack(padx=2, pady=2, fill='x', expand=True) + # Create the UI Element for Button ID selection + self.buttonid = common.object_id_selection(self.frame1, "Button ID", + exists_function = buttons.button_exists) + self.buttonid.frame.pack(side=Tk.LEFT, padx=2, pady=2, fill='y') + # Create the button width and colour selection elements in a second label frame + self.frame1subframe2 = Tk.LabelFrame(self.frame1, text="Button width") + self.frame1subframe2.pack(side=Tk.LEFT, padx=2, pady=2, fill='y' ) + self.frame1subframe2label1 = Tk.Label(self.frame1subframe2, text="Chars:") + self.frame1subframe2label1.pack(padx=2, pady=2, side=Tk.LEFT) + self.buttonwidth = common.integer_entry_box(self.frame1subframe2, width=3, min_value=5, + max_value= 25, tool_tip="Specify the width of the button (5 to 25 characters)") + self.buttonwidth.pack(padx=2, pady=2, side=Tk.LEFT) + self.buttoncolour = common.colour_selection(self.frame1, label="Button colour") + self.buttoncolour.frame.pack(side=Tk.LEFT, padx=2, pady=2, fill='x', expand=True) + # Create the general settings in a third label frame + self.frame1subframe4 = Tk.LabelFrame(self.frame1, text="General Settings") + self.frame1subframe4.pack(padx=2, pady=2, fill='both', expand=True) + self.buttonhidden = common.check_box(self.frame1subframe4, label="Hidden", + tool_tip= "Select to hide the Button in Run Mode") + self.buttonhidden.pack(padx=2, pady=2) + #---------------------------------------------------------------------------------- + # Create a Label Frame for the switch type UI element (frame2) + #---------------------------------------------------------------------------------- + self.switchtype = common.selection_buttons(self.main_frame, "DCC accessory switch type", + "Select DCC Accessory switch type", self.switch_type_updated, "On/off switch", "Momentary switch") + self.switchtype.frame.pack(padx=2, pady=2, fill='both', expand=True) + #---------------------------------------------------------------------------------- + # Create a Label Frame for the Button name and description elements (frame3) + #---------------------------------------------------------------------------------- + self.frame3 = Tk.LabelFrame(self.main_frame, text="Button information") + self.frame3.pack(padx=2, pady=2, fill='x') + # The Label and the entry box for the button name are packed in their own frame + self.frame3subframe1 = Tk.Frame(self.frame3) + self.frame3subframe1.pack() + self.frame3subframe1label1 = Tk.Label(self.frame3subframe1, text="Name:") + self.frame3subframe1label1.pack(padx=2, pady=2, side=Tk.LEFT) + self.buttonname = common.entry_box(self.frame3subframe1, width=25, tool_tip="Specify a name for the button "+ + "(which will be displayed on the DCC accessory button)") + self.buttonname.pack(padx=2, pady=2, side=Tk.LEFT) + # Button description is packed below the Button Name elements + self.description = common.scrollable_text_frame(self.frame3, max_height=4, max_width=28, + min_height=2, min_width=28, editable=True, auto_resize=False) + self.description.pack(padx=2, pady=2, fill='both', expand=True) + #---------------------------------------------------------------------------------- + # Create a Frame for the DCC command sequences (frame4) + #---------------------------------------------------------------------------------- + self.frame4 = Tk.LabelFrame(self.main_frame, text="DCC") + self.frame4.pack(padx=2, pady=2, fill='x') + # Create a subframe for the ON labels and DCC command sequence + self.frame4subframe1 = Tk.Frame(self.frame4) + self.frame4subframe1.pack() + self.frame4subframe1label1 = Tk.Label(self.frame4subframe1, width=5, text= "ON") + self.frame4subframe1label1.pack(side=Tk.LEFT, padx=2, pady=2) + self.oncommands = common.row_of_validated_dcc_commands(self.frame4subframe1, columns=4, item_type="Switch") + self.oncommands.pack(side=Tk.LEFT, padx=2, pady=2) + # Create a subframe for the OFF labels and DCC command sequence + self.frame4subframe2 = Tk.Frame(self.frame4) + self.frame4subframe2.pack() + self.frame4subframe2label1 = Tk.Label(self.frame4subframe2, width=5, text= "OFF") + self.frame4subframe2label1.pack(side=Tk.LEFT, padx=2, pady=2) + self.offcommands = common.row_of_validated_dcc_commands(self.frame4subframe2, columns=4, item_type="Switch") + self.offcommands.pack(side=Tk.LEFT, padx=2, pady=2) + #------------------------------------------------------------------ + # Create the common Apply/OK/Reset/Cancel buttons for the window + #------------------------------------------------------------------ + self.controls = common.window_controls(self.window, self.load_state, self.save_state, self.close_window) + self.controls.frame.pack(padx=2, pady=2) + # Create the Validation error message (this gets packed/unpacked on apply/save) + self.validation_error = Tk.Label(self.window, text="Errors on Form need correcting", fg="red") + # load the initial UI state + self.load_state() + + def switch_type_updated(self): + if self.switchtype.get_value() == buttons.button_type.momentary.value: + self.offcommands.disable() + else: + self.offcommands.enable() + +#------------------------------------------------------------------------------------ +# Functions for load, save and close window +#------------------------------------------------------------------------------------ + + def load_state(self): + # Check the line we are editing still exists (hasn't been deleted from the schematic) + # If it no longer exists then we just destroy the window and exit without saving + if self.object_id not in objects.schematic_objects.keys(): + self.close_window() + else: + item_id = objects.schematic_objects[self.object_id]["itemid"] + # Label the edit window + self.window.title("Button "+str(item_id)) + # Set the Initial UI state from the current object settings + self.buttonid.set_value(item_id) + self.buttonname.set_value(objects.schematic_objects[self.object_id]["switchname"]) + self.description.set_value(objects.schematic_objects[self.object_id]["switchdescription"]) + self.switchtype.set_value(objects.schematic_objects[self.object_id]["itemtype"]) + self.buttoncolour.set_value(objects.schematic_objects[self.object_id]["buttoncolour"]) + self.buttonwidth.set_value(objects.schematic_objects[self.object_id]["buttonwidth"]) + self.buttonhidden.set_value(objects.schematic_objects[self.object_id]["hidden"]) + self.oncommands.set_values(objects.schematic_objects[self.object_id]["dcconcommands"], item_id=item_id) + self.offcommands.set_values(objects.schematic_objects[self.object_id]["dccoffcommands"], item_id=item_id) + # Enable/disable the 'off' UI elements depending on switch type + self.switch_type_updated() + # Hide the validation error message + self.validation_error.pack_forget() + return() + + def save_state(self, close_window:bool): + # Check the object we are editing still exists (hasn't been deleted from the schematic) + # If it no longer exists then we just destroy the window and exit without saving + if self.object_id not in objects.schematic_objects.keys(): + self.close_window() + # Validate all user entries prior to applying the changes. Each of these would have + # been validated on entry, but changes to other objects may have been made since then + elif (self.buttonid.validate() and self.buttonname.validate() and self.buttonwidth.validate() and + self.oncommands.validate() and self.offcommands.validate()): + # Copy the original object Configuration (elements get overwritten as required) + new_object_configuration = copy.deepcopy(objects.schematic_objects[self.object_id]) + # Update the object coniguration elements from the current user selections + new_object_configuration["itemid"] = self.buttonid.get_value() + new_object_configuration["switchname"] = self.buttonname.get_value() + new_object_configuration["switchdescription"] = self.description.get_value() + new_object_configuration["itemtype"] = self.switchtype.get_value() + new_object_configuration["buttoncolour"] = self.buttoncolour.get_value() + new_object_configuration["buttonwidth"] = self.buttonwidth.get_value() + new_object_configuration["hidden"] = self.buttonhidden.get_value() + new_object_configuration["dcconcommands"] = self.oncommands.get_values() + new_object_configuration["dccoffcommands"] = self.offcommands.get_values() + # Save the updated configuration (and re-draw the object) + objects.update_object(self.object_id, new_object_configuration) + # Close window on "OK" or re-load UI for "apply" + if close_window: self.close_window() + else: self.load_state() + else: + # Display the validation error message + self.validation_error.pack(side=Tk.BOTTOM, before=self.controls.frame) + return() + + def close_window(self): + # Prevent the dialog being closed if the colour chooser is still open as + # for some reason this doesn't get destroyed when the parent is destroyed + if not self.buttoncolour.is_open(): + self.window.destroy() + del open_windows[self.object_id] + +############################################################################################# diff --git a/model_railway_signals/editor/configure_textbox.py b/model_railway_signals/editor/configure_textbox.py index 4159ed8c..79f208ae 100644 --- a/model_railway_signals/editor/configure_textbox.py +++ b/model_railway_signals/editor/configure_textbox.py @@ -126,12 +126,12 @@ def __init__(self, root, object_id): self.frame2.pack(padx=2, pady=2, fill='x') # Create the text colour and text background colour selection elements self.colour = common.colour_selection(self.frame2, label="Text colour") - self.colour.frame.pack(padx=2, pady=2, fill='x', side=Tk.LEFT, expand=1) - self.background = common.colour_selection(self.frame2, label="Background colour") + self.colour.frame.pack(padx=2, pady=2, fill='both', side=Tk.LEFT, expand=1) + self.background = common.colour_selection(self.frame2, label="Background colour", transparent_option=True) self.background.frame.pack(padx=2, pady=2, fill='x', side=Tk.LEFT, expand=1) # Create a Frame for the Text Justification self.frame3 = Tk.Frame(self.main_frame) - self.frame3.pack(padx=2, pady=2, fill='x') + self.frame3.pack(fill='x') # Use radio buttons for the text justification selection self.justify = common.selection_buttons(self.frame3, label="Text Justification", tool_tip= "select text justification", b1="Left", b2="Centre", b3="Right", @@ -139,7 +139,13 @@ def __init__(self, root, object_id): self.justify.frame.pack(padx=2, pady=2, fill='x') # Create a Frame for the Text Style Entry widgey self.textstyle = text_style_entry (self.main_frame, callback = self.text_style_updated) - self.textstyle.frame.pack(padx=2, pady=2, fill='x') + self.textstyle.frame.pack(padx=2, pady=2, fill='x') + # Create a Frame for the other general settings + self.subframe1 = Tk.LabelFrame(self.window, text="General Settings") + self.subframe1.pack(padx=2, pady=2, fill='x') + self.hidden = common.check_box(self.subframe1, label="Hidden", + tool_tip= "Select to hide the Text Box in Run Mode") + self.hidden.pack(padx=2, pady=2) # load the initial UI state self.load_state() @@ -162,6 +168,7 @@ def load_state(self): self.text.set_value(objects.schematic_objects[self.object_id]["text"]) self.colour.set_value(objects.schematic_objects[self.object_id]["colour"]) self.background.set_value(objects.schematic_objects[self.object_id]["background"]) + self.hidden.set_value(objects.schematic_objects[self.object_id]["hidden"]) justify = objects.schematic_objects[self.object_id]["justify"] font = objects.schematic_objects[self.object_id]["font"] font_size = objects.schematic_objects[self.object_id]["fontsize"] @@ -188,6 +195,7 @@ def save_state(self, close_window:bool): new_object_configuration["text"] = self.text.get_value() new_object_configuration["colour"] = self.colour.get_value() new_object_configuration["background"] = self.background.get_value() + new_object_configuration["hidden"] = self.hidden.get_value() new_object_configuration["justify"] = self.justify.get_value() font, font_size, font_style, border = self.textstyle.get_values() new_object_configuration["font"] = font diff --git a/model_railway_signals/editor/configure_track_sensor.py b/model_railway_signals/editor/configure_track_sensor.py index 703c6e34..c250dc82 100644 --- a/model_railway_signals/editor/configure_track_sensor.py +++ b/model_railway_signals/editor/configure_track_sensor.py @@ -16,7 +16,6 @@ # gpio_sensors.get_gpio_sensor_callback - To see if a GPIO sensor is already mapped # track_sensors.track_sensor_exists(id) - To see if the track sensor exists # track_sections.section_exists(id) - To see if the track section exists (local or remote) -# points.point_exists(id) - To see if the point exists # # Inherits the following common editor base classes (from common): # common.str_int_item_id_entry_box @@ -33,7 +32,6 @@ from . import common from . import objects -from ..library import points from ..library import gpio_sensors from ..library import track_sensors from ..library import track_sections @@ -69,7 +67,7 @@ def __init__(self, parent_frame): # Create a subframe to centre the UI elements self.subframe=Tk.Frame(self.frame) self.subframe.pack() - self.label = Tk.Label(self.subframe, text=" Track Sensor 'passed' sensor:") + self.label = Tk.Label(self.subframe, text=" Sensor 'passed' sensor:") self.label.pack(side=Tk.LEFT, padx=2, pady=2) # The 'exists' function will return true if the GPIO sensor exists exists_function = gpio_sensors.gpio_sensor_exists @@ -124,12 +122,12 @@ def __init__(self, parent_frame, label:str): self.label = Tk.Label(self.frame, anchor='w', width=5, text=label) self.label.pack(side = Tk.LEFT) tool_tip = "Specify the points that need to be configured for the route" - self.p1 = common.point_interlocking_entry(self.frame, points.point_exists, tool_tip) - self.p2 = common.point_interlocking_entry(self.frame, points.point_exists, tool_tip) - self.p3 = common.point_interlocking_entry(self.frame, points.point_exists, tool_tip) - self.p4 = common.point_interlocking_entry(self.frame, points.point_exists, tool_tip) - self.p5 = common.point_interlocking_entry(self.frame, points.point_exists, tool_tip) - self.p6 = common.point_interlocking_entry(self.frame, points.point_exists, tool_tip) + self.p1 = common.point_interlocking_entry(self.frame, tool_tip) + self.p2 = common.point_interlocking_entry(self.frame, tool_tip) + self.p3 = common.point_interlocking_entry(self.frame, tool_tip) + self.p4 = common.point_interlocking_entry(self.frame, tool_tip) + self.p5 = common.point_interlocking_entry(self.frame, tool_tip) + self.p6 = common.point_interlocking_entry(self.frame, tool_tip) self.p1.pack(side = Tk.LEFT) self.p2.pack(side = Tk.LEFT) self.p3.pack(side = Tk.LEFT) @@ -256,7 +254,13 @@ def __init__(self, root, object_id): self.sensorid.frame.pack(side=Tk.LEFT, padx=2, pady=2, fill='y') # Create the UI Element for the GPIO Sensor selection. self.gpiosensor = gpio_sensor_selection(self.frame) - self.gpiosensor.frame.pack(padx=2, pady=2, fill='x') + self.gpiosensor.frame.pack(side=Tk.LEFT, padx=2, pady=2, fill='x') + # Create the UI Element for the general settings + self.subframe1 = Tk.LabelFrame(self.frame, text="General Settings") + self.subframe1.pack(padx=2, pady=2, fill='x') + self.hidden = common.check_box(self.subframe1, label="Hidden", + tool_tip= "Select to hide the Track Sensor in Run Mode") + self.hidden.pack(padx=2, pady=2) # Create the UI Elements for the track sensor route elements self.behind = track_sensor_route_frame(self.window,label="Routes / Track Sections 'behind' Track Sensor") self.behind.frame.pack(padx=2, pady=2, fill='x') @@ -288,6 +292,7 @@ def load_state(self): self.gpiosensor.set_value(objects.schematic_objects[self.object_id]["passedsensor"], item_id) self.ahead.set_routes(objects.schematic_objects[self.object_id]["routeahead"]) self.behind.set_routes(objects.schematic_objects[self.object_id]["routebehind"]) + self.hidden.set_value(objects.schematic_objects[self.object_id]["hidden"]) # Hide the validation error message self.validation_error.pack_forget() return() @@ -308,6 +313,7 @@ def save_state(self, close_window:bool): new_object_configuration["passedsensor"] = self.gpiosensor.get_value() new_object_configuration["routeahead"] = self.ahead.get_routes() new_object_configuration["routebehind"] = self.behind.get_routes() + new_object_configuration["hidden"] = self.hidden.get_value() # Save the updated configuration (and re-draw the object) objects.update_object(self.object_id, new_object_configuration) # Close window on "OK" or re-load UI for "apply" diff --git a/model_railway_signals/editor/editor.py b/model_railway_signals/editor/editor.py index 01742658..f828b537 100644 --- a/model_railway_signals/editor/editor.py +++ b/model_railway_signals/editor/editor.py @@ -230,9 +230,6 @@ def __init__(self, root): self.logging_update() # Initialise the editor configuration at startup (using the default settings) self.initialise_editor() - # If a filename has been specified as a command line argument then load it. The loaded - # settings will overwrite the default settings and initialise_editor will be called again - if args.filename is not None: self.load_schematic(args.filename) # The following code is to help with advanced debugging (start the app with the -d flag) if args.debug_mode: self.debug_menu = Tk.Menu(self.mainmenubar,tearoff=False) @@ -242,7 +239,15 @@ def __init__(self, root): self.mainmenubar.add_cascade(label = "Debug ", menu=self.debug_menu) tracemalloc.start() self.monitor_memory_usage = False - + # If a filename has been specified as a command line argument then load it. The loaded + # settings will overwrite the default settings and initialise_editor will be called again + # Note we schedule this to run immediately after the main loop starts so Tkinter is + # 'ready' to handle any events that may be passed in from other threads when we configure + # the application with the newly loaded settings (GPIO or MQTT events) + if args.filename is not None: + self.root.after(0, self.load_schematic, args.filename) + print(args.filename) + # -------------------------------------------------------------------------------------- # Advanced debugging functions (memory allocation monitoring/reporting) # Full acknowledgements to stack overflow for the reporting functions used here @@ -584,6 +589,7 @@ def gpio_update(self): def general_settings_update(self): # The spad popups enabled flag is the 6th parameter returned run_layout.configure_spad_popups(settings.get_general()[5]) + library_common.configure_button_size(settings.get_general()[6]) #------------------------------------------------------------------------------------------ # FILE menubar functions @@ -665,7 +671,7 @@ def load_schematic(self, filename=None): Tk.messagebox.showerror(parent=self.root, title="Load Error", message="File was saved by "+sig_file_version+". Upgrade application to "+ sig_file_version+" or later to support this layout file.") - elif self.tuple_version(sig_file_version) < self.tuple_version("3.5.0"): + elif self.tuple_version(sig_file_version) < self.tuple_version("4.0.0"): # We only provide backward compatibility for a few versions - before that, fail fast logging.error("Load File - File was saved by application "+sig_file_version) logging.error("Load File - Current version of the application is "+application_version) diff --git a/model_railway_signals/editor/menubar_windows.py b/model_railway_signals/editor/menubar_windows.py index 24b3963b..4bbda07a 100644 --- a/model_railway_signals/editor/menubar_windows.py +++ b/model_railway_signals/editor/menubar_windows.py @@ -4,7 +4,8 @@ # Classes (pop up windows) called from the main editor module menubar selections # display_help(root) # display_about(root) -# edit_layout_info() +# edit_layout_info(root) +# edit_general_settings(root, general_settings_update_callback) # edit_mqtt_settings(root, mqtt_connect_callback, mqtt_update_callback) # edit_sprog_settings(root, sprog_connect_callback, sprog_update_callback) # edit_logging_settings(root, logging_update_callback) @@ -16,8 +17,8 @@ # settings.set_canvas() - Save the new canvas settings (as specified) # settings.get_logging() - Get the current log level (for editing) # settings.set_logging(level) - Save the new log level (as specified) -# settings.get_general() - Get the current settings (layout info, version for info/editing) -# settings.set_general() - Save the new settings (only layout info can be edited/saved) +# settings.get_general() - Get the current settings (for editing) +# settings.set_general() - Save the new settings (as specified) # settings.get_sprog() - Get the current SPROG settings (for editing) # settings.set_sprog() - Save the new SPROG settings (as specified) # settings.get_mqtt() - Get the current MQTT settings (for editing) @@ -1083,26 +1084,52 @@ def __init__(self, root_window, update_function): self.window.protocol("WM_DELETE_WINDOW", self.close_window) self.window.resizable(False, False) edit_general_settings_window = self.window + # Create a labelframe for the run Layout Settings + self.labelframe1 = Tk.LabelFrame(self.window, text = "Run Layout settings") + self.labelframe1.pack(padx=2, pady=2, fill=Tk.BOTH) # Create the "SPAD Popups" selection element - self.spad = common.check_box(self.window, label="Enable Signal Passed at Danger popup warnings", + self.spad = common.check_box(self.labelframe1, label="Enable popup SPAD warnings", tool_tip="Select to Enable popup Signal Passed at Danger (SPAD) and other track occupancy warnings") self.spad.pack(padx=2, pady=2) + # Create a labelframe for the run Layout Settings + self.labelframe2 = Tk.LabelFrame(self.window, text = "Appearance Settings") + self.labelframe2.pack(padx=2, pady=2, fill=Tk.BOTH) + # Create the "Button Fontsize" selection element + self.subframe = Tk.Frame(self.labelframe2) + self.subframe.pack(padx=2, pady=2) + self.label1 = Tk.Label(self.subframe, text="Control button fontsize") + self.label1.pack(side=Tk.LEFT, padx=2, pady=2) + self.fontsize = common.integer_entry_box(self.subframe, min_value=8, max_value=12, width=3, allow_empty=False, + tool_tip="Specify the font-size to use for the point and signal control buttons on the schematic "+ + "(8-12). Note that the new fontsize will only be applied on layout save/load or layout reset") + self.fontsize.pack(side=Tk.LEFT, padx=2, pady=2) # Create the common Apply/OK/Reset/Cancel buttons for the window self.controls = common.window_controls(self.window, self.load_state, self.save_state, self.close_window) self.controls.frame.pack(padx=2, pady=2) + # Create the Validation error message (this gets packed/unpacked on apply/save) + self.validation_error = Tk.Label(self.window, text="Errors on Form need correcting", fg="red") # Load the initial UI state self.load_state() def load_state(self): + self.validation_error.pack_forget() # Spad Popups flag is the 6th parameter returned from get_general + # fontsize is the 7th parameter returned from get_general self.spad.set_value(settings.get_general()[5]) + self.fontsize.set_value(settings.get_general()[6]) def save_state(self, close_window:bool): - settings.set_general(spad=self.spad.get_value()) - # Make the callback to apply the updated settings - self.update_function() - # close the window (on OK ) - if close_window: self.close_window() + if self.fontsize.validate(): + self.validation_error.pack_forget() + settings.set_general(spad=self.spad.get_value()) + settings.set_general(buttonsize=self.fontsize.get_value()) + # Make the callback to apply the updated settings + self.update_function() + # close the window (on OK ) + if close_window: self.close_window() + else: + # Display the validation error message + self.validation_error.pack(side=Tk.BOTTOM, before=self.controls.frame) def close_window(self): global edit_general_settings_window diff --git a/model_railway_signals/editor/objects/__init__.py b/model_railway_signals/editor/objects/__init__.py index 5af3b3bb..e55d7f92 100644 --- a/model_railway_signals/editor/objects/__init__.py +++ b/model_railway_signals/editor/objects/__init__.py @@ -13,6 +13,7 @@ # track_sensor(item_id:int) - helper function to find the object Id by Item ID # line(item_id:int) - helper function to find the object Id by Item ID # route(item_id:int) - helper function to find the object Id by Item ID +# switch(item_id:int) - helper function to find the object Id by Item ID # # create_gpio_sensors(trigger,timeout,mappings) - Configure the local GPIO sensor mappings # configure_local_gpio_sensor_event_mappings() - configure local GPIO event mappings (after MQTT config update) @@ -42,6 +43,7 @@ # line_index - for iterating through all the line objects # track_sensor_index - for iterating through all the track_sensor objects # route_index - for iterating through all the route objects +# switch_index - for iterating through all the switch objects # # Makes the following external API calls to other editor modules: # run_layout.initialise_layout() - Re-initiallise the state of schematic objects following a change @@ -72,6 +74,7 @@ from .objects_common import line from .objects_common import track_sensor from .objects_common import route +from .objects_common import switch from .objects_common import object_type from .objects_common import schematic_objects @@ -82,6 +85,7 @@ from .objects_common import line_index from .objects_common import track_sensor_index from .objects_common import route_index +from .objects_common import switch_index from .objects_gpio import create_gpio_sensors from .objects_gpio import configure_remote_gpio_sensor_event_mappings @@ -116,6 +120,7 @@ 'line', 'track_sensor', 'route', + 'switch', # Main schematic object dict and the type-specific indexes 'schematic_objects', 'signal_index', @@ -125,6 +130,7 @@ 'line_index', 'track_sensor_index', 'route_index', + 'switch_index', # GPIO Event configuration functions 'create_gpio_sensors', 'configure_remote_gpio_sensor_event_mappings', diff --git a/model_railway_signals/editor/objects/objects.py b/model_railway_signals/editor/objects/objects.py index a46993ac..b5f8709d 100644 --- a/model_railway_signals/editor/objects/objects.py +++ b/model_railway_signals/editor/objects/objects.py @@ -75,6 +75,13 @@ # objects_routes.delete_route_object(object_id) - soft delete the drawing object (prior to recreating) # objects_routes.redraw_route_object(object_id) - Redraw the object on the canvas following an update # objects_routes.default_route_object - The dictionary of default values for the object +# objects_switches.create_switch() - Create a default object on the schematic +# objects_switches.delete_switch(object_id) - Hard Delete an object when deleted from the schematic +# objects_switches.update_switch(obj_id,new_obj) - Update the configuration of an existing object +# objects_switches.paste_switch(object) - Paste a copy of an object to create a new one (returns new object_id) +# objects_switches.delete_switch_object(object_id) - soft delete the drawing object (prior to recreating) +# objects_switches.redraw_switch_object(object_id) - Redraw the object on the canvas following an update +# objects_switches.default_switch_object - The dictionary of default values for the object #------------------------------------------------------------------------------------ @@ -90,26 +97,20 @@ from . import objects_sensors from . import objects_points from . import objects_routes +from . import objects_switches from .. import run_layout -####################################################################################################### -### Handle change of sensors being a configuration item in their own right from release 3.6.0 ######### -####################################################################################################### -from .. import settings -####################################################################################################### -################################## End of code to handle breaking changes ############################# -####################################################################################################### - #------------------------------------------------------------------------------------ -# Internal function to bring all track sections and route buttons to the front -# his insures they are not obscured by any lines drawn on the canvas +# Internal function to bring all track sections, route buttons and switches to the +# front. This ensures they are not obscured by any lines drawn on the canvas #------------------------------------------------------------------------------------ def bring_track_sections_to_the_front(): for object_id in objects_common.schematic_objects: if ( objects_common.schematic_objects[object_id]["item"] == objects_common.object_type.section or objects_common.schematic_objects[object_id]["item"] == objects_common.object_type.route or + objects_common.schematic_objects[object_id]["item"] == objects_common.object_type.switch or objects_common.schematic_objects[object_id]["item"] == objects_common.object_type.textbox): objects_common.canvas.tag_raise(objects_common.schematic_objects[object_id]["tags"]) return() @@ -140,6 +141,8 @@ def redraw_all_objects(create_new_bbox:bool, reset_state:bool): objects_sensors.redraw_track_sensor_object(object_id) elif this_object_type == objects_common.object_type.route: objects_routes.redraw_route_object(object_id) + elif this_object_type == objects_common.object_type.switch: + objects_switches.redraw_switch_object(object_id) # Ensure all track sections are brought forward on the schematic (in front of any lines) bring_track_sections_to_the_front() return() @@ -169,6 +172,8 @@ def reset_all_schematic_indexes(): objects_common.track_sensor_index[str(this_object_item_id)] = object_id elif this_object_type == objects_common.object_type.route: objects_common.route_index[str(this_object_item_id)] = object_id + elif this_object_type == objects_common.object_type.switch: + objects_common.switch_index[str(this_object_item_id)] = object_id # Note that textboxes don't have an index as we don't track their IDs return() @@ -263,6 +268,8 @@ def reset_objects(): objects_sensors.delete_track_sensor_object(object_id) elif type_of_object == objects_common.object_type.route: objects_routes.delete_route_object(object_id) + elif type_of_object == objects_common.object_type.switch: + objects_switches.delete_switch_object(object_id) # Redraw all point, section, instrument and signal objects in their default state # We don't need to create a new bbox as soft_delete keeps the tkinter object redraw_all_objects(create_new_bbox=False, reset_state=True) @@ -295,6 +302,8 @@ def create_object(new_object_type, item_type=None, item_subtype=None): object_id = objects_sensors.create_track_sensor() elif new_object_type == objects_common.object_type.route: object_id = objects_routes.create_route() + elif new_object_type == objects_common.object_type.switch: + object_id = objects_switches.create_switch() else: object_id = None # save the current state (for undo/redo) @@ -325,6 +334,8 @@ def update_object(object_id, new_object): objects_sensors.update_track_sensor(object_id, new_object) elif type_of_object == objects_common.object_type.route: objects_routes.update_route(object_id, new_object) + elif type_of_object == objects_common.object_type.switch: + objects_switches.update_switch(object_id, new_object) # Ensure all track sections are brought forward on the schematic (in front of any lines) bring_track_sections_to_the_front() # save the current state (for undo/redo) @@ -357,6 +368,8 @@ def delete_object(object_id): objects_sensors.delete_track_sensor(object_id) elif type_of_object == objects_common.object_type.route: objects_routes.delete_route(object_id) + elif type_of_object == objects_common.object_type.switch: + objects_switches.delete_switch(object_id) return() #------------------------------------------------------------------------------------ @@ -485,6 +498,8 @@ def paste_objects(): new_object_id = objects_sensors.paste_track_sensor(object_to_paste, deltax, deltay) elif type_of_object == objects_common.object_type.route: new_object_id = objects_routes.paste_route(object_to_paste, deltax, deltay) + elif type_of_object == objects_common.object_type.switch: + new_object_id = objects_switches.paste_switch(object_to_paste, deltax, deltay) # Add the new object to the list of clipboard objects # in case the user wants to paste the same objects again list_of_new_object_ids.append(new_object_id) @@ -505,7 +520,7 @@ def set_all(new_objects): ################################################################################## ### Code block to Handle breaking changes - see later in the code for details #### ################################################################################## - list_of_track_sensors_to_create =[] + one_up_text_box_id = 1 ################################################################################## ################ End of code block to handle breaking changes #################### ################################################################################## @@ -535,6 +550,8 @@ def set_all(new_objects): default_object = objects_sensors.default_track_sensor_object elif new_object_type == objects_common.object_type.route: default_object = objects_routes.default_route_object + elif new_object_type == objects_common.object_type.switch: + default_object = objects_switches.default_switch_object else: default_object = {} logging.debug("LOAD LAYOUT - "+new_object_type+" "+str(item_id)+ @@ -546,6 +563,18 @@ def set_all(new_objects): if element not in default_object.keys(): logging.debug("LOAD LAYOUT - "+new_object_type+" "+str(item_id)+ " - Unexpected element: '"+element+"' - DISCARDED") + ################################################################################################# + ## Handle breaking change of text boxes being library objects from Release 4.7 onwards ########## + ## and hence requiring a unique item ID (even if this is 'under the hood') ###################### + ################################################################################################# + elif (new_object_type == objects_common.object_type.textbox and + element == "itemid" and new_objects[object_id][element] == 0): + objects_common.schematic_objects[object_id][element] = one_up_text_box_id + one_up_text_box_id = one_up_text_box_id + 1 + ################################################################################################# + ## End of Handle breaking change for Text Box IDs ############################################### + ################################################################################################# + ################################################################################################# ## Handle breaking change of tracks sections now a list of 3 sections from release 4.0.0 ######### ## The 'tracksections' element is a list of [section_behind, sections_ahead] #################### @@ -586,67 +615,9 @@ def set_all(new_objects): ## End of Handle non-breaking change for Signal opposing signals interlocking table ############# ################################################################################################# - ################################################################################################# - ### Handle change of GPIO sensor IDs being strings from Release 3.6.0 onwards ################### - ### This is something we can resolve without affecting the user so we resolve ################### - ### it silently without an Log message or load warning message ################################## - ################################################################################################# - elif new_object_type == objects_common.object_type.signal and element == "passedsensor": - objects_common.schematic_objects[object_id][element][0] = new_objects[object_id][element][0] - if new_objects[object_id][element][1] == 0: - objects_common.schematic_objects[object_id][element][1] = "" - logging.debug("LOAD LAYOUT - "+new_object_type+" "+str(item_id)+ - " - Handling version 3.6.0 breaking change to : '"+element+"'") - elif isinstance(new_objects[object_id][element][1],int): - objects_common.schematic_objects[object_id][element][1] = str(new_objects[object_id][element][1]) - list_of_track_sensors_to_create.append([new_objects[object_id][element][1],new_objects[object_id][element][1]]) - logging.debug("LOAD LAYOUT - "+new_object_type+" "+str(item_id)+ - " - Handling version 3.6.0 breaking change to : '"+element+"'") - else: - objects_common.schematic_objects[object_id][element] = new_objects[object_id][element] - elif new_object_type == objects_common.object_type.signal and element == "approachsensor": - objects_common.schematic_objects[object_id][element][0] = new_objects[object_id][element][0] - if new_objects[object_id][element][1] == 0: - objects_common.schematic_objects[object_id][element][1] = "" - logging.debug("LOAD LAYOUT - "+new_object_type+" "+str(item_id)+ - " - Handling version 3.6.0 breaking change to : '"+element+"'") - elif isinstance(new_objects[object_id][element][1],int): - objects_common.schematic_objects[object_id][element][1] = str(new_objects[object_id][element][1]) - list_of_track_sensors_to_create.append([new_objects[object_id][element][1],new_objects[object_id][element][1]]) - logging.debug("LOAD LAYOUT - "+new_object_type+" "+str(item_id)+ - " - Handling version 3.6.0 breaking change to : '"+element+"'") - else: - objects_common.schematic_objects[object_id][element] = new_objects[object_id][element] - ################################################################################################# - ## End of Handle breaking change for GPIO sensor IDs ############################################ - ################################################################################################# - - ################################################################################################# - ## Handle bugfix for Signal point interlocking tables (i.e. list wrongly included 7 points ###### - ## on point deletion whereas the list should only ever include 6 points ######################### - ################################################################################################# - elif new_object_type == objects_common.object_type.signal and element == "pointinterlock": - for index, route in enumerate (new_objects[object_id][element]): - objects_common.schematic_objects[object_id][element][index][0] = route[0][0:6] - objects_common.schematic_objects[object_id][element][index][1] = route[1] - objects_common.schematic_objects[object_id][element][index][2] = route[2] - ################################################################################################# - ## End of Handle bugfix for Signal point interlocking tables #################################### - ################################################################################################# - else: objects_common.schematic_objects[object_id][element] = new_objects[object_id][element] - ################################################################################################# - ### Handle change of sensor IDs being strings from Release 3.6.0 onwards ######################## - ################################################################################################# - if len(list_of_track_sensors_to_create) > 0: - logging.debug("LOAD LAYOUT - Populating track sensor mappings to handle version 3.6.0 breaking change") - settings.set_gpio(mappings = list_of_track_sensors_to_create) - ################################################################################################# - ## End of Handle breaking change for sensor IDs ################################################# - ################################################################################################# - # Now report any elements missing from the new object - intended to provide a # level of backward capability (able to load old config files into an extended config) for element in default_object: diff --git a/model_railway_signals/editor/objects/objects_common.py b/model_railway_signals/editor/objects/objects_common.py index 34d7cd8c..d4c8e814 100644 --- a/model_railway_signals/editor/objects/objects_common.py +++ b/model_railway_signals/editor/objects/objects_common.py @@ -9,6 +9,8 @@ # set_bbox - Common function to create/update the boundary box for a schematic object # find_initial_canvas_position - common function to return the next 'free' position (x,y) # new_item_id - Common function - common function to return the next 'free' item ID +# get_offset_colour - Get a colour with a specified brightness offset to a specified colour +# get_text_colour - Get text colour (black/white) for max contrast with the background colour # # signal(item_id:int) - helper function to find the object Id by Item ID # point(item_id:int) - helper function to find the object Id by Item ID @@ -17,9 +19,11 @@ # line(item_id:int) - helper function to find the object Id by Item ID # track_sensor(item_id:int) - helper function to find the object Id by Item ID # route(item_id:int) - helper function to find the object Id by Item ID +# switch(item_id:int) - helper function to find the object Id by Item ID # # Objects intended to be accessed directly by other editor modules: # +# root - global reference to the Tkinter root object # canvas - global reference to the Tkinter drawing object # object_type - Enumeration type for the supported objects # schematic_objects - for accessing/editing the configuration of an object @@ -33,6 +37,7 @@ # line_index - for iterating through all the line objects # track_sensor_index - for iterating through all the sensor objects # route_index - for iterating through all the route objects +# switch_index - for iterating through all the switch objects # #------------------------------------------------------------------------------------ @@ -51,6 +56,7 @@ class object_type(): instrument:str = "instrument" track_sensor:str = "tracksensor" route:str = "route" + switch:str = "switch" #------------------------------------------------------------------------------------ # All Objects we create (and their configuration) are stored in a global dictionary @@ -71,6 +77,7 @@ class object_type(): line_index:dict={} track_sensor_index:dict={} route_index:dict={} +switch_index:dict={} #------------------------------------------------------------------------------------ # Helper functions to get the main dictionary index (the object_id) from the item_id @@ -83,6 +90,7 @@ def section(ID:int): return (section_index[str(ID)]) def line(ID:int): return (line_index[str(ID)]) def track_sensor(ID:int): return (track_sensor_index[str(ID)]) def route(ID:int): return (route_index[str(ID)]) +def switch(ID:int): return (switch_index[str(ID)]) #------------------------------------------------------------------------------------ # Common parameters for a Default Layout Object (i.e. state at creation) @@ -107,13 +115,15 @@ def route(ID:int): return (route_index[str(ID)]) #------------------------------------------------------------------------------------ canvas = None +root = None canvas_width = 0 canvas_height = 0 canvas_grid = 0 -def initialise (canvas_object, width:int, height:int, grid:int): - global canvas +def initialise (root_object, canvas_object, width:int, height:int, grid:int): + global canvas, root canvas = canvas_object + root = root_object update_canvas(canvas_width, canvas_height, grid) return() @@ -150,7 +160,6 @@ def set_bbox(object_id:str, canvas_tags:str): schematic_objects[object_id]["bbox"] = canvas.create_rectangle(x1,y1,x2,y2,state='hidden') return() - #------------------------------------------------------------------------------------ # Internal function to find an initial canvas position for the created object. # This is used by all the object type-specific creation functions (below). @@ -194,4 +203,35 @@ def new_item_id(exists_function): item_id += 1 return(item_id) +#------------------------------------------------------------------------------------ +# Common Function to calculate an appropriate colour for the 'active' and 'selected' button +# state based on the selected colour for the button - Full acknowledgement to stack overflow +# Used to set the button colours when creating "Route" objects and "Switch" objects +#------------------------------------------------------------------------------------ + +def get_offset_colour(colour:str, brightness_offset:int): + # First we ensure the colour is in Hex format + rgb = root.winfo_rgb(colour) + r,g,b = [x>>8 for x in rgb] + hex_colour = '#{:02x}{:02x}{:02x}'.format(r,g,b) + # Now we can work out the 'offset colour' from this + rgb_hex = [hex_colour[x:x+2] for x in [1, 3, 5]] + new_rgb_int = [int(hex_value, 16) + brightness_offset for hex_value in rgb_hex] + new_rgb_int = [min([255, max([0, i])]) for i in new_rgb_int] + # hex() produces "0x88", we want just "88" + active_colour = "#" + "".join([hex(i)[2:] for i in new_rgb_int]) + return(active_colour) + +#------------------------------------------------------------------------------------ +# Common Function to set the text colour to black or white depending on the overall +# intensities of the background RGB elements - Full acknowledgement to stack overflow +#------------------------------------------------------------------------------------ + +def get_text_colour(colour:str): + rgb = root.winfo_rgb(colour) + r,g,b = [x>>8 for x in rgb] + if (r*0.299 + g*0.587 + b*0.114) > 186: text_colour = "#000000" + else: text_colour = "#FFFFFF" + return(text_colour) + #################################################################################### diff --git a/model_railway_signals/editor/objects/objects_routes.py b/model_railway_signals/editor/objects/objects_routes.py index cbb78aff..23eec9d0 100644 --- a/model_railway_signals/editor/objects/objects_routes.py +++ b/model_railway_signals/editor/objects/objects_routes.py @@ -1,5 +1,6 @@ #------------------------------------------------------------------------------------ -# This module contains all the functions for managing 'Route' objects +# This module contains all the functions for managing 'Route' objects. Note that +# "Route" objects use the same underlying button library functions as "Switch" Objects #------------------------------------------------------------------------------------ # # External API functions intended for use by other editor modules: @@ -25,19 +26,25 @@ # objects_common.set_bbox - to create/update the boundary box for the schematic object # objects_common.find_initial_canvas_position - to find the next 'free' canvas position # objects_common.new_item_id - to find the next 'free' item ID when creating objects +# objects_common.get_offset_colour - Get a colour with a specified brightness offset to a specified colour +# objects_common.get_text_colour - Get text colour (black/white) for max contrast with the background colour # # Accesses the following external editor objects directly: -# objects_common.objects_common.schematic_objects - the master dictionary of Schematic Objects -# objects_common.objects_common.route_index - the type-specific index for this object type +# objects_common.schematic_objects - the master dictionary of Schematic Objects +# objects_common.route_index - the type-specific index for this object type # objects_common.default_object - The common dictionary element for all objects # objects_common.object_type - The Enumeration of supported objects # objects_common.canvas - Reference to the Tkinter drawing canvas +# objects_common.root - Reference to the Tkinter root object # -## Makes the following external API calls to library modules: +# Makes the following external API calls to library modules: # buttons.create_button(id) - Create the library object # buttons.delete_button(id) - Delete the library object # buttons.button_exists - to find out if the specified Item ID already exists # +# Accesses the following external library objects directly: +# button.button_type - for setting the enum value when creating the object +# #------------------------------------------------------------------------------------ import uuid @@ -62,9 +69,11 @@ default_route_object["linestohighlight"] = [] default_route_object["pointstohighlight"] = [] default_route_object["routecolour"] = "black" +default_route_object["buttoncolour"] = "SeaGreen3" default_route_object["switchdelay"] = 0 default_route_object["resetpoints"] = False default_route_object["tracksensor"] = 0 +default_route_object["setupsensor"] = 0 #------------------------------------------------------------------------------------ # Function to remove all references to a point from the Route's points table. @@ -179,26 +188,32 @@ def update_references_to_line(old_line_id:int, new_line_id:int): #------------------------------------------------------------------------------------ # Function to remove references to a Sensor ID from the Route's configuration. -# This is the 'tracksensor' element in the Route description dictionary. +# These are the 'tracksensor' and 'setupsensor' elements in the Route dictionary. #------------------------------------------------------------------------------------ def remove_references_to_sensor(sensor_id:int): for route_id in objects_common.route_index: - current_sensor_id = objects_common.schematic_objects[objects_common.route(route_id)]["tracksensor"] - if current_sensor_id == sensor_id: + current_cleardown_sensor_id = objects_common.schematic_objects[objects_common.route(route_id)]["tracksensor"] + if current_cleardown_sensor_id == sensor_id: objects_common.schematic_objects[objects_common.route(route_id)]["tracksensor"] = 0 + current_setup_sensor_id = objects_common.schematic_objects[objects_common.route(route_id)]["setupsensor"] + if current_setup_sensor_id == sensor_id: + objects_common.schematic_objects[objects_common.route(route_id)]["setupsensor"] = 0 return() #------------------------------------------------------------------------------------ # Function to update references to a Sensor ID in the Route's configuration -# This is the 'tracksensor' element in the Route description dictionary. +# These are the 'tracksensor' and 'setupsensor' elements in the Route dictionary. #------------------------------------------------------------------------------------ def update_references_to_sensor(old_sensor_id:int, new_sensor_id:int): for route_id in objects_common.route_index: - current_sensor_id = objects_common.schematic_objects[objects_common.route(route_id)]["tracksensor"] - if current_sensor_id == old_sensor_id: + current_cleardown_sensor_id = objects_common.schematic_objects[objects_common.route(route_id)]["tracksensor"] + if current_cleardown_sensor_id == old_sensor_id: objects_common.schematic_objects[objects_common.route(route_id)]["tracksensor"] = new_sensor_id + current_setup_sensor_id = objects_common.schematic_objects[objects_common.route(route_id)]["setupsensor"] + if current_setup_sensor_id == old_sensor_id: + objects_common.schematic_objects[objects_common.route(route_id)]["setupsensor"] = new_sensor_id return() #------------------------------------------------------------------------------------ @@ -226,16 +241,25 @@ def update_route(object_id, new_object_configuration): #------------------------------------------------------------------------------------ def redraw_route_object(object_id): + # Work out what the active and selected colours for the button should be + button_colour = objects_common.schematic_objects[object_id]["buttoncolour"] + active_colour = objects_common.get_offset_colour(button_colour, brightness_offset=25) + selected_colour = objects_common.get_offset_colour(button_colour, brightness_offset=50) + # Work out what the text colour should be - using the brightest of the three + text_colour = objects_common.get_text_colour(selected_colour) # Create the associated library object canvas_tags = buttons.create_button(objects_common.canvas, button_id = objects_common.schematic_objects[object_id]["itemid"], + buttontype = buttons.button_type.switched, x = objects_common.schematic_objects[object_id]["posx"], y = objects_common.schematic_objects[object_id]["posy"], selected_callback = run_routes.set_schematic_route_callback, deselected_callback = run_routes.clear_schematic_route_callback, width = objects_common.schematic_objects[object_id]["buttonwidth"], label = objects_common.schematic_objects[object_id]["routename"], - tooltip = objects_common.schematic_objects[object_id]["routedescription"] ) + tooltip = objects_common.schematic_objects[object_id]["routedescription"], + button_colour = button_colour, active_colour = active_colour, + selected_colour = selected_colour, text_colour = text_colour) # Store the tkinter tags for the library object and Create/update the selection rectangle objects_common.schematic_objects[object_id]["tags"] = canvas_tags objects_common.set_bbox(object_id, canvas_tags) diff --git a/model_railway_signals/editor/objects/objects_sections.py b/model_railway_signals/editor/objects/objects_sections.py index b990df51..86ef7247 100644 --- a/model_railway_signals/editor/objects/objects_sections.py +++ b/model_railway_signals/editor/objects/objects_sections.py @@ -55,6 +55,7 @@ default_section_object["item"] = objects_common.object_type.section default_section_object["defaultlabel"] = "XXXXX" default_section_object["editable"] = True +default_section_object["hidden"] = False default_section_object["mirror"] = "" #------------------------------------------------------------------------------------ @@ -130,6 +131,7 @@ def redraw_section_object(object_id): section_callback = run_layout.section_updated_callback, default_label = objects_common.schematic_objects[object_id]["defaultlabel"], editable = objects_common.schematic_objects[object_id]["editable"], + hidden = objects_common.schematic_objects[object_id]["hidden"], mirror_id = objects_common.schematic_objects[object_id]["mirror"]) # Create/update the canvas "tags" and selection rectangle for the Track Section objects_common.schematic_objects[object_id]["tags"] = canvas_tags diff --git a/model_railway_signals/editor/objects/objects_sensors.py b/model_railway_signals/editor/objects/objects_sensors.py index f218968d..61def5be 100644 --- a/model_railway_signals/editor/objects/objects_sensors.py +++ b/model_railway_signals/editor/objects/objects_sensors.py @@ -56,6 +56,7 @@ default_track_sensor_object = copy.deepcopy(objects_common.default_object) default_track_sensor_object["item"] = objects_common.object_type.track_sensor default_track_sensor_object["passedsensor"] = "" +default_track_sensor_object["hidden"] = False # The "routeahead" element comprises a list of routes: [main, lh1, lh2, rh1, rh2] # Each route comprises: [[p1, p2, p3, p4, p5, p6, p7], section_id] # Each point element in the point list comprises [point_id, point_state] @@ -217,8 +218,9 @@ def redraw_track_sensor_object(object_id): x = objects_common.schematic_objects[object_id]["posx"] y = objects_common.schematic_objects[object_id]["posy"] item_id = objects_common.schematic_objects[object_id]["itemid"] + hidden = objects_common.schematic_objects[object_id]["hidden"] callback = run_layout.sensor_passed_callback - canvas_tags = track_sensors.create_track_sensor(objects_common.canvas, item_id, x, y, callback=callback) + canvas_tags = track_sensors.create_track_sensor(objects_common.canvas, item_id, x, y, callback=callback, hidden=hidden) # Store the tkinter tags for the library object and Create/update the selection rectangle objects_common.schematic_objects[object_id]["tags"] = canvas_tags objects_common.set_bbox(object_id, canvas_tags) diff --git a/model_railway_signals/editor/objects/objects_signals.py b/model_railway_signals/editor/objects/objects_signals.py index 8f117ec2..b07a97a0 100644 --- a/model_railway_signals/editor/objects/objects_signals.py +++ b/model_railway_signals/editor/objects/objects_signals.py @@ -527,7 +527,6 @@ def redraw_signal_object(object_id): rhfeather45 = objects_common.schematic_objects[object_id]["feathers"][3], rhfeather90 = objects_common.schematic_objects[object_id]["feathers"][4], theatre_route_indicator = objects_common.schematic_objects[object_id]["theatreroute"], - refresh_immediately = False, fully_automatic = objects_common.schematic_objects[object_id]["fullyautomatic"]) # set the initial theatre route indication (for MAIN) for the signal if appropriate if objects_common.schematic_objects[object_id]["theatreroute"]: diff --git a/model_railway_signals/editor/objects/objects_switches.py b/model_railway_signals/editor/objects/objects_switches.py new file mode 100644 index 00000000..f25f755e --- /dev/null +++ b/model_railway_signals/editor/objects/objects_switches.py @@ -0,0 +1,203 @@ +#------------------------------------------------------------------------------------ +# This module contains all the functions for managing 'Switch' objects. Note that +# "Switch" objects use the same underlying button library functions as "Route" Objects +#------------------------------------------------------------------------------------ +# +# External API functions intended for use by other editor modules: +# create_switch() - Create a default "accessory switch" object on the schematic +# delete_switch(object_id) - Hard Delete an object when deleted from the schematic +# update_switch(obj_id,new_obj) - Update the configuration of an existing object +# paste_switch_(object) - Paste a copy of an object to create a new one (returns new object_id) +# delete_switch_object(object_id) - Soft delete the drawing object (prior to recreating) +# redraw_switch_object(object_id) - Redraw the object on the canvas following an update +# default_switch_object - The dictionary of default values for the object +# +# Makes the following external API calls to other editor modules: +# objects_common.set_bbox - to create/update the boundary box for the schematic object +# objects_common.find_initial_canvas_position - to find the next 'free' canvas position +# objects_common.new_item_id - to find the next 'free' item ID when creating objects +# +# Accesses the following external editor objects directly: +# objects_common.schematic_objects - the master dictionary of Schematic Objects +# objects_common.switch_index - the type-specific index for this object type +# objects_common.default_object - The common dictionary element for all objects +# objects_common.object_type - The Enumeration of supported objects +# objects_common.canvas - Reference to the Tkinter drawing canvas +# objects_common.root - Reference to the Tkinter root object +# +# Makes the following external API calls to library modules: +# buttons.create_button(id) - Create the library object +# buttons.delete_button(id) - Delete the library object +# buttons.button_exists - to find out if the specified Item ID already exists +# dcc_control.map_dcc_switch - Create a DCC mapping for the Accessory Switch +# dcc_control.delete_switch_mapping - Delete the DCC mapping for the switch +# Accesses the following external library objects directly: +# button.button_type - for setting the enum value when creating the object +# +#------------------------------------------------------------------------------------ + +import uuid +import copy + +from ...library import buttons +from ...library import dcc_control +from . import objects_common + +#------------------------------------------------------------------------------------ +# Default Switch Object (i.e. state at creation) +#------------------------------------------------------------------------------------ + +default_switch_object = copy.deepcopy(objects_common.default_object) +default_switch_object["item"] = objects_common.object_type.switch +default_switch_object["itemtype"] = buttons.button_type.switched.value +default_switch_object["switchname"] = "DCC Switch" +default_switch_object["switchdescription"] = "Switch description (Run Mode tooltip)" +default_switch_object["buttonwidth"] = 10 +default_switch_object["buttoncolour"] = "SkyBlue2" +default_switch_object["hidden"] = False +# Each DCC command sequence comprises a variable list of DCC commands +# Each DCC command comprises: [DCC address, DCC state] +default_switch_object["dcconcommands"] = [] +default_switch_object["dccoffcommands"] = [] + +#------------------------------------------------------------------------------------ +# Function to to update a Switch object following a configuration change +#------------------------------------------------------------------------------------ + +def update_switch(object_id, new_object_configuration): + # We need to track whether the Item ID has changed + old_item_id = objects_common.schematic_objects[object_id]["itemid"] + new_item_id = new_object_configuration["itemid"] + # Delete the existing object, copy across the new config and redraw + delete_switch_object(object_id) + objects_common.schematic_objects[object_id] = copy.deepcopy(new_object_configuration) + redraw_switch_object(object_id) + # Check to see if the Type-specific ID has been changed + if old_item_id != new_item_id: + # Update the type-specific index + del objects_common.switch_index[str(old_item_id)] + objects_common.switch_index[str(new_item_id)] = object_id + return() + +#------------------------------------------------------------------------------------ +# Null callback for the DCC Accessory buttons as these are just sending out DCC +# Commands when selected/deselected - there is no other processing to do. +#------------------------------------------------------------------------------------ + +def local_null_callback(button_id): + return() + +#------------------------------------------------------------------------------------ +# Function to re-draw a Switch object on the schematic. Called when the object +# is first created or after the object attributes have been updated. +#------------------------------------------------------------------------------------ + +def redraw_switch_object(object_id): + # Create the DCC mapping for the switch + dcc_control.map_dcc_switch(objects_common.schematic_objects[object_id]["itemid"], + objects_common.schematic_objects[object_id]["dcconcommands"], + objects_common.schematic_objects[object_id]["dccoffcommands"]) + # Turn the button type value back into the required enumeration type + button_type = buttons.button_type(objects_common.schematic_objects[object_id]["itemtype"]) + # Work out what the active and selected colours for the button should be + button_colour = objects_common.schematic_objects[object_id]["buttoncolour"] + active_colour = objects_common.get_offset_colour(button_colour, brightness_offset=25) + selected_colour = objects_common.get_offset_colour(button_colour, brightness_offset=50) + # Work out what the text colour should be - using the brightest of the three + text_colour = objects_common.get_text_colour(selected_colour) + # Create the associated library object + canvas_tags = buttons.create_button(objects_common.canvas, + button_id = objects_common.schematic_objects[object_id]["itemid"], + buttontype = button_type, + x = objects_common.schematic_objects[object_id]["posx"], + y = objects_common.schematic_objects[object_id]["posy"], + selected_callback = local_null_callback, + deselected_callback = local_null_callback, + width = objects_common.schematic_objects[object_id]["buttonwidth"], + label = objects_common.schematic_objects[object_id]["switchname"], + tooltip = objects_common.schematic_objects[object_id]["switchdescription"], + hidden = objects_common.schematic_objects[object_id]["hidden"], + button_colour = button_colour, active_colour = active_colour, + selected_colour = selected_colour, text_colour = text_colour) + # Store the tkinter tags for the library object and Create/update the selection rectangle + objects_common.schematic_objects[object_id]["tags"] = canvas_tags + objects_common.set_bbox(object_id, canvas_tags) + return() + +#------------------------------------------------------------------------------------ +# Function to Create a new default Switch object (and draw it on the canvas) +#------------------------------------------------------------------------------------ + +def create_switch(): + # Generate a new object from the default configuration with a new UUID + object_id = str(uuid.uuid4()) + objects_common.schematic_objects[object_id] = copy.deepcopy(default_switch_object) + # Find the initial canvas position and assign the initial ID + x, y = objects_common.find_initial_canvas_position() + item_id = objects_common.new_item_id(exists_function=buttons.button_exists) + # Add the specific elements for this particular instance of the object + objects_common.schematic_objects[object_id]["itemid"] = item_id + objects_common.schematic_objects[object_id]["posx"] = x + objects_common.schematic_objects[object_id]["posy"] = y + # Add the new object to the type-specific index + objects_common.switch_index[str(item_id)] = object_id + # Draw the Object on the canvas + redraw_switch_object(object_id) + return(object_id) + +#------------------------------------------------------------------------------------ +# Function to paste a copy of an existing Switch Object - returns the new Object ID +#------------------------------------------------------------------------------------ + +def paste_switch(object_to_paste, deltax:int, deltay:int): + # Create a new UUID for the pasted object + new_object_id = str(uuid.uuid4()) + objects_common.schematic_objects[new_object_id] = copy.deepcopy(object_to_paste) + # Assign a new type-specific ID for the object and add to the index + new_id = objects_common.new_item_id(exists_function=buttons.button_exists) + objects_common.schematic_objects[new_object_id]["itemid"] = new_id + objects_common.switch_index[str(new_id)] = new_object_id + # Set the position for the "pasted" object (offset from the original position) + objects_common.schematic_objects[new_object_id]["posx"] += deltax + objects_common.schematic_objects[new_object_id]["posy"] += deltay + # Now set the default values for all elements we don't want to copy + # The bits we want to copy are - buttonwidth, buttoncolour, hidden + objects_common.schematic_objects[new_object_id]["switchname"] = default_switch_object["switchname"] + objects_common.schematic_objects[new_object_id]["switchdescription"] = default_switch_object["switchdescription"] + objects_common.schematic_objects[new_object_id]["dcconcommands"] = default_switch_object["dcconcommands"] + objects_common.schematic_objects[new_object_id]["dccoffcommands"] = default_switch_object["dccoffcommands"] + # Set the Boundary box for the new object to None so it gets created on re-draw + objects_common.schematic_objects[new_object_id]["bbox"] = None + # Create the associated library objects + redraw_switch_object(new_object_id) + return(new_object_id) + +#------------------------------------------------------------------------------------ +# Function to "soft delete" the Switch object from the canvas - Primarily used to +# delete the object in its current configuration prior to re-creating in its new +# configuration - also called as part of a hard delete (below). +#------------------------------------------------------------------------------------ + +def delete_switch_object(object_id): + # Delete the associated library objects + item_id = objects_common.schematic_objects[object_id]["itemid"] + buttons.delete_button(item_id) + dcc_control.delete_switch_mapping(objects_common.schematic_objects[object_id]["itemid"]) + return() + +#------------------------------------------------------------------------------------ +# Function to 'hard delete' a Switch object (drawing objects and the main +# dictionary entry). Function called when object is deleted from the schematic. +#------------------------------------------------------------------------------------ + +def delete_switch(object_id): + # Soft delete the associated library objects from the canvas + delete_switch_object(object_id) + # "Hard Delete" the selected object - deleting the boundary box rectangle and + # deleting the object from the dictionary of schematic objects + objects_common.canvas.delete(objects_common.schematic_objects[object_id]["bbox"]) + del objects_common.switch_index[str(objects_common.schematic_objects[object_id]["itemid"])] + del objects_common.schematic_objects[object_id] + return() + +#################################################################################### diff --git a/model_railway_signals/editor/objects/objects_textboxes.py b/model_railway_signals/editor/objects/objects_textboxes.py index 628ca239..1752faee 100644 --- a/model_railway_signals/editor/objects/objects_textboxes.py +++ b/model_railway_signals/editor/objects/objects_textboxes.py @@ -21,12 +21,18 @@ # objects_common.object_type - The Enumeration of supported objects # objects_common.canvas - Reference to the Tkinter drawing canvas # +# Makes the following external API calls to library modules: +# text_boxes.create_text_box(id) - Create the library object +# text_boxes.delete_text_box(id) - Delete the library object +# text_boxes.text_box_exists - to find out if the specified Item ID already exists +# #------------------------------------------------------------------------------------ import uuid import copy import tkinter as Tk +from ...library import text_boxes from . import objects_common #------------------------------------------------------------------------------------ @@ -43,6 +49,7 @@ default_textbox_object["fontsize"] = 10 default_textbox_object["fontstyle"] = "" default_textbox_object["border"] = 0 +default_textbox_object["hidden"] = False #------------------------------------------------------------------------------------ # Function to to update a text box object following a configuration change @@ -61,33 +68,36 @@ def update_textbox(object_id, new_object_configuration): #------------------------------------------------------------------------------------ def redraw_textbox_object(object_id): - x1 = objects_common.schematic_objects[object_id]["posx"] - y1 = objects_common.schematic_objects[object_id]["posy"] - colour = objects_common.schematic_objects[object_id]["colour"] - background = objects_common.schematic_objects[object_id]["background"] - font = (objects_common.schematic_objects[object_id]["font"], - objects_common.schematic_objects[object_id]["fontsize"], - objects_common.schematic_objects[object_id]["fontstyle"]) - text = objects_common.schematic_objects[object_id]["text"] - width = objects_common.schematic_objects[object_id]["border"] - # Create the textbox, justifying the text as required - if objects_common.schematic_objects[object_id]["justify"] == 1: justify=Tk.LEFT - elif objects_common.schematic_objects[object_id]["justify"] == 2: justify=Tk.CENTER - elif objects_common.schematic_objects[object_id]["justify"] == 3: justify=Tk.RIGHT - text_box = objects_common.canvas.create_text(x1,y1,fill=colour,text=text,tags=str(object_id),justify=justify,font=font) - # find the boundary box and create the rectangle for the background - bbox = objects_common.canvas.bbox(text_box) - rectangle = objects_common.canvas.create_rectangle(bbox[0]-3, bbox[1]-3, bbox[2]+3, bbox[3]+2, - width=width, tags=str(object_id), fill=background, outline=colour) - # Raise the text item to be in front of the rectangle item - objects_common.canvas.tag_raise(text_box, rectangle) - # Create/update the canvas "tags" and selection rectangle for the textbox - objects_common.schematic_objects[object_id]["tags"] = str(object_id) - objects_common.set_bbox(object_id, str(object_id)) + # Create the Tkinter Font tuple + tkinter_font_tuple = (objects_common.schematic_objects[object_id]["font"], + objects_common.schematic_objects[object_id]["fontsize"], + objects_common.schematic_objects[object_id]["fontstyle"]) + # Assign the tkinter justification parameter + if objects_common.schematic_objects[object_id]["justify"] == 1: tkinter_justification=Tk.LEFT + elif objects_common.schematic_objects[object_id]["justify"] == 2: tkinter_justification=Tk.CENTER + elif objects_common.schematic_objects[object_id]["justify"] == 3: tkinter_justification=Tk.RIGHT + else: tkinter_justification=Tk.CENTER + # Create the text box on the canvas + canvas_tags = text_boxes.create_text_box(objects_common.canvas, + textbox_id = objects_common.schematic_objects[object_id]["itemid"], + x = objects_common.schematic_objects[object_id]["posx"], + y = objects_common.schematic_objects[object_id]["posy"], + text = objects_common.schematic_objects[object_id]["text"], + colour = objects_common.schematic_objects[object_id]["colour"], + background = objects_common.schematic_objects[object_id]["background"], + justify = tkinter_justification, + borderwidth = objects_common.schematic_objects[object_id]["border"], + font = tkinter_font_tuple, + hidden = objects_common.schematic_objects[object_id]["hidden"]) + # Create/update the selection rectangle for the textbox + objects_common.schematic_objects[object_id]["tags"] = canvas_tags + objects_common.set_bbox(object_id, canvas_tags) return() #------------------------------------------------------------------------------------ # Function to Create a new default Text Box (and draw it on the canvas) +# Note that, unlike other objects, there is no requirement to iterate through +# text boxes at run time so we don't need to maintain a type-specific index #------------------------------------------------------------------------------------ def create_textbox(): @@ -99,6 +109,8 @@ def create_textbox(): # Find the initial canvas position x, y = objects_common.find_initial_canvas_position() # Add the specific elements for this particular instance of the object + item_id = objects_common.new_item_id(exists_function=text_boxes.text_box_exists) + objects_common.schematic_objects[object_id]["itemid"] = item_id objects_common.schematic_objects[object_id]["posx"] = x objects_common.schematic_objects[object_id]["posy"] = y # Draw the text box on the canvas @@ -107,12 +119,16 @@ def create_textbox(): #------------------------------------------------------------------------------------ # Function to paste a copy of an existing text box - returns the new Object ID +# For textboxes we copy all elements across apart from the position and the Item ID #------------------------------------------------------------------------------------ def paste_textbox(object_to_paste, deltax:int, deltay:int): # Create a new UUID for the pasted object new_object_id = str(uuid.uuid4()) objects_common.schematic_objects[new_object_id] = copy.deepcopy(object_to_paste) + # Assign a new type-specific ID for the object + new_id = objects_common.new_item_id(exists_function=text_boxes.text_box_exists) + objects_common.schematic_objects[new_object_id]["itemid"] = new_id # Set the position for the "pasted" object (offset from the original position) objects_common.schematic_objects[new_object_id]["posx"] += deltax objects_common.schematic_objects[new_object_id]["posy"] += deltay @@ -129,8 +145,9 @@ def paste_textbox(object_to_paste, deltax:int, deltay:int): #------------------------------------------------------------------------------------ def delete_textbox_object(object_id): - # Delete the tkinter drawing objects associated with the object - objects_common.canvas.delete(objects_common.schematic_objects[object_id]["tags"]) + # Delete the associated library objects + item_id = objects_common.schematic_objects[object_id]["itemid"] + text_boxes.delete_text_box(item_id) return() #------------------------------------------------------------------------------------ @@ -142,7 +159,7 @@ def delete_textbox(object_id): # Soft delete the associated library objects from the canvas delete_textbox_object(object_id) # "Hard Delete" the selected object - deleting the boundary box rectangle and deleting - # the object from the dictionary of schematic objects + # the object from the dictionary of schematic objects (note no index for text boxes) objects_common.canvas.delete(objects_common.schematic_objects[object_id]["bbox"]) del objects_common.schematic_objects[object_id] return() diff --git a/model_railway_signals/editor/run_layout.py b/model_railway_signals/editor/run_layout.py index a2aedcb5..28f5f629 100644 --- a/model_railway_signals/editor/run_layout.py +++ b/model_railway_signals/editor/run_layout.py @@ -23,7 +23,13 @@ # objects.point(point_id) - To get the object_id for a given point_id # objects.section(section_id) - To get the object_id for a given section_id # objects.track_sensor(sensor_id) - To get the object_id for a given sensor_id -# +# run_routes.enable_disable_schematic_routes() +# run_routes.initialise_all_schematic_routes() +# run_routes.check_routes_valid_after_signal_change(signal_id, route_id) +# run_routes.check_routes_valid_after_point_change(point_id, route_id) +# run_routes.check_routes_valid_after_subsidary_change(signal_id, route_id) +# run_routes.trigger_routes_after_sensor_passed(sensor_id) +# # Accesses the following external editor objects directly: # objects.schematic_objects - the dict holding descriptions for all objects # objects.object_type - used to establish the type of the schematic objects @@ -351,10 +357,11 @@ def update_signal_behind(int_or_str_signal_id:Union[int,str], recursion_level:in return() #------------------------------------------------------------------------------------ -# Functions to update a signal aspect based on the signal ahead and then to work back -# along the set route to update any other signals that need changing. Note that the -# signal ID could be LOCAL or REMOTE. We only update on the signal ahead for LOCAL -# signals but always update the signals behind for LOCAL or REMOTE signals. +# Internal function to update a colour light signal aspect based on the displayed aspect +# of the signal ahead and then to work back along the set route to update any other colour +# light signals signals need changing. Note that the signal ID could be LOCAL or REMOTE. +# We only update on the signal ahead for LOCAL signals (as we have no idea of the signal +# ahead on the other schematic) but update the signals behind for LOCAL or REMOTE signals. #------------------------------------------------------------------------------------ def process_signal_aspect_update(int_or_str_signal_id:Union[int,str]): @@ -450,7 +457,8 @@ def update_signal_approach_control(int_signal_id:int, force_set:bool, recursion_ signals.clear_approach_control(int_signal_id) else: signals.clear_approach_control(int_signal_id) - # Update the signal aspect and change the displayed aspect of any signals behind (if required) + # Update the signal aspect (for colour light signals) and work back along the route to change the displayed + # aspect of any colour light signals behind (if the displayed aspect of the current signal has changed) process_signal_aspect_update(int_signal_id) # If the displayed aspect has changed then we also need to work back along the route to update # the approach control status of any signals behind (for the semaphore approach control use case) @@ -1031,7 +1039,7 @@ def sensor_passed_callback(sensor_id:int): update_approach_control_status_for_all_signals() override_distant_signals_based_on_signals_ahead() process_all_signal_interlocking() - run_routes.clear_down_routes_after_sensor_passed(sensor_id) + run_routes.trigger_routes_after_sensor_passed(sensor_id) run_routes.enable_disable_schematic_routes() return() diff --git a/model_railway_signals/editor/run_routes.py b/model_railway_signals/editor/run_routes.py index a0fac978..5129ac7b 100644 --- a/model_railway_signals/editor/run_routes.py +++ b/model_railway_signals/editor/run_routes.py @@ -10,7 +10,7 @@ # check_routes_valid_after_point_change(point_id) - Clears down route highlighting if compromised # check_routes_valid_after_signal_change(signal_id) - Clears down route highlighting if compromised # check_routes_valid_after_subsidary_change(subsidary_id) - Clears down route highlighting if compromised -# clear_down_routes_after_sensor_passed(sensor_id) - automatically clear down routes on sensor passed events +# trigger_routes_after_sensor_passed(sensor_id) - automatically set up routes on sensor passed events # enable_disable_schematic_routes() - enable/disable route buttons based on route viability # initialise_all_schematic_routes() - highlight/unhighlight routes demending on mode and route selections # @@ -45,9 +45,10 @@ # points.reset_point_colour(line_id) - Used for shematic route setting # buttons.enable_button(button_id) - to enable the schematic route selection # buttons.disable_button(button_id) - to disable the schematic route selection -# buttons.toggle_button(button_id) - to get the current state of the button +# buttons.toggle_button(button_id) - to toggle the current state of the button # buttons.button_state(button_id) - to get the current state of the button -# buttons.processing_complete(button_id) - to enable the button after route setting has completed +# buttons.lock_button(button_id) - lock the button whilst route setup/cleardown is in progress +# buttons.unlock_button(button_id) - unlock the button after route setup/cleardown has completed # lines.set_line_colour(line_id) - Used for shematic route setting # lines.reset_line_colour(line_id) - Used for shematic route setting # block_instruments.block_section_ahead_clear(inst_id) - Test if an instrument is clear @@ -55,6 +56,8 @@ # #------------------------------------------------------------------------------------ +import logging + from . import run_layout from ..library import signals @@ -106,13 +109,13 @@ def configure_automation(automation:bool): return() #------------------------------------------------------------------------------------ -# This is a sub-function to test if any of thee signals along the route would be locked -# by an opposing signal once the route has been set up. The function is called twice from -# the enable_disable_schematic_routes function - once for the main signals on the route -# and a second time for the subsidariy signals (attached to a main signal) on the route. +# This is a sub-function to test if any of the signals along the route would be locked +# by an opposing signal once the route has been set up. The function is called twice +# from the parent functions function - once for the main signals on the route and a +# second time for the subsidariy signals (attached to a main signal) on the route. #------------------------------------------------------------------------------------ -def check_conflicting_signals(route_object, route_tooltip:str, route_viable:bool, subsidaries:bool=False): +def check_conflicting_signals(route_object:dict, route_tooltip:str, route_viable:bool, subsidaries:bool=False): # Set up the function to check Signals or subsidaries if subsidaries: signals_on_route_dict_key = "subsidariesonroute" @@ -182,6 +185,60 @@ def check_conflicting_signals(route_object, route_tooltip:str, route_viable:bool route_tooltip = route_tooltip + message return(route_tooltip, route_viable) +#------------------------------------------------------------------------------------ +# This is a sub-function to test if any of the points along the route are locked in +# the wrong position by a signal (preventing the route from being set up) +#------------------------------------------------------------------------------------ + +def check_conflicting_points(route_object:dict, route_tooltip:str, route_viable:bool): + # See if any points that need to be set for the route are locked by a signal at OFF + # Note that automatic signals are ignored (manual points should have been specified)) + for str_point_id in route_object["pointsonroute"].keys(): + automatic_point = objects.schematic_objects[objects.point(str_point_id)]["automatic"] + required_point_state = route_object["pointsonroute"][str_point_id] + int_point_id = int(str_point_id) + if not automatic_point and points.point_switched(int_point_id) != required_point_state and points.point_locked(int_point_id): + # We've found a manual point that needs switching for the route but is currently locked + # We then iterate through the signal interlocking table for the point to test each + # interlocked signal (and signal route) to find the signal(s) that are locking the point + # The Point interlocking Table comprises a variable length list of interlocked signals + # Each signal entry in the list comprises [sig_id, [main, lh1, lh2, rh1, rh2]] + # Each route element in the list of routes is a boolean value (True or False) + point_object = objects.schematic_objects[objects.point(int_point_id)] + for interlocked_signal in point_object["siginterlock"]: + interlocked_sig_id = interlocked_signal[0] + interlocked_routes = interlocked_signal[1] + for index, interlocked_route in enumerate(interlocked_routes): + route_to_test = signals.route_type(index+1) + if interlocked_route: + if signals.signal_clear(interlocked_sig_id, route_to_test): + message = "\nPoint "+str_point_id+" is locked by Signal "+str(interlocked_sig_id) + route_tooltip = route_tooltip + message + route_viable = False + if run_layout.has_subsidary(interlocked_sig_id) and signals.subsidary_clear(interlocked_sig_id, route_to_test): + message = "\nPoint "+str_point_id+" is locked by subsidary "+str(interlocked_sig_id) + route_tooltip = route_tooltip + message + route_viable = False + return(route_tooltip, route_viable) + +#------------------------------------------------------------------------------------ +# This is a sub-function to test if a given route is viable (i.e. the current state +# of the points and signals on the layout do not prevent the route being set up) +#------------------------------------------------------------------------------------ + +def check_route_viable(str_route_id:str): + route_object = objects.schematic_objects[objects.route(str_route_id)] + route_tooltip, route_viable = "", True + # See if any points that need to be set for the route are locked by a signal at OFF + # Note that automatic signals are ignored (manual points should have been specified)) + route_tooltip, route_viable = check_conflicting_points(route_object, route_tooltip, route_viable) + # See if any signals along the route WOULD be locked by an opposing signal once the route is set + # This function also tests to see any of the signals WOULD be locked by the Block Instrument Ahead + # and if any of the signals WOULD be locked by an occupied track section on the route ahead + route_tooltip, route_viable = check_conflicting_signals(route_object, route_tooltip, route_viable) + route_tooltip, route_viable = check_conflicting_signals(route_object, route_tooltip, route_viable, subsidaries=True) + return(route_tooltip, route_viable) + #------------------------------------------------------------------------------------ # This function is called after any layout state change that might affect the viability # of a schematic route, enabling or disabling the route buttons accordingly @@ -190,45 +247,14 @@ def check_conflicting_signals(route_object, route_tooltip:str, route_viable:bool def enable_disable_schematic_routes(): # Iterate through all the schematic routes for str_route_id in objects.route_index.keys(): - route_viable = True - route_tooltip = "Route cannot be set because:" - route_object = objects.schematic_objects[objects.route(str_route_id)] - # See if any points that need to be set for the route are locked by a signal at OFF - # Note that automatic signals are ignored (manual points should have been specified)) - for str_point_id in route_object["pointsonroute"].keys(): - automatic_point = objects.schematic_objects[objects.point(str_point_id)]["automatic"] - required_point_state = route_object["pointsonroute"][str_point_id] - int_point_id = int(str_point_id) - if not automatic_point and points.point_switched(int_point_id) != required_point_state and points.point_locked(int_point_id): - # We've found a manual point that needs switching for the route but is currently locked - # We then iterate through the signal interlocking table for the point to test each - # interlocked signal (and signal route) to find the signal(s) that are locking the point - # The Point interlocking Table comprises a variable length list of interlocked signals - # Each signal entry in the list comprises [sig_id, [main, lh1, lh2, rh1, rh2]] - # Each route element in the list of routes is a boolean value (True or False) - point_object = objects.schematic_objects[objects.point(int_point_id)] - for interlocked_signal in point_object["siginterlock"]: - interlocked_sig_id = interlocked_signal[0] - interlocked_routes = interlocked_signal[1] - for index, interlocked_route in enumerate(interlocked_routes): - route_to_test = signals.route_type(index+1) - if interlocked_route: - if signals.signal_clear(interlocked_sig_id, route_to_test): - message = "\nPoint "+str_point_id+" is locked by Signal "+str(interlocked_sig_id) - route_tooltip = route_tooltip + message - route_viable = False - if run_layout.has_subsidary(interlocked_sig_id) and signals.subsidary_clear(interlocked_sig_id, route_to_test): - message = "\nPoint "+str_point_id+" is locked by subsidary "+str(interlocked_sig_id) - route_tooltip = route_tooltip + message - route_viable = False - # See if any signals along the route WOULD be locked by an opposing signal once the route is set - # This function also tests to see any of the signals WOULD be locked by the Block Instrument Ahead - # and if any of the signals WOULD be locked by an occupied track section on the route ahead - route_tooltip, route_viable = check_conflicting_signals(route_object, route_tooltip, route_viable) - route_tooltip, route_viable = check_conflicting_signals(route_object, route_tooltip, route_viable, subsidaries=True) - # Enable/disable the route button as required - if route_viable: buttons.enable_button(int(str_route_id)) - else: buttons.disable_button(int(str_route_id), route_tooltip) + route_tooltip, route_viable = check_route_viable(str_route_id) + # Enable/disable the route button as required. Note that selected route buttons + # always remain enabled (unless locked) so they can be deselected as required + if route_viable or buttons.button_state(int(str_route_id)): + buttons.enable_button(int(str_route_id)) + else: + route_tooltip = "Route "+str_route_id+" cannot be set because:"+route_tooltip + buttons.disable_button(int(str_route_id), route_tooltip) return() #------------------------------------------------------------------------------------ @@ -309,17 +335,29 @@ def check_routes_valid_after_point_change(point_id:int, route_id:int): return() #------------------------------------------------------------------------------------ -# Function to automatically reset a schematic route after a track sensor passed event +# Function to automatically set/reset a schematic route after a track sensor passed event #------------------------------------------------------------------------------------ -def clear_down_routes_after_sensor_passed(sensor_id:int): +def trigger_routes_after_sensor_passed(sensor_id:int): for str_route_id in objects.route_index: route_object = objects.schematic_objects[objects.route(str_route_id)] + # Process the clear down of any routes (button is always enabled if active) if buttons.button_state(int(str_route_id)) and route_object["tracksensor"] == sensor_id: - # Reset the button to show the route has been cleared down + # Reset the button (and then lock it) to show the route has been deselected buttons.toggle_button(int(str_route_id)) - # Schedule all the events to clear down the route + # Schedule all the events to clear down the route (finally unlocking the button) clear_schematic_route_callback(int(str_route_id)) + # Process the set up of any routes (button may be enabled or disabled) + if not buttons.button_state(int(str_route_id)) and route_object["setupsensor"] == sensor_id: + # Only trigger the route setup if the route can be set up (i.e. route is viable) + route_tooltip, route_viable = check_route_viable(str_route_id) + if route_viable: + # Set the button (and then lock it) to show the route has been selected + buttons.toggle_button(int(str_route_id)) + # Schedule all the events to set up the route (finally unlocking the button) + set_schematic_route_callback(int(str_route_id)) + else: + logging.warning("RUN ROUTES - Track Sensor "+str(sensor_id)+" cannot trigger Route "+str_route_id+" set-up because:"+route_tooltip) return() #------------------------------------------------------------------------------------------------- @@ -375,6 +413,22 @@ def set_point_state(route_id:int, point_id:int, state:bool): run_layout.point_switched_callback(point_id, route_id) return() +def set_route_highlighting(route_id:int, colour:str): + # Set the point and line colours to highlight the route + for point_id in objects.schematic_objects[objects.route(route_id)]["pointstohighlight"]: + points.set_point_colour(point_id, colour) + for line_id in objects.schematic_objects[objects.route(route_id)]["linestohighlight"]: + lines.set_line_colour(line_id, colour) + return() + +def reset_route_highlighting(route_id:int): + # Reset the point and line colours to un-highlight the route + for point_id in objects.schematic_objects[objects.route(route_id)]["pointstohighlight"]: + points.reset_point_colour(point_id) + for line_id in objects.schematic_objects[objects.route(route_id)]["linestohighlight"]: + lines.reset_line_colour(line_id) + return() + def complete_route_setup(route_id:int): if run_mode: # Confirm the route has been set up correctly - just in case there have been any other events @@ -397,26 +451,31 @@ def complete_route_setup(route_id:int): # If successful we update the point and line colours to highlight the route # If unsuccessful we de-select the button (to show the route was not set up) if route_set_up_and_locked: - colour=objects.schematic_objects[objects.route(route_id)]["routecolour"] - for point_id in objects.schematic_objects[objects.route(route_id)]["pointstohighlight"]: - points.set_point_colour(point_id, colour) - for line_id in objects.schematic_objects[objects.route(route_id)]["linestohighlight"]: - lines.set_line_colour(line_id, colour) + set_route_highlighting(route_id, objects.schematic_objects[objects.route(route_id)]["routecolour"]) else: if buttons.button_state(route_id): buttons.toggle_button(route_id) - # Unlock the route button now the processing is complete - buttons.processing_complete(route_id) + # Unlock the route button now processing is complete (so it can be enabled) + buttons.unlock_button(route_id) + # Re-enable the button if the route remains viable (as something might have changed in the interim). + # We always enable the button if it is selected (so it can always be de-selected) + tooltip, route_viable = check_route_viable(route_id) + if route_viable or buttons.button_state(route_id): buttons.enable_button(route_id) + else: buttons.disable_button(route_id, tooltip) + logging.info("RUN ROUTES - Set-up of Route "+str(route_id)+" is now complete **************************************") return() def complete_route_cleardown(route_id:int): - # Reset the point and line colours to un-highlight the route + # Ensure the route is unhighlighted + reset_route_highlighting(route_id) + # Unlock the route button now processing is complete (so it can be enabled) # Note that this function will get executed in both EDIT and RUN Modes - for point_id in objects.schematic_objects[objects.route(route_id)]["pointstohighlight"]: - points.reset_point_colour(point_id) - for line_id in objects.schematic_objects[objects.route(route_id)]["linestohighlight"]: - lines.reset_line_colour(line_id) - # Unlock the route button now the processing is complete - buttons.processing_complete(route_id) + buttons.unlock_button(route_id) + # Re-enable the button if the route remains viable (as something might have changed in the interim). + # We always enable the button if it is selected (so it can always be de-selected) + tooltip, route_viable = check_route_viable(route_id) + if route_viable or buttons.button_state(route_id): buttons.enable_button(route_id) + else: buttons.disable_button(route_id, tooltip) + logging.info("RUN ROUTES - Clear-down of Route "+str(route_id)+" is now complete **********************************") return() #------------------------------------------------------------------------------------ @@ -432,6 +491,10 @@ def complete_route_cleardown(route_id:int): #------------------------------------------------------------------------------------ def set_schematic_route_callback(route_id:int): + logging.info("RUN ROUTES - Initiating set-up of Route "+str(route_id)+" *******************************************") + # Lock and disable the route button (to prevent further clicks before we have finished setting up the route) + buttons.lock_button(route_id) + buttons.disable_button(route_id, tooltip="Route clear down in progress") # Retrieve the object configuration for the Route route_object = objects.schematic_objects[objects.route(route_id)] delay = route_object["switchdelay"] @@ -492,9 +555,15 @@ def set_schematic_route_callback(route_id:int): #------------------------------------------------------------------------------------ def clear_schematic_route_callback(route_id:int): + logging.info("RUN ROUTES - Initiating clear-down of Route "+str(route_id)+" ***************************************") + # Lock and disable the route button (to prevent further clicks before we have finished clearing down the route) + buttons.lock_button(route_id) + buttons.disable_button(route_id, tooltip="Route clear down in progress") # Retrieve the object configuration for the Route route_object = objects.schematic_objects[objects.route(route_id)] delay = route_object["switchdelay"] + # Unhighlight the route to show it has been de-selected + reset_route_highlighting(route_id) # Schedule tasks to set all the signals along the route to ON. The "signalsonroute" and # "subsidariesonroute" elements of the route object comprise a list of signal_ids. We # don't need to check their state beforehand as any user initiated change (to ON) would diff --git a/model_railway_signals/editor/schematic.py b/model_railway_signals/editor/schematic.py index 58e2ec2c..0a4feaf5 100644 --- a/model_railway_signals/editor/schematic.py +++ b/model_railway_signals/editor/schematic.py @@ -26,6 +26,7 @@ # configure_textbox.edit_textbox(root,object_id) - Open textbox edit window (on double click) # configure_track_sensor.edit_track_sensor(root,object_id) - Open the edit window (on double click) # configure_route.edit_route(root,object_id) - Open the edit window (on double click) +# configure_switch.edit_switch(root,object_id) - Open the edit window (on double click) # run_layout.initialise(root_window, canvas) - Initialise the run_layout module with the root and canvas # run_routes.initialise(root_window, canvas) - Initialise the run_routes module with the root and canvas # @@ -65,6 +66,7 @@ from . import configure_textbox from . import configure_track_sensor from . import configure_route +from . import configure_switch import importlib.resources import math @@ -248,6 +250,8 @@ def edit_selected_object(): edit_popup = configure_track_sensor.edit_track_sensor(root,object_id) elif objects.schematic_objects[object_id]["item"] == objects.object_type.route: edit_popup = configure_route.edit_route(root,object_id) + elif objects.schematic_objects[object_id]["item"] == objects.object_type.switch: + edit_popup = configure_switch.edit_switch(root,object_id) return() # The following function is for test purposes only - to close the windows opened above by the system tests @@ -761,6 +765,9 @@ def enable_all_keypress_events_after_completion_of_move(): enable_arrow_keypress_events() canvas.bind('', canvas_event_callback) # Toggle Mode (Edit/Run) canvas.bind('', reset_window_size) + # Re-bind the other mouse buttons to re-enable right clicks + canvas.bind('', right_button_click) + canvas.bind('', right_button_click) return() def disable_all_keypress_events_during_move(): @@ -768,7 +775,10 @@ def disable_all_keypress_events_during_move(): disable_arrow_keypress_events() canvas.bind('',cancel_move_in_progress) canvas.unbind('') # Toggle Mode (Edit/Run) - canvas.unbind('') # Toggle Mode (Edit/Run) + canvas.unbind('') # Revert Canvas Size + # Unbind the other mouse buttons to prevent inadvertant clicks + canvas.unbind('') + canvas.unbind('') return() #------------------------------------------------------------------------------------ @@ -942,7 +952,9 @@ def initialise (root_window, event_callback, width:int, height:int, grid:int, sn ["sensor", lambda:create_object(objects.object_type.track_sensor) ], ["instrument", lambda:create_object(objects.object_type.instrument, block_instruments.instrument_type.single_line.value) ], - ["route", lambda:create_object(objects.object_type.route)] ] + ["route", lambda:create_object(objects.object_type.route)] ] ######################## +# ["route", lambda:create_object(objects.object_type.route)], ######################## +# ["switch", lambda:create_object(objects.object_type.switch)] ] ######################## # Create the buttons we need (Note that the button images are added to a global # list so they remain in scope (otherwise the buttons won't work) resource_folder = 'model_railway_signals.editor.resources' @@ -960,7 +972,7 @@ def initialise (root_window, event_callback, width:int, height:int, grid:int, sn button = Tk.Button (button_frame, text=selections[index][0],command=selections[index][1], bg="grey85") button.pack(padx=2, pady=2, fill='x') # Initialise the Objects and run_layout modules with the canvas details - objects.initialise(canvas, canvas_width, canvas_height, canvas_grid) + objects.initialise(root_window, canvas, canvas_width, canvas_height, canvas_grid) run_layout.initialise(root_window, canvas) run_routes.initialise(root_window, canvas) return() diff --git a/model_railway_signals/editor/settings.py b/model_railway_signals/editor/settings.py index 3bb18fec..67f3b7ce 100644 --- a/model_railway_signals/editor/settings.py +++ b/model_railway_signals/editor/settings.py @@ -50,10 +50,11 @@ default_settings["general"] = {} default_settings["general"]["filename"] = "new_layout.sig" default_settings["general"]["editmode"] = True -default_settings["general"]["version"] = "Version 4.6.1" +default_settings["general"]["version"] = "Version 4.7.0" default_settings["general"]["info"] = "Document your layout here" default_settings["general"]["automation"] = True default_settings["general"]["spadpopups"] = False +default_settings["general"]["buttonsize"] = 8 default_settings["canvas"] = {} default_settings["canvas"]["width"] = 1000 default_settings["canvas"]["height"] = 500 @@ -162,15 +163,17 @@ def get_general(param=None): info = settings["general"]["info"] automation = settings["general"]["automation"] spad = settings["general"]["spadpopups"] - return(filename, editmode, version, info, automation, spad) + buttonsize = settings["general"]["buttonsize"] + return(filename, editmode, version, info, automation, spad, buttonsize) -def set_general(filename:str=None, editmode:bool=None, version:str=None, info:str=None, automation:bool=None, spad:bool=None): +def set_general(filename:str=None, editmode:bool=None, version:str=None, info:str=None, automation:bool=None, spad:bool=None, buttonsize:int=None): if filename is not None: settings["general"]["filename"] = filename if editmode is not None: settings["general"]["editmode"] = editmode if version is not None: settings["general"]["version"] = version if info is not None: settings["general"]["info"] = info if automation is not None: settings["general"]["automation"] = automation if spad is not None: settings["general"]["spadpopups"] = spad + if buttonsize is not None: settings["general"]["buttonsize"] = buttonsize return() #------------------------------------------------------------------------------------ diff --git a/model_railway_signals/library/__init__.py b/model_railway_signals/library/__init__.py index 6fe03ffc..3902c25a 100644 --- a/model_railway_signals/library/__init__.py +++ b/model_railway_signals/library/__init__.py @@ -80,6 +80,10 @@ from .track_sensors import track_sensor_exists from .track_sensors import delete_track_sensor +from .text_boxes import create_text_box +from .text_boxes import text_box_exists +from .text_boxes import delete_text_box + from .pi_sprog_interface import sprog_connect from .pi_sprog_interface import sprog_disconnect from .pi_sprog_interface import service_mode_read_cv @@ -117,10 +121,10 @@ from .buttons import delete_button from .buttons import toggle_button from .buttons import enable_button +from .buttons import lock_button +from .buttons import unlock_button from .buttons import disable_button from .buttons import button_state -from .buttons import button_enabled -from .buttons import processing_complete from .file_interface import load_schematic from .file_interface import purge_loaded_state_information @@ -129,12 +133,14 @@ from .common import set_root_window from .common import shutdown from .common import configure_edit_mode +from .common import configure_button_size __all__ = [ # Public common functions 'set_root_window', 'shutdown', 'configure_edit_mode', + 'configure_button_size', # Public point types/functions 'point_type', 'create_point', @@ -154,6 +160,10 @@ 'create_track_sensor', 'delete_track_sensor', 'track_sensor_exists', + # public text box types/functions + 'create_text_box', + 'delete_text_box', + 'text_box_exists', # Public signal types/functions 'signal_type', 'signal_subtype', @@ -252,9 +262,9 @@ 'toggle_button', 'enable_button', 'disable_button', + 'lock_button', + 'unlock_button', 'button_state', - 'button_enabled', - 'processing_complete', # Public file interface functions 'save_schematic', 'load_schematic', diff --git a/model_railway_signals/library/buttons.py b/model_railway_signals/library/buttons.py index 9e6ecdc0..5a96d35e 100644 --- a/model_railway_signals/library/buttons.py +++ b/model_railway_signals/library/buttons.py @@ -4,19 +4,27 @@ # # Public types and functions: # +# button_type (use when creating buttons) +# button_type.switched +# button_type.momentary +# # create_button - Creates a button object # Mandatory Parameters: # Canvas - The Tkinter Drawing canvas on which the Button is to be displayed -# button_id:int - The ID to be used for the Button +# button_id:int - The ID to be used for the Button +# buttontype:button_type - The type of button to create # x:int, y:int - Position of the Button on the canvas (in pixels) # selected_callback - The function to call when the Button is selected (returns item_id) # deselected_callback - The function to call when the Button is deselected (returns item_id) # Optional parameters: # width:int - The width of the button in characters (default 10) -# label:int - The label for the button (default is empty string) -# tooltip:str - the default tooltip to be displayed (default is empty string) -# -# processing_complete(button_id:int) - 'activate' the button after completing the associated processing +# label:int - The label for the button (default is 'Button') +# tooltip:str - the default tooltip to be displayed (default is 'Tooltip') +# hidden:bool - Whether the Button should be 'hidden' in Run Mode (default=False) +# button_colour:str - the colour to use for the button when 'normal' (default='SeaGreen3') +# active_colour:str - the colour to use for the button when 'active' (default='SeaGreen2') +# selected_colour:str - the colour to use for the button when 'selected' (default='SeaGreen1') +# text_colour:str - the colour to use for the button text (default='black') # # button_exists(button_id:int) - returns true if the Button object 'exists' on the schematic # @@ -24,26 +32,38 @@ # # button_state(button_id:int) - get the current state of a button (returns True for Active) # -# button_enabled(button_id:int) - get the current state of a button (returns True for Enabled) -# # toggle_button(button_id:int) - toggle the state of the button # # enable_button(button_id:int) - enable the button (and revert to the standard tooltip) # # disable_button(button_id:int, tooltip:str) - disable the button (with a new toottip) # +# lock_button(button_id:int) - lock the button (to prevent it being enabled) +# +# unlock_button(button_id:int, tooltip:str) - unlock the button (to allow it to be enabled) +# # External API - classes and functions (used by the other library modules): # # configure_edit_mode(edit_mode:bool) - True for Edit Mode, False for Run Mode -# +# #--------------------------------------------------------------------------------------------- import logging +import enum import tkinter as Tk from ..editor.common import CreateToolTip from . import file_interface +from . import dcc_control + +# ------------------------------------------------------------------------- +# Public API classes (to be used by external functions) +# ------------------------------------------------------------------------- + +class button_type(enum.Enum): + switched = 1 # Toggle Button for control of DCC accessories + momentary = 2 # Momentary Button for control of DCC accessories #--------------------------------------------------------------------------------------------- # Button objects are to be added to a global dictionary when created @@ -63,17 +83,21 @@ def configure_edit_mode(edit_mode:bool): # Maintain a global flag (for creating new library objects) editing_enabled = edit_mode # Update all existing library objects (according to the current mode) - # Note that only local objects (ID is an integer) are updated for button_id in buttons: button = buttons[button_id] if editing_enabled: + # In Edit Mode - Hide the button window and display all placeholder objects in their normal + # configuration (placeholder1 is the text object and placeholder2 is the rectangle object) button["canvas"].itemconfig(button["buttonwindow"], state='hidden') button["canvas"].itemconfig(button["placeholder1"], state='normal') - button["canvas"].itemconfig(button["placeholder2"], state='normal') + button["canvas"].itemconfig(button["placeholder2"], fill=button["deselectedcolour"], width=1) else: - button["canvas"].itemconfig(button["buttonwindow"], state='normal') + # In Run Mode - If the object is configured as 'hidden' then we hide the text object but set + # the rectangle object to transparent - effectively hiding it whilst maintaining its 'presence' + if not button["hidden"]: + button["canvas"].itemconfig(button["buttonwindow"], state='normal') button["canvas"].itemconfig(button["placeholder1"], state='hidden') - button["canvas"].itemconfig(button["placeholder2"], state='hidden') + button["canvas"].itemconfig(button["placeholder2"], fill='', width=0) return() #--------------------------------------------------------------------------------------------- @@ -82,6 +106,7 @@ def configure_edit_mode(edit_mode:bool): #--------------------------------------------------------------------------------------------- def button_exists(button_id:int): + # Validate the parameters we have been given as this is a library API function if not isinstance(button_id, int): logging.error("Button "+str(button_id)+": button_exists - Button ID must be an int") button_exists = False @@ -90,115 +115,100 @@ def button_exists(button_id:int): return(button_exists) #--------------------------------------------------------------------------------------------- -# Internal callback for processing Button presses (select/deselect). This function toggles -# the state of the button and makes the external callback to trigger the required processing -# (arising from the button select/deselect event). We disable the button to prevent further -# user clicks until we are told that the required processing is complete (by the application -# making a call the 'processing_complete' function). +# Callback and API functions for processing Button presses. Action will depend on button type: +# Momentary - Pulse the button and send out send out DCC commands for ON (if configured) +# Switched - Change the state of the button and send out DCC commands for ON/OFF as required +# Note that DCC commands will only be sent out if a DCC mapping exists for the switch #--------------------------------------------------------------------------------------------- def button_event(button_id:int): - logging.info ("Button "+str(button_id)+": Button Toggled *********************************************************") - buttons[str(button_id)]["processing"] = True - update_button_appearance(button_id) + toggle_button(button_id) if buttons[str(button_id)]["selected"]: - logging.info("Button "+str(button_id)+": has been de-selected") - buttons[str(button_id)]["selected"] = False - buttons[str(button_id)]["processing"] = True - update_button_appearance(button_id) - buttons[str(button_id)]["deselectedcallback"] (button_id) - else: - logging.info("Button "+str(button_id)+": has been selected") - buttons[str(button_id)]["selected"] = True - buttons[str(button_id)]["processing"] = True - update_button_appearance(button_id) buttons[str(button_id)]["selectedcallback"] (button_id) - return () - -#--------------------------------------------------------------------------------------------- -# API function to toggle the internal state of before updating the button state. If this -# function gets called whilst processing of a previous button event is still in progress -# then we assume that processing of that event has been cancelled -#--------------------------------------------------------------------------------------------- + else: + buttons[str(button_id)]["deselectedcallback"] (button_id) + return() def toggle_button(button_id:int): global buttons + # Validate the button ID as this is an API function as well as a callback if not isinstance(button_id, int) : logging.error("Button "+str(button_id)+": toggle_button - Button ID must be an int") elif not button_exists(button_id): logging.error("Button "+str(button_id)+": toggle_button - Button ID does not exist") - elif buttons[str(button_id)]["selected"]: - logging.info("Button "+str(button_id)+": has been toggled to OFF") - buttons[str(button_id)]["selected"] = False - else: - logging.info("Button "+str(button_id)+": has been toggled to ON") - buttons[str(button_id)]["selected"] = True - buttons[str(button_id)]["processing"] = True - update_button_appearance(button_id) - return() - -#--------------------------------------------------------------------------------------------- -# API function to confirm that the processing (arising from the activation/deactivation of the -# button has been completed (and the button can therefore be unlocked to allow further clicks -#--------------------------------------------------------------------------------------------- - -def processing_complete(button_id:int): - if not isinstance(button_id, int) : - logging.error("Button "+str(button_id)+": processing_complete - Button ID must be an int") - elif not button_exists(button_id): - logging.error("Button "+str(button_id)+": processing_complete - Button ID does not exist") else: - logging.debug("Button "+str(button_id)+": processing_complete - unlocking button") - buttons[str(button_id)]["processing"] = False - update_button_appearance(button_id) + if buttons[str(button_id)]["buttontype"] == button_type.momentary: + logging.info("Button "+str(button_id)+": Button has been pressed ***************************************************") + dcc_control.update_dcc_switch(button_id, True) + elif buttons[str(button_id)]["selected"]: + logging.info("Button "+str(button_id)+": Button has been de-selected ***********************************************") + buttons[str(button_id)]["button"].config(relief="raised",bg=buttons[str(button_id)]["deselectedcolour"]) + buttons[str(button_id)]["selected"] = False + dcc_control.update_dcc_switch(button_id, False) + else: + logging.info("Button "+str(button_id)+": Button has been selected **************************************************") + buttons[str(button_id)]["button"].config(relief="sunken",bg=buttons[str(button_id)]["selectedcolour"]) + buttons[str(button_id)]["selected"] = True + dcc_control.update_dcc_switch(button_id, True) return() #--------------------------------------------------------------------------------------------- # API functions to enable or disable a Button - for the disable function, a tooltip can also -# be specified to provide the user with the reasons why the button is disabled +# be specified to provide the user with the reasons why the button is disabled. Note that a +# button can only be enabled or disabled if it is not 'Locked' by external processing. #--------------------------------------------------------------------------------------------- def enable_button(button_id:int): + global buttons + # Validate the parameters we have been given as this is a library API function if not isinstance(button_id, int) : logging.error("Button "+str(button_id)+": enable_button - Button ID must be an int") elif not button_exists(button_id): logging.error("Button "+str(button_id)+": enable_button - Button ID does not exist") - else: + elif not buttons[str(button_id)]["locked"]: + buttons[str(button_id)]["button"].config(state="normal") + buttons[str(button_id)]["tooltip"].text = buttons[str(button_id)]["tooltiptext"] buttons[str(button_id)]["enabled"] = True - update_button_appearance(button_id) return() -def disable_button(button_id:int, tooltip:str): +def disable_button(button_id:int, tooltip:str="Button Disabled"): + global buttons + # Validate the parameters we have been given as this is a library API function if not isinstance(button_id, int) : logging.error("Button "+str(button_id)+": disable_button - Button ID must be an int") elif not button_exists(button_id): logging.error("Button "+str(button_id)+": disable_button - Button ID does not exist") else: - buttons[str(button_id)]["disabledtooltiptext"] = tooltip + buttons[str(button_id)]["button"].config(state="disabled") + buttons[str(button_id)]["tooltip"].text = tooltip buttons[str(button_id)]["enabled"] = False - update_button_appearance(button_id) return() #--------------------------------------------------------------------------------------------- -# Internal function to update the appearance of the button depending on its current state +# API functions to lock / unlock a button whilst external processing is taking place. +# Whilst a button is locked, it can't be enabled via the enable_button function call. #--------------------------------------------------------------------------------------------- -def update_button_appearance(button_id:int): - # Enable or disable the button (with the appropriate tooltip) - if not buttons[str(button_id)]["enabled"] and not buttons[str(button_id)]["selected"]: - buttons[str(button_id)]["button"].config(state="disabled") - buttons[str(button_id)]["tooltip"].text = buttons[str(button_id)]["disabledtooltiptext"] - elif buttons[str(button_id)]["processing"]: - buttons[str(button_id)]["button"].config(state="disabled") - buttons[str(button_id)]["tooltip"].text = "Processing in progress" +def unlock_button(button_id:int): + global buttons + # Validate the parameters we have been given as this is a library API function + if not isinstance(button_id, int) : + logging.error("Button "+str(button_id)+": unlock_button - Button ID must be an int") + elif not button_exists(button_id): + logging.error("Button "+str(button_id)+": unlock_button - Button ID does not exist") else: - buttons[str(button_id)]["button"].config(state="normal") - buttons[str(button_id)]["tooltip"].text = buttons[str(button_id)]["enabledtooltiptext"] - # Activate or deactivate the button - if buttons[str(button_id)]["selected"]: - buttons[str(button_id)]["button"].config(relief="sunken",bg="SeaGreen1") + buttons[str(button_id)]["locked"] = False + return() + +def lock_button(button_id:int): + global buttons + # Validate the parameters we have been given as this is a library API function + if not isinstance(button_id, int) : + logging.error("Button "+str(button_id)+": lock_button - Button ID must be an int") + elif not button_exists(button_id): + logging.error("Button "+str(button_id)+": lock_button - Button ID does not exist") else: - buttons[str(button_id)]["button"].config(relief="raised",bg="SeaGreen3") + buttons[str(button_id)]["locked"] = True return() #--------------------------------------------------------------------------------------------- @@ -217,30 +227,13 @@ def button_state(button_id:int): button_state = buttons[str(button_id)]["selected"] return(button_state) -#--------------------------------------------------------------------------------------------- -# API function to get the current state of a Button (enabled or disabled) -#--------------------------------------------------------------------------------------------- - -def button_enabled(button_id:int): - # Validate the parameters we have been given as this is a library API function - if not isinstance(button_id, int) : - logging.error("Button "+str(button_id)+": button_locked - Button ID must be an int") - button_enabled = False - elif not button_exists(button_id): - logging.error("Button "+str(button_id)+": button_locked - Button ID does not exist") - button_enabled = False - else: - # Button is always enabled if selected (so it can always be de-selected) - button_enabled = buttons[str(button_id)]["enabled"] or buttons[str(button_id)]["selected"] - return(button_enabled) - #--------------------------------------------------------------------------------------------- # Public API function to create a Button object (drawing objects plus internal state) #--------------------------------------------------------------------------------------------- -def create_button (canvas, button_id:int, x:int, y:int, - selected_callback, deselected_callback, - width:int=10, label:str="", tooltip=""): +def create_button (canvas, button_id:int, buttontype:button_type, x:int, y:int, selected_callback, deselected_callback, + width:int=10, label:str="Button", tooltip="Tooltip", hidden:bool=False, button_colour:str="SeaGreen3", + active_colour:str="SeaGreen2", selected_colour:str="SeaGreen1", text_colour:str="black"): global buttons # Set a unique 'tag' to reference the tkinter drawing objects canvas_tag = "button"+str(button_id) @@ -249,56 +242,71 @@ def create_button (canvas, button_id:int, x:int, y:int, logging.error("Button "+str(button_id)+": create_button - Button ID must be an int (1-999)") elif button_exists(button_id): logging.error("Button "+str(button_id)+": create_button - Button ID already exists") + elif buttontype != button_type.switched and buttontype != button_type.momentary: + logging.error("Button "+str(button_id)+": create_button - Invalid Button Type specified") else: logging.debug("Button "+str(button_id)+": Creating Button on the Canvas") # Specify the fontsize locally fontsize = 9 - # Create the button object, callbacks and window to hold it. - button = Tk.Button(canvas, text=label, state="normal", relief="raised", width=width, disabledforeground="grey40", - font=('Courier',fontsize,"normal"), bg="SeaGreen3", activebackground="SeaGreen2", - padx=2, pady=2, command=lambda:button_event(button_id)) - button_window = canvas.create_window(x, y, window=button, tags=canvas_tag) + # Create the Button and its canvas window (for Run Mode operation). Note the Window is created + # as 'hidden' - assuming we are in edit mode - but changed later if we are in Run Mode + button = Tk.Button(canvas, text=label, state="normal", relief="raised", width=width, foreground="grey40", + font=('Courier',fontsize,"normal"), bg=button_colour, activebackground=active_colour, padx=2, pady=2, + activeforeground=text_colour, fg=text_colour, command=lambda:button_event(button_id)) + button_window = canvas.create_window(x, y, window=button, tags=canvas_tag, state="hidden") # Create and store a tool-tip for the button tooltip_object = CreateToolTip(button, text=tooltip) tooltip_object.waittime = 200 # miliseconds - tooltip_object.wraplength = 400 #pixels + tooltip_object.wraplength = 400 # pixels # Create the 'placeholder' for the button to display in Edit Mode (so it an be selected/moved) # Note that the 'width' parameter is the maximum width in pixels before the text starts to wrap. To set the # minimum width we need to specify an initial 'text' value that contains the required number of characters. placeholder1 = canvas.create_text(x, y, text=label.zfill(width), width=width*fontsize, - font=('Courier',fontsize,"normal"), fill="black", tags=canvas_tag) + font=('Courier',fontsize,"normal"), fill=text_colour, tags=canvas_tag) bbox = canvas.bbox(placeholder1) placeholder2 = canvas.create_rectangle(bbox[0]-4, bbox[1]-4, bbox[2]+4, bbox[3]+2, - tags=canvas_tag, fill="SeaGreen3") + tags=canvas_tag, fill=button_colour, width=1) canvas.tag_raise(placeholder1, placeholder2) # Now we have created the textbox at the right width, update it to display the 'proper' label canvas.itemconfig(placeholder1, text=label) - # Display either the button or button 'placeholder' depending on the mode - if editing_enabled: - canvas.itemconfig(button_window, state='hidden') - else: + # Hide the placeholder objects if we are in Run Mode. Note we can't just make the rectangle + # 'hidden' as the canvas.bbox function (used by the editor to get the selection area) would + # just return zero values when subsequently queried (after the return from this function) and + # so the object would be unselectable when the user toggles back to edit mode. We therefore + # make the rectangle transparent (fill="") to effectively make it hidden. Note we also hide + # the button window if the Button itself needs to be 'hidden' in Run Mode. + if not editing_enabled: + if hidden: canvas.itemconfig(button_window, state='hidden') + else: canvas.itemconfig(button_window, state='normal') canvas.itemconfig(placeholder1, state='hidden') - canvas.itemconfig(placeholder2, state='hidden') + canvas.itemconfig(placeholder2, fill='', width=0) # Compile a dictionary of everything we need to track buttons[str(button_id)] = {} buttons[str(button_id)]["canvas"] = canvas # Tkinter canvas object buttons[str(button_id)]["selectedcallback"] = selected_callback # External callback to make buttons[str(button_id)]["deselectedcallback"] = deselected_callback # External callback to make buttons[str(button_id)]["selected"] = False # Current state (selected or de-selected) + buttons[str(button_id)]["locked"] = False # The master lock for the button + buttons[str(button_id)]["hidden"] = hidden # True if the button should be hidden in run mode + buttons[str(button_id)]["buttontype"] = buttontype # Type of the button (route, switch or button) buttons[str(button_id)]["button"] = button # Tkinter button object (for run mode) buttons[str(button_id)]["buttonwindow"] = button_window # Tkinter drawing object (for run mode) buttons[str(button_id)]["placeholder1"] = placeholder1 # Tkinter drawing object (for edit mode) buttons[str(button_id)]["placeholder2"] = placeholder2 # Tkinter drawing object (for edit mode) - buttons[str(button_id)]["enabledtooltiptext"] = tooltip # The default tooltip text to display - buttons[str(button_id)]["disabledtooltiptext"] = tooltip # The tooltip text to display when disabled + buttons[str(button_id)]["tooltiptext"] = tooltip # The default tooltip text to display buttons[str(button_id)]["tooltip"] = tooltip_object # Reference to the Tooltip class instance - buttons[str(button_id)]["enabled"] = False # Flag to indicate if the button is enabled/disabled - buttons[str(button_id)]["processing"] = False # True between button press and processing complete events + buttons[str(button_id)]["deselectedcolour"] = button_colour # button colour in its normal/unselected state + buttons[str(button_id)]["selectedcolour"] = selected_colour # button colour in its selected state buttons[str(button_id)]["tags"] = canvas_tag # Canvas Tag for ALL drawing objects # Get the initial state for the button (if layout state has been successfully loaded) loaded_state = file_interface.get_initial_item_state("buttons",button_id) # Toggle the button to 'Selected' if required - if loaded_state["selected"]: toggle_button(button_id) + if loaded_state["selected"]: + buttons[str(button_id)]["selected"] = True + buttons[str(button_id)]["button"].config(relief="sunken",bg=buttons[str(button_id)]["selectedcolour"]) + # Send out any DCC commands associated with the initial state of the button + # Note that commands will only be sent out if a mapping exists + dcc_control.update_dcc_switch(button_id, buttons[str(button_id)]["selected"]) return(canvas_tag) #--------------------------------------------------------------------------------------------- diff --git a/model_railway_signals/library/common.py b/model_railway_signals/library/common.py index 446344eb..7937434a 100644 --- a/model_railway_signals/library/common.py +++ b/model_railway_signals/library/common.py @@ -11,8 +11,14 @@ # # configure_edit_mode(edit_mode:bool) - True for Edit Mode, False for Run Mode # +# configure_button_size(button_size:int) - Specify the Font size for layout control buttons) +# # External API - classes and functions (used by the other library modules): # +# rotate_point(ox,oy,px,py,angle) - Rotate a point (px,py) around the origin (ox,oy) +# +# rotate_line(ox,oy,px1,py1,px2,py2,angle) - Rotate a line (px1,py1,px3,py2) around the origin (ox,oy) +# # execute_function_in_tkinter_thread(callback_function) - Will 'pass' the function # into the main tkinter thread via a queue (and tkinter event) and then execute # the function - used for MQTT and GPIO events to keep everything threadsafe. @@ -22,11 +28,13 @@ import math import logging import time +import queue from . import mqtt_interface from . import pi_sprog_interface from . import track_sensors from . import track_sections +from . import text_boxes from . import buttons # ------------------------------------------------------------------------- @@ -41,20 +49,27 @@ ypadding = -1 # Used by the Signals and Points modules bgraised = "grey85" # Used by the Signals and Points modules bgsunken = "white" # Used by the Signals and Points modules - # Global Variable to hold a reference to the TkInter Root Window root_window = None # Global variable to signal (to other modules) that application is closing shutdown_initiated = False +# Event queue for passing "commands" back into the main tkinter thread +event_queue = queue.Queue() -#------------------------------------------------------------------------------------ -# The behavior/appearance of the some library objects may change in Edit Mode -#------------------------------------------------------------------------------------ +#------------------------------------------------------------------------- +# Function to set the tkinter "root" window reference as this is used to +# schedule callback events in the main tkinter event loop using the 'after' +# method and also for feeding custom callback functions into the main tkinter +# thread. We do this as all the information out there on the internet concludes +# tkinter isn't fully thread safe and so all manipulation of tkinter drawing +# objects should be done from within the main tkinter thread. +#------------------------------------------------------------------------- -def configure_edit_mode(edit_mode:bool): - track_sensors.configure_edit_mode(edit_mode) - track_sections.configure_edit_mode(edit_mode) - buttons.configure_edit_mode(edit_mode) +def set_root_window(root): + global root_window + root_window = root + # bind the tkinter event for handling events raised in external threads + root_window.bind("<>", handle_callback_in_tkinter_thread) return() #------------------------------------------------------------------------- @@ -90,18 +105,24 @@ def shutdown(): logging.warning ("Timeout waiting for scheduled tkinter events to complete - Exiting anyway") return() -#------------------------------------------------------------------------- -# Function to set the tkinter "root" window reference as this is used to -# schedule callback events in the main tkinter event loop using the 'after' -# method and also for feeding custom callback functions into the main tkinter -# thread. We do this as all the information out there on the internet concludes -# tkinter isn't fully thread safe and so all manipulation of tkinter drawing -# objects should be done from within the main tkinter thread. -#------------------------------------------------------------------------- +#------------------------------------------------------------------------------------ +# The behavior/appearance of the some library objects may change in Edit Mode +#------------------------------------------------------------------------------------ -def set_root_window(root): - global root_window - root_window = root +def configure_edit_mode(edit_mode:bool): + track_sensors.configure_edit_mode(edit_mode) + track_sections.configure_edit_mode(edit_mode) + text_boxes.configure_edit_mode(edit_mode) + buttons.configure_edit_mode(edit_mode) + return() + +#------------------------------------------------------------------------------------ +# Function to configure the font size for layout control buttons (points/signals) +#------------------------------------------------------------------------------------ + +def configure_button_size(button_size:int): + global fontsize + fontsize = button_size return() # ------------------------------------------------------------------------- @@ -120,5 +141,38 @@ def rotate_line(ox,oy,px1,py1,px2,py2,angle): end_point = rotate_point(ox,oy,px2,py2,angle) return (start_point, end_point) +#------------------------------------------------------------------------- +# Functions to allow custom callback functions to be passed in (from an external thread) +# and then handled in the main Tkinter thread (to keep everything threadsafe). We use +# the tkinter event_generate method to generate a custom event in the main event loop +# in conjunction with a (threadsafe) queue to pass the callback function. We don't use +# the tkinter root.after method as we don't believe that this is threadsafe. +# Use as follows: execute_function_in_tkinter_thread (lambda: my_function(arg1,arg2...)) +#------------------------------------------------------------------------- + +def handle_callback_in_tkinter_thread(*args): + while not event_queue.empty(): + callback = event_queue.get(False) + callback() + return() + +def execute_function_in_tkinter_thread(callback_function): + if root_window is not None: + event_queue.put(callback_function) + # When loading a layout file on startup, there were a number of possible edge cases that could cause + # this function to be called before root.mainloop had been called (e.g. publish MQTT heartbeat messages + # or receive other MQTT/GPIO events). This could cause exceptions (i've seen them when running the code + # on the Pi-Zero). This has been mitigated in the main 'editor.py' module by using the root.after method + # to shedule loading the layout file after the tkinter main loop has been started. The exception handling + # code here is 'belt and braces' defensive programming so we don't inadvertantly kill the calling thread. + try: + root_window.event_generate("<>", when="tail") + except Exception as exception: + logging.error("execute_function_in_tkinter_thread - Exception when calling root.event_generate:") + logging.error(str(exception)) + else: + logging.error("execute_function_in_tkinter_thread - cannot execute callback function as root window is undefined") + return() + ################################################################################################## diff --git a/model_railway_signals/library/dcc_control.py b/model_railway_signals/library/dcc_control.py index ee8ff5fe..a5e84f97 100644 --- a/model_railway_signals/library/dcc_control.py +++ b/model_railway_signals/library/dcc_control.py @@ -76,9 +76,17 @@ # Optional Parameters: # state_reversed:bool - Set to True to reverse the DCC logic (default = false) # +# map_dcc_switch - Generate DCC mappings for a DCC accessory +# Mandatory Parameters: +# switch_id:int - The ID for the point to create a DCC mapping for +# proceed[[add:int,state:bool],] - List of DCC Commands for "Green" +# danger [[add:int,state:bool],] - List of DCC Commands for "Red" +# # delete_point_mapping(point_id:int) - Delete a DCC mapping (called when the Point is deleted) # # delete_signal_mapping(sig_id:int) - Delete a DCC mapping (called when the Signal is deleted) +# +# delete_switch_mapping(switch_id:int) - Delete a DCC mapping (called when the Switch is deleted) # # The following API functions are for configuring the pub/sub of DCC command feeds. The functions are called # by the editor on 'Apply' of the MQTT settings. First, 'reset_dcc_mqtt_configuration' is called to clear down @@ -97,6 +105,8 @@ # # update_dcc_point(point_id:int,state:bool) - Called on state change of a point # +# update_dcc_switch(switch_id:int,state:bool) - Called on state change of a switch +# # update_dcc_signal_aspects(sig_id:int, sig_state:signals.signal_state_type) - called on change of a Colour Light Signal # # update_dcc_signal_element(sig_id:int, state:bool, element:str)- called on update of a Semaphore Signal @@ -183,25 +193,26 @@ class mapping_type(enum.Enum): SEMAPHORE = 1 # One to one mapping of single DCC Addresses to each signal element COLOUR_LIGHT = 2 # Each aspect is mapped to a sequence of one or more DCC Addresses/states -# The DCC commands for Signals and Points are held in global dictionaries where the dictionary +# The DCC commands for Signals, Points and switches are held in global dicts where the dict # 'key' is the ID of the signal or point. Each entry is another dictionary, with each element # holding the DCC commands (or sequences) needed to put the signal/point into the desired state. # Note that the mappings are completely different for Colour Light or Semaphore signals, so the # common 'mapping_type' value is used by the software to differentiate between the two types dcc_signal_mappings:dict = {} dcc_point_mappings:dict = {} +dcc_switch_mappings:dict = {} # Define the Flag to control whether DCC Commands are published to the MQTT Broker publish_dcc_commands_to_mqtt_broker:bool = False -# List of DCC Mappings - The key is the address, with each element a list of [item,item_id] +# List of DCC Mappings - The key is the address, with each element a list of [item_type, item_id] # Note that we use the DCC Address as an INTEGER for the key - so we can sort on the key -# Item - either "Signal" or "Point" to identify the type of item the address is mapped to -# Item ID - the ID of the Signal or Point that the DCC address is mapped to +# Item_type - either "Signal", "Point" or "Switch" to identify the item the address is mapped to +# Item id - the ID of the Signal or Point that the DCC address is mapped to dcc_address_mappings:dict = {} #---------------------------------------------------------------------------------------------------- -# API function to return a dictionary of all DCC Address mappings (to signals/points) +# API function to return a dictionary of all DCC Address mappings (to signals/points/switches) #---------------------------------------------------------------------------------------------------- def get_dcc_address_mappings(): @@ -220,20 +231,107 @@ def dcc_address_mapping(dcc_address:int): else: dcc_address_mapping = dcc_address_mappings[dcc_address] return(dcc_address_mapping) - + #---------------------------------------------------------------------------------------------------- -# Internal function to test if a DCC mapping already exists for a signal +# Internal functions to test if DCC mappings already exists for a signal, point or switch #---------------------------------------------------------------------------------------------------- def sig_mapped(sig_id:int): return (str(sig_id) in dcc_signal_mappings.keys()) +def point_mapped(point_id:int): + return (str(point_id) in dcc_point_mappings.keys()) + +def switch_mapped(switch_id:int): + return (str(switch_id) in dcc_switch_mappings.keys()) + #---------------------------------------------------------------------------------------------------- -# Internal function to test if a DCC mapping already exists for a point +# Internal functions to add/remove DCC addresses/commands to the dcc_address_mappings dictionary +# We only add DCC addresses greater than zero (zero meens no mapping) if they don't already exist +# in the list to avoud duplicates. DCC commands are specified as [address:int, state:bool] +# Note that we force the dict key to be an integer so the dict can be sorted on keys. #---------------------------------------------------------------------------------------------------- -def point_mapped(point_id:int): - return (str(point_id) in dcc_point_mappings.keys()) +def add_dcc_address_to_dcc_address_mappings(item_type:str, item_id:int, address:int): + global dcc_address_mappings + if address > 0 and address not in dcc_address_mappings.keys(): + dcc_address_mappings[int(address)] = [item_type, item_id] + return() + +def add_dcc_addresses_to_dcc_address_mappings(item_type:str, item_id:int, addresses:[int,]): + for address in addresses: + add_dcc_address_to_dcc_address_mappings(item_type, item_id, address) + return() + +def add_dcc_commands_to_dcc_address_mappings(item_type:str, item_id:int, commands:[[int,bool],]): + for command in commands: + add_dcc_address_to_dcc_address_mappings(item_type, item_id, command[0]) + return() + +def remove_dcc_address_from_dcc_address_mappings(address:int): + global dcc_address_mappings + if address in dcc_address_mappings.keys(): + del dcc_address_mappings[address] + return() + +def remove_dcc_commands_from_dcc_address_mappings(commands:[[int,bool],]): + for command in commands: + remove_dcc_address_from_dcc_address_mappings(command[0]) + return() + +#---------------------------------------------------------------------------------------------------- +# Internal Functions to Validate DCC address and DCC Commands +#---------------------------------------------------------------------------------------------------- + +def dcc_address_valid(func_text:str, item_text:str, item_id:int, address:int): + address_valid = True + if not isinstance(address,int) or address < 0 or address > 2047: + logging.error ("DCC Control: "+func_text+" - "+item_text+" "+str(item_id)+" - Invalid DCC address: "+str(address)) + address_valid = False + elif dcc_address_mapping(address) is not None: + # If there is a mapping then a list of [item_type, item_id will be returned from the dcc_address_mapping function + logging.error ("DCC Control: "+func_text+" - "+item_text+" "+str(item_id)+" - DCC Address "+str(address)+ + " is already assigned to "+dcc_address_mapping(address)[0]+" "+str(dcc_address_mapping(address)[1])) + address_valid = False + return(address_valid) + +def dcc_addresses_valid(func_text:str, item_text:str, item_id:int, addresses:[int,]): + addresses_valid = True + for address in addresses: + if not dcc_address_valid(func_text, item_text, item_id, address): + addresses_valid = False + return(addresses_valid) + +def dcc_command_valid(func_text:str, item_text:str, item_id:int, command:[int,bool]): + command_valid = True + if not isinstance(command,list) or not len(command) == 2: + logging.error ("DCC Control: "+func_text+" - "+item_text+" "+str(item_id)+" - Invalid DCC command: "+str(command)) + command_valid = False + elif not isinstance(command[1],bool): + logging.error ("DCC Control: "+func_text+" - "+item_text+" "+str(item_id)+" - Invalid DCC state: " +str(command)) + command_valid = False + elif not dcc_address_valid(func_text, item_text, item_id, command[0]): + command_valid = False + return(command_valid) + +def dcc_commands_valid(func_text:str, item_text:str, item_id:int, commands:[[int,bool],]): + commands_valid = True + for command in commands: + if not dcc_command_valid(func_text, item_text, item_id, command): + commands_valid = False + return(commands_valid) + +#---------------------------------------------------------------------------------------------------- +# Internal helper function to get a list of DCC Commands for the theatre route indicator. The input +# mapping comprises a list of routes, with each route comprising: [route_character, list_of_commands] +# where each dcc_command entry is a list comprising [dcc_address, dcc_state] +#---------------------------------------------------------------------------------------------------- + +def get_list_of_theatre_dcc_commands(theatre_mapping:[[str, [[int, bool],]],]): + list_of_commands=[] + for theatre_state in theatre_mapping: + list_of_commands = list_of_commands + theatre_state[1] + return(list_of_commands) #---------------------------------------------------------------------------------------------------- # Function to "map" a Colour Light signal object to a series of DCC addresses/command sequences @@ -257,47 +355,28 @@ def map_dcc_signal(sig_id:int, THEATRE = [["#", [[0,False],]],], subsidary:int=0): global dcc_signal_mappings - global dcc_address_mappings # Do some basic validation on the parameters we have been given if not isinstance(sig_id,int) or sig_id < 1: logging.error ("DCC Control: map_dcc_signal - Signal "+str(sig_id)+" - Signal ID must be a positive integer") elif sig_mapped(sig_id): logging.error ("DCC Control: map_dcc_signal - Signal "+str(sig_id)+" - already has a DCC mapping") else: - # Create a list of DCC addresses [address,state] to validate - addresses = ( danger + proceed + caution + prelim_caution + flash_caution + - flash_prelim_caution + LH1 + LH2 + RH1 + RH2 + MAIN + NONE ) - # Add the Theatre route indicator addresses - these are the form [char,[[address,state],]] - for theatre_state in THEATRE: - addresses = addresses + theatre_state[1] - # Add the subsidary signal DCC address into the list (this is a single DCC address) - addresses = addresses + [[subsidary,True]] - # Validate the DCC Addresses we have been given are either 0 (i.e. don't send anything) or - # within the valid DCC accessory address range of 1 and 2047. - addresses_valid = True - for entry in addresses: - if not isinstance(entry,list) or not len(entry) == 2: - logging.error ("DCC Control: map_dcc_signal - Signal "+str(sig_id)+" - Invalid DCC command: "+str(entry)) - addresses_valid = False - elif not isinstance(entry[1],bool): - logging.error ("DCC Control: map_dcc_signal - Signal "+str(sig_id)+" - Invalid DCC state: " +str(entry[1])) - addresses_valid = False - elif not isinstance(entry[0],int) or entry[0] < 0 or entry[0] > 2047: - logging.error ("DCC Control: map_dcc_signal - Signal "+str(sig_id)+" - Invalid DCC address: "+str(entry[0])) - addresses_valid = False - elif dcc_address_mapping(entry[0]) is not None: - logging.error ("DCC Control: map_dcc_signal - Signal "+str(sig_id)+" - DCC Address "+str(entry[0])+ - " is already assigned to "+dcc_address_mapping(entry[0])[0]+" "+str(dcc_address_mapping(entry[0])[1])) - addresses_valid = False - # We now know if all the DCC addresses we have been given are valid - if addresses_valid: + # Create a list of DCC commands [address,state] to validate (aspects and feathers) + list_of_commands = ( danger + proceed + caution + prelim_caution + flash_caution + + flash_prelim_caution + LH1 + LH2 + RH1 + RH2 + MAIN + NONE ) + # Add the DCC commands for the Theatre route indicator and subsidary signal (single address) + list_of_commands = list_of_commands + get_list_of_theatre_dcc_commands(THEATRE) + list_of_commands = list_of_commands + [[subsidary, True]] + # Validate all DCC commands and adddresses for the Colour Light Signal DCC Mapping + # If all DCC commands are valid then we can create the DCC Mapping for the signal + if dcc_commands_valid("map_dcc_signal", "Signal", sig_id, list_of_commands): logging.debug ("DCC Control - Creating DCC Address mapping for Colour Light Signal "+str(sig_id)) # Create the DCC Mapping entry for the signal new_dcc_mapping = { - "mapping_type" : mapping_type.COLOUR_LIGHT, # Common to Colour_Light & Semaphore Mappings - "auto_route_inhibit" : auto_route_inhibit, # Common to Colour_Light & Semaphore Mappings - "main_subsidary" : subsidary, # Common to Colour_Light & Semaphore Mappings - "THEATRE" : THEATRE, # Common to Colour_Light & Semaphore Mappings + "mapping_type" : mapping_type.COLOUR_LIGHT, # Common to Colour_Light & Semaphore Mappings + "auto_route_inhibit" : auto_route_inhibit, # Common to Colour_Light & Semaphore Mappings + "main_subsidary" : subsidary, # Common to Colour_Light & Semaphore Mappings + "THEATRE" : THEATRE, # Common to Colour_Light & Semaphore Mappings str(signals.signal_state_type.DANGER) : danger, # Specific to Colour_Light Mappings str(signals.signal_state_type.PROCEED) : proceed, # Specific to Colour_Light Mappings str(signals.signal_state_type.CAUTION) : caution, # Specific to Colour_Light Mappings @@ -312,10 +391,8 @@ def map_dcc_signal(sig_id:int, str(signals.route_type.MAIN) : MAIN, # Specific to Colour_Light Mappings str(signals.route_type.NONE) : NONE } # Specific to Colour_Light Mappings dcc_signal_mappings[str(sig_id)] = new_dcc_mapping - # Update the DCC mappings dictionary (note the key is an INTEGER) - for entry in addresses: - if entry[0] > 0 and entry[0] not in dcc_address_mappings.keys(): - dcc_address_mappings[int(entry[0])] = ["Signal",sig_id] + # Update the DCC mappings dictionary with all addresses used by the signal + add_dcc_commands_to_dcc_address_mappings("Signal", sig_id, list_of_commands) return() #---------------------------------------------------------------------------------------------------- @@ -338,50 +415,23 @@ def map_semaphore_signal(sig_id:int, rh2_subsidary:int = 0, THEATRE = [["#", [[0,False],]],]): global dcc_signal_mappings - global dcc_address_mappings # Do some basic validation on the parameters we have been given if not isinstance(sig_id,int) or sig_id < 1: logging.error ("DCC Control: map_semaphore_signal - Signal "+str(sig_id)+" - Signal ID must be a positive integer") elif sig_mapped(sig_id): logging.error ("DCC Control: map_semaphore_signal - Signal "+str(sig_id)+" - already has a DCC Address mapping") else: - # Create a list of DCC addresses to validate - addresses = [main_signal,main_subsidary,lh1_signal,lh1_subsidary,rh1_signal,rh1_subsidary, - lh2_signal,lh2_subsidary,rh2_signal,rh2_subsidary] - # Validate the DCC Addresses we have been given are either 0 (i.e. don't send anything) or - # within the valid DCC accessory address range of 1 and 2047. - addresses_valid = True - for entry in addresses: - if not isinstance(entry,int) or entry < 0 or entry > 2047: - logging.error ("DCC Control: map_semaphore_signal - Signal "+str(sig_id)+" - Invalid DCC address: "+str(entry)) - addresses_valid = False - elif dcc_address_mapping(entry) is not None: - logging.error ("DCC Control: map_semaphore_signal - Signal "+str(sig_id)+" - DCC Address "+str(entry)+ - " is already assigned to "+dcc_address_mapping(entry)[0]+" "+str(dcc_address_mapping(entry)[1])) - addresses_valid = False - # Validate the Theatre route indicator addresses - these are the form [char,[address,state] - for theatre_state in THEATRE: - for entry in theatre_state[1]: - if not isinstance(entry,list) or not len(entry) == 2: - logging.error ("DCC Control: map_semaphore_signal - Signal "+str(sig_id)+" - Invalid DCC command: "+str(entry)) - addresses_valid = False - elif not isinstance(entry[1],bool): - logging.error ("DCC Control: map_semaphore_signal - Signal "+str(sig_id)+" - Invalid DCC state: "+str(entry[1])) - addresses_valid = False - elif not isinstance(entry[0],int) or entry[0] < 0 or entry[0] > 2047: - logging.error ("DCC Control: map_semaphore_signal - Signal "+str(sig_id)+" - Invalid DCC address "+str(entry[0])) - addresses_valid = False - elif dcc_address_mapping(entry[0]) is not None: - logging.error ("DCC Control: map_semaphore_signal - Signal "+str(sig_id)+" - DCC Address "+str(entry[0])+ - " is already assigned to "+dcc_address_mapping(entry[0])[0]+" "+str(dcc_address_mapping(entry[0])[1])) - addresses_valid = False - else: - # Add to the list of addresses (so we can add to the mappings later on) - addresses.append(entry[0]) - # We now know if all the DCC addresses we have been given are valid - if addresses_valid: + # Validate all basic DCC adddresses for the Semaphore Signal DCC Mapping + list_of_addresses = [main_signal, main_subsidary, lh1_signal, lh1_subsidary, rh1_signal, + rh1_subsidary, lh2_signal, lh2_subsidary, rh2_signal, rh2_subsidary] + addresses_valid = dcc_addresses_valid("map_semaphore_signal", "Signal", sig_id, list_of_addresses) + # Validate the DCC commands for the Theatre route indicator + list_of_commands = get_list_of_theatre_dcc_commands(THEATRE) + commands_valid = dcc_commands_valid("map_semaphore_signal", "Signal", sig_id, list_of_commands) + # If all individual DCC addresses and DCC commands are valid then we can create the Mapping + if addresses_valid and commands_valid: logging.debug("Signal "+str(sig_id)+": Creating DCC Address mapping for a Semaphore Signal") - # Create the DCC Mapping entry for the signal. + # Create the DCC Mapping entry for the Semaphore signal. new_dcc_mapping = { "mapping_type" : mapping_type.SEMAPHORE, # Common to Colour_Light & Semaphore Mappings "auto_route_inhibit" : False, # Common to Colour_Light & Semaphore Mappings @@ -397,10 +447,9 @@ def map_semaphore_signal(sig_id:int, "rh2_signal" : rh2_signal, # Specific to Semaphore Signal Mappings "rh2_subsidary" : rh2_subsidary } # Finally save the DCC mapping into the dictionary of mappings dcc_signal_mappings[str(sig_id)] = new_dcc_mapping - # Update the DCC mappings dictionary (note the key is an INTEGER) - for entry in addresses: - if entry > 0 and entry not in dcc_address_mappings.keys(): - dcc_address_mappings[int(entry)] = ["Signal",sig_id] + # Update the DCC mappings dictionary with all addresses used by the signal + add_dcc_commands_to_dcc_address_mappings("Signal", sig_id, list_of_commands) + add_dcc_addresses_to_dcc_address_mappings("Signal", sig_id, list_of_addresses) return() #---------------------------------------------------------------------------------------------------- @@ -411,27 +460,48 @@ def map_semaphore_signal(sig_id:int, #---------------------------------------------------------------------------------------------------- def map_dcc_point(point_id:int, address:int, state_reversed:bool=False): + global dcc_point_mappings # Do some basic validation on the parameters we have been given if not isinstance(point_id,int) or point_id < 1: logging.error ("DCC Control: map_dcc_point - Point "+str(point_id)+" - Point ID must be a positive integer") elif point_mapped(point_id): logging.error ("DCC Control: map_dcc_point - Point "+str(point_id)+" - already has a DCC Address mapping") - elif not isinstance(address,int) or address < 0 or address > 2047: - logging.error ("DCC Control: map_dcc_point - Point "+str(point_id)+" - Invalid DCC address "+str(address)) - elif not isinstance(state_reversed,bool): - logging.error ("DCC Control: map_dcc_point - Point "+str(point_id)+" - Invalid state_reversed flag") - elif dcc_address_mapping(address) is not None: - logging.error ("DCC Control: map_dcc_point - Point "+str(point_id)+" - DCC Address "+str(address)+ - " is already assigned to "+dcc_address_mapping(address)[0]+" "+str(dcc_address_mapping(address)[1])) - else: + elif dcc_address_valid("map_dcc_point", "Point", point_id, address): logging.debug("Point "+str(point_id)+": Creating DCC Address mapping for Point") # Create the DCC Mapping entry for the point new_dcc_mapping = { "address" : address, "reversed" : state_reversed } dcc_point_mappings[str(point_id)] = new_dcc_mapping - # Update the DCC mappings dictionary (note the key is an INTEGER) - if address > 0: dcc_address_mappings[int(address)] = ["Point",point_id] + # Update the DCC mappings dictionary with the address used by the point + add_dcc_address_to_dcc_address_mappings("Point", point_id, address) + return() + +#---------------------------------------------------------------------------------------------------- +# Function to "map" a DCC Switch object to a series of DCC addresses/command sequences +# The variable length command lists contain valid DCC commands with no 'blanks' (address=zero) +#---------------------------------------------------------------------------------------------------- + +def map_dcc_switch(switch_id:int, on_commands:[[int,bool],], off_commands:[[int,bool],]): + global dcc_switch_mappings + global dcc_address_mappings + # Do some basic validation on the parameters we have been given + if not isinstance(switch_id,int) or switch_id < 1: + logging.error ("DCC Control: map_dcc_switch - Switch "+str(switch_id)+" - Switch ID must be a positive integer") + elif switch_mapped(switch_id): + logging.error ("DCC Control: map_dcc_switch - Switch "+str(switch_id)+" - already has a DCC mapping") + else: + # Create a list of DCC commands [address,state] to validate (on and off sequences) + list_of_commands = on_commands + on_commands + if dcc_commands_valid("map_dcc_switch", "Switch", switch_id, list_of_commands): + logging.debug ("DCC Control - Creating DCC Address mapping for DCC Switch "+str(switch_id)) + # Create the DCC Mapping entry for the signal + new_dcc_mapping = { + "oncommands" : on_commands, + "offcommands": off_commands } + dcc_switch_mappings[str(switch_id)] = new_dcc_mapping + # Update the DCC mappings dictionary with all addresses used by the switch + add_dcc_commands_to_dcc_address_mappings("Switch", switch_id, list_of_commands) return() #---------------------------------------------------------------------------------------------------- @@ -440,7 +510,6 @@ def map_dcc_point(point_id:int, address:int, state_reversed:bool=False): def update_dcc_point(point_id:int, state:bool): if point_mapped(point_id): - logging.debug ("Point "+str(point_id)+": Looking up DCC commands to switch point") dcc_mapping = dcc_point_mappings[str(point_id)] if dcc_mapping["reversed"]: state = not state if dcc_mapping["address"] > 0: @@ -451,6 +520,24 @@ def update_dcc_point(point_id:int, state:bool): publish_accessory_short_event(dcc_mapping["address"],state) return() +#---------------------------------------------------------------------------------------------------- +# Function to send the appropriate DCC commands to set the state of a DCC accessory. +# The variable length command lists contain valid DCC commands with no 'blanks' (address=zero) +#---------------------------------------------------------------------------------------------------- + +def update_dcc_switch(switch_id:int, state:bool): + if switch_mapped(switch_id): + if state: commands = dcc_switch_mappings[str(switch_id)]["oncommands"] + else: commands = dcc_switch_mappings[str(switch_id)]["offcommands"] + for entry in commands: + # Send the DCC commands to change the state via the serial port to the Pi-Sprog. + # Note that the commands will only be sent if the pi-sprog interface is configured + pi_sprog_interface.send_accessory_short_event(entry[0],entry[1]) + # Publish the DCC commands to a remote pi-sprog "node" via an external MQTT broker. + # Commands will only be published if networking is configured and publishing is enabled + publish_accessory_short_event(entry[0],entry[1]) + return() + #---------------------------------------------------------------------------------------------------- # Function to send the appropriate DCC commands to set the state of a DCC Colour Light # Signal. The commands to be sent will depend on the displayed aspect of the signal. @@ -464,7 +551,6 @@ def update_dcc_signal_aspects(sig_id:int, sig_state:signals.signal_state_type): if dcc_mapping["mapping_type"] != mapping_type.COLOUR_LIGHT: logging.error ("Signal "+str(sig_id)+": Incorrect DCC Mapping Type for signal - Expecting a Colour Light signal") else: - logging.debug ("Signal "+str(sig_id)+": Looking up DCC commands to change main signal aspect") for entry in dcc_mapping[str(sig_state)]: if entry[0] > 0: # Send the DCC commands to change the state via the serial port to the Pi-Sprog. @@ -490,7 +576,6 @@ def update_dcc_signal_element(sig_id:int, state:bool, element:str="main_subsidar if element != "main_subsidary" and dcc_mapping["mapping_type"] != mapping_type.SEMAPHORE: logging.error ("Signal "+str(sig_id)+": Incorrect DCC Mapping Type for signal - Expecting a Semaphore signal") else: - logging.debug ("Signal "+str(sig_id)+": Looking up DCC commands to change \'"+element+"\' ") if dcc_mapping[element] > 0: # Send the DCC commands to change the state via the serial port to the Pi-Sprog. # Note that the commands will only be sent if the pi-sprog interface is configured @@ -527,7 +612,6 @@ def update_dcc_signal_route(sig_id:int, route:signals.route_type, if ( (dcc_mapping["auto_route_inhibit"] and not signal_change) or (not dcc_mapping["auto_route_inhibit"] and signal_change) or (not sig_at_danger and not signal_change) ): - logging.debug ("Signal "+str(sig_id)+": Looking up DCC commands to change route display") for entry in dcc_mapping[str(route)]: if entry[0] > 0: # Send the DCC commands to change the state via the serial port to the Pi-Sprog. @@ -562,7 +646,6 @@ def update_dcc_signal_theatre(sig_id:int, character_to_display:str, if ( (dcc_mapping["auto_route_inhibit"] and not signal_change) or (not dcc_mapping["auto_route_inhibit"] and signal_change) or (not sig_at_danger and not signal_change) ): - logging.debug ("Signal "+str(sig_id)+": Looking up DCC commands to change Theatre display") # Send the DCC commands to change the state if required for entry in dcc_mapping["THEATRE"]: if entry[0] == character_to_display: @@ -621,22 +704,39 @@ def publish_accessory_short_event(address:int,active:bool): def delete_point_mapping(point_id:int): global dcc_point_mappings - global dcc_address_mappings if not isinstance(point_id, int): logging.error("DCC Control: delete_point_mapping - Point "+str(point_id)+" - Point ID must be an integer") elif not point_mapped(point_id): logging.error("DCC Control: delete_point_mapping - Point "+str(point_id)+" - DCC Mapping does not exist") else: logging.debug("Point "+str(point_id)+": Deleting DCC Address mapping for Point") - # Retrieve the DCC mapping address for the Point - dcc_address = dcc_point_mappings[str(point_id)]["address"] - # Remove the DCC address from the dcc_address_mappings dictionary (note the key is an INTEGER) - if dcc_address in dcc_address_mappings.keys(): - del dcc_address_mappings[int(dcc_address)] + # Remove the DCC address from the dcc_address_mappings dictionary + remove_dcc_address_from_dcc_address_mappings(dcc_point_mappings[str(point_id)]["address"]) # Now delete the point mapping from the dcc_point_mappings dictionary del dcc_point_mappings[str(point_id)] return() +#---------------------------------------------------------------------------------------------------- +# API function for deleting a DCC Switch mapping and removing the DCC address +# associated with the point from the dcc_address_mappings. This is used by the +# schematic editor for deleting existing DCC mappings (before creating new ones) +#---------------------------------------------------------------------------------------------------- + +def delete_switch_mapping(switch_id:int): + global dcc_switch_mappings + if not isinstance(switch_id, int): + logging.error("DCC Control: delete_switch_mapping - Switch "+str(switch_id)+" - Switch ID must be an integer") + elif not switch_mapped(switch_id): + logging.error("DCC Control: delete_switch_mapping - Switch "+str(switch_id)+" - DCC Mapping does not exist") + else: + logging.debug("Switch "+str(switch_id)+": Deleting DCC Address mapping for Switch") + # Remove the DCC address from the dcc_address_mappings dictionary + remove_dcc_commands_from_dcc_address_mappings(dcc_switch_mappings[str(switch_id)]["oncommands"]) + remove_dcc_commands_from_dcc_address_mappings(dcc_switch_mappings[str(switch_id)]["offcommands"]) + # Now delete the switch mapping from the dcc_point_mappings dictionary + del dcc_switch_mappings[str(switch_id)] + return() + #---------------------------------------------------------------------------------------------------- # API function for deleting a DCC signal mapping and removing all DCC addresses # associated with the signal from the dcc_address_mappings. This is used by the @@ -645,7 +745,6 @@ def delete_point_mapping(point_id:int): def delete_signal_mapping(sig_id:int): global dcc_signal_mappings - global dcc_address_mappings if not isinstance(sig_id, int): logging.error("DCC Control: delete_signal_mapping - Signal "+str(sig_id)+" - Signal ID must be an integer") elif not sig_mapped(sig_id): @@ -654,53 +753,36 @@ def delete_signal_mapping(sig_id:int): logging.debug("Signal "+str(sig_id)+": Deleting DCC Address mapping for signal") # Retrieve the DCC mappings for the signal and determine the mapping type dcc_signal_mapping = dcc_signal_mappings[str(sig_id)] - # Colour Light Signal mappings if dcc_signal_mapping["mapping_type"] == mapping_type.COLOUR_LIGHT: - # Compile a list of all DCC commands associated with the signal (aspects, feathers) - # Note we don't need to add the 'CAUTION_APP_CNTL' list as this is the same as CAUTION - dcc_command_list = [[dcc_signal_mapping["main_subsidary"],True]] - dcc_command_list.extend(dcc_signal_mapping[str(signals.signal_state_type.DANGER)]) - dcc_command_list.extend(dcc_signal_mapping[str(signals.signal_state_type.PROCEED)]) - dcc_command_list.extend(dcc_signal_mapping[str(signals.signal_state_type.CAUTION)]) - dcc_command_list.extend(dcc_signal_mapping[str(signals.signal_state_type.PRELIM_CAUTION)]) - dcc_command_list.extend(dcc_signal_mapping[str(signals.signal_state_type.FLASH_CAUTION)]) - dcc_command_list.extend(dcc_signal_mapping[str(signals.signal_state_type.FLASH_PRELIM_CAUTION)]) - dcc_command_list.extend(dcc_signal_mapping[str(signals.route_type.NONE)]) - dcc_command_list.extend(dcc_signal_mapping[str(signals.route_type.MAIN)]) - dcc_command_list.extend(dcc_signal_mapping[str(signals.route_type.LH1)]) - dcc_command_list.extend(dcc_signal_mapping[str(signals.route_type.LH2)]) - dcc_command_list.extend(dcc_signal_mapping[str(signals.route_type.RH1)]) - dcc_command_list.extend(dcc_signal_mapping[str(signals.route_type.RH2)]) - # Add the Theatre route indicator addresses - Each Route Element is [char,[[address,state],]] - for theatre_route_element in dcc_signal_mapping["THEATRE"]: - dcc_command_list.extend(theatre_route_element[1]) - # List is now complete - remove all DCC addresses from the dcc_address_mappings dictionary - # Note that the dictionary key is an INTEGER - for dcc_command in dcc_command_list: - if dcc_command[0] in dcc_address_mappings.keys(): - del dcc_address_mappings[int(dcc_command[0])] - # Semaphors Signal mappings + # Remove all DCC addresses used by the colour light signal from the dcc_address_mappings dictionary + # Note we don't need to remove the 'CAUTION_APP_CNTL' commands as they are the same as CAUTION + remove_dcc_commands_from_dcc_address_mappings([[dcc_signal_mapping["main_subsidary"], True]]) + remove_dcc_commands_from_dcc_address_mappings(dcc_signal_mapping[str(signals.signal_state_type.DANGER)]) + remove_dcc_commands_from_dcc_address_mappings(dcc_signal_mapping[str(signals.signal_state_type.PROCEED)]) + remove_dcc_commands_from_dcc_address_mappings(dcc_signal_mapping[str(signals.signal_state_type.CAUTION)]) + remove_dcc_commands_from_dcc_address_mappings(dcc_signal_mapping[str(signals.signal_state_type.PRELIM_CAUTION)]) + remove_dcc_commands_from_dcc_address_mappings(dcc_signal_mapping[str(signals.signal_state_type.FLASH_CAUTION)]) + remove_dcc_commands_from_dcc_address_mappings(dcc_signal_mapping[str(signals.signal_state_type.FLASH_PRELIM_CAUTION)]) + remove_dcc_commands_from_dcc_address_mappings(dcc_signal_mapping[str(signals.route_type.NONE)]) + remove_dcc_commands_from_dcc_address_mappings(dcc_signal_mapping[str(signals.route_type.MAIN)]) + remove_dcc_commands_from_dcc_address_mappings(dcc_signal_mapping[str(signals.route_type.LH1)]) + remove_dcc_commands_from_dcc_address_mappings(dcc_signal_mapping[str(signals.route_type.LH2)]) + remove_dcc_commands_from_dcc_address_mappings(dcc_signal_mapping[str(signals.route_type.RH1)]) + remove_dcc_commands_from_dcc_address_mappings(dcc_signal_mapping[str(signals.route_type.RH2)]) + remove_dcc_commands_from_dcc_address_mappings(get_list_of_theatre_dcc_commands(dcc_signal_mapping["THEATRE"])) elif dcc_signal_mapping["mapping_type"] == mapping_type.SEMAPHORE: - # Compile a list of all DCC addresses associated with the signal (signal arms) - dcc_address_list = [dcc_signal_mapping["main_signal"]] - dcc_address_list.extend([dcc_signal_mapping["lh1_signal"]]) - dcc_address_list.extend([dcc_signal_mapping["lh2_signal"]]) - dcc_address_list.extend([dcc_signal_mapping["rh1_signal"]]) - dcc_address_list.extend([dcc_signal_mapping["rh2_signal"]]) - dcc_address_list.extend([dcc_signal_mapping["main_subsidary"]]) - dcc_address_list.extend([dcc_signal_mapping["lh1_subsidary"]]) - dcc_address_list.extend([dcc_signal_mapping["lh2_subsidary"]]) - dcc_address_list.extend([dcc_signal_mapping["rh1_subsidary"]]) - dcc_address_list.extend([dcc_signal_mapping["rh2_subsidary"]]) - # Add the Theatre route indicator addresses - Each Route Element is [char,[[address,state],]] - for theatre_route_element in dcc_signal_mapping["THEATRE"]: - for dcc_command in theatre_route_element[1]: - dcc_address_list.extend([dcc_command[0]]) - # List is now complete - remove all DCC addresses from the dcc_address_mappings dictionary - # Note that the dictionary key is an INTEGER - for dcc_address in dcc_address_list: - if dcc_address in dcc_address_mappings.keys(): - del dcc_address_mappings[int(dcc_address)] + # Remove all DCC addresses used by the semaphore signal from the dcc_address_mappings dictionary + remove_dcc_address_from_dcc_address_mappings(dcc_signal_mapping["main_signal"]) + remove_dcc_address_from_dcc_address_mappings(dcc_signal_mapping["lh1_signal"]) + remove_dcc_address_from_dcc_address_mappings(dcc_signal_mapping["lh2_signal"]) + remove_dcc_address_from_dcc_address_mappings(dcc_signal_mapping["rh1_signal"]) + remove_dcc_address_from_dcc_address_mappings(dcc_signal_mapping["rh2_signal"]) + remove_dcc_address_from_dcc_address_mappings(dcc_signal_mapping["main_subsidary"]) + remove_dcc_address_from_dcc_address_mappings(dcc_signal_mapping["lh1_subsidary"]) + remove_dcc_address_from_dcc_address_mappings(dcc_signal_mapping["lh2_subsidary"]) + remove_dcc_address_from_dcc_address_mappings(dcc_signal_mapping["rh1_subsidary"]) + remove_dcc_address_from_dcc_address_mappings(dcc_signal_mapping["rh2_subsidary"]) + remove_dcc_commands_from_dcc_address_mappings(get_list_of_theatre_dcc_commands(dcc_signal_mapping["THEATRE"])) # Now delete the signal mapping from the dcc_signal_mappings dictionary del dcc_signal_mappings[str(sig_id)] return() diff --git a/model_railway_signals/library/file_interface.py b/model_railway_signals/library/file_interface.py index 285d22e6..2897b758 100644 --- a/model_railway_signals/library/file_interface.py +++ b/model_railway_signals/library/file_interface.py @@ -135,6 +135,7 @@ def load_schematic(requested_filename:str=None): if filename_to_load == () or filename_to_load == "": filename_to_load = None elif not os.path.isfile(requested_filename): + logging.error("Load File - Layout file '"+requested_filename+"' does not exist") filename_to_load = None else: filename_to_load = requested_filename diff --git a/model_railway_signals/library/gpio_sensors.py b/model_railway_signals/library/gpio_sensors.py index 7088c010..c404d82d 100644 --- a/model_railway_signals/library/gpio_sensors.py +++ b/model_railway_signals/library/gpio_sensors.py @@ -77,7 +77,7 @@ # 2) Leave the references to the gpiozero button objects stable - On 'reset_gpio_mqtt_configuration' we only # remove the elements we need from the master gpio_port_mappings - leaving everything else as is # 3) Simplify the processing of trigger events - we now use the gpiozero library to implement the trigger delay -# and immediately pass execution back to the main tkinter thread (using the root.after method) +# and pass execution back to the main tkinter thread (using common.execute_function_in_tkinter_thread) # GPIO button is triggeres # #--------------------------------------------------------------------------------------------------- @@ -175,7 +175,7 @@ def gpio_sensor_exists(sensor_id:Union[int,str]): #--------------------------------------------------------------------------------------------------- def gpio_triggered_callback(*args): - common.root_window.after(0, lambda:gpio_sensor_triggered(*args)) + common.execute_function_in_tkinter_thread(lambda:gpio_sensor_triggered(*args)) #--------------------------------------------------------------------------------------------------- # Internal function executed in the main Tkinter thread whenever a "Button Held" event is detected diff --git a/model_railway_signals/library/mqtt_interface.py b/model_railway_signals/library/mqtt_interface.py index a8ac2b54..4dde6119 100644 --- a/model_railway_signals/library/mqtt_interface.py +++ b/model_railway_signals/library/mqtt_interface.py @@ -186,8 +186,9 @@ def thread_to_send_heartbeat_messages(): if node_config["enhanced_debugging"]: logging.debug("MQTT-Client: Heartbeat thread started") node_config["heartbeat_thread_terminated"] = False while not node_config["terminate_heartbeat_thread"]: - # The PAHO MQTT client may not be thread safe so publish the message from the main Tkinter thread - common.root_window.after(0, publish_heartbeat_message) + # The PAHO MQTT client may not be thread safe so publish the message from the main Tkinter + # thread as this thread is the thread that 'created' our MQTT client instance + common.execute_function_in_tkinter_thread(lambda:publish_heartbeat_message()) # Wait before we send out the next heartbeat last_heartbeat_time = time.time() while (time.time() < last_heartbeat_time + node_config["heartbeat_frequency"] @@ -323,7 +324,7 @@ def process_message(msg): def on_message(mqtt_client,obj,msg): # Only process the message if there is a payload - If there is no payload then the message is # a "null message" - sent to purge retained messages from the broker on application exit - if msg.payload: common.root_window.after(0, lambda:process_message(msg)) + if msg.payload: common.execute_function_in_tkinter_thread(lambda:process_message(msg)) return() #----------------------------------------------------------------------------------------------- diff --git a/model_railway_signals/library/signals.py b/model_railway_signals/library/signals.py index aaa53b2e..33665a58 100644 --- a/model_railway_signals/library/signals.py +++ b/model_railway_signals/library/signals.py @@ -553,11 +553,12 @@ def enable_disable_theatre_route_indication(sig_id:int): # ------------------------------------------------------------------------- def update_signal_aspect(sig_id:int): - # Call the signal type-specific functions to update the signal (note that we only update - # Colour light signals if they are configured to update immediately after a state change) - if signals[str(sig_id)]["sigtype"] == signal_type.colour_light: - if signals[str(sig_id)]["refresh"]: signals_colour_lights.update_colour_light_signal(sig_id) - elif signals[str(sig_id)]["sigtype"] == signal_type.ground_position: + # Call the signal type-specific functions to update semaphore, ground position and + # ground disc signals. Note that we don't update colour light signals as the aspect + # they need to display may depend on the signal ahead - in this case, the calling + # programme needs to call 'signals_colour_lights.update_colour_light_signal' with + # the ID of the signal to update and the ID of the signal on the route ahead + if signals[str(sig_id)]["sigtype"] == signal_type.ground_position: signals_ground_position.update_ground_position_signal(sig_id) elif signals[str(sig_id)]["sigtype"] == signal_type.semaphore: signals_semaphores.update_semaphore_signal(sig_id) diff --git a/model_railway_signals/library/signals_colour_lights.py b/model_railway_signals/library/signals_colour_lights.py index 20287505..c079e020 100644 --- a/model_railway_signals/library/signals_colour_lights.py +++ b/model_railway_signals/library/signals_colour_lights.py @@ -26,11 +26,6 @@ # rhfeather45:bool - Creates a RH route feather at 45 degrees - Default = False # rhfeather90:bool - Creates a RH route feather at 90 degrees - Default = False # theatre_route_indicator:bool - Creates a Theatre route indicator - Default = False -# refresh_immediately:bool - When set to False the signal aspects will NOT be automatically -# updated when the signal is changed and the calling programme will need to call -# the seperate 'update_signal' function. Primarily intended for use with 3/4 -# aspect signals, where the displayed aspect will depend on the displayed aspect -# of the signal ahead if the signal is clear - Default = True # fully_automatic:bool - Creates a signal without a manual controls - Default = False # # Classes and functions used by the other library modules: @@ -76,7 +71,6 @@ def create_colour_light_signal (canvas, sig_id:int, rhfeather45:bool=False, rhfeather90:bool=False, theatre_route_indicator:bool=False, - refresh_immediately:bool=True, fully_automatic:bool=False): # Set a default 'tag' to reference the tkinter drawing objects (if creation fails) canvas_tag = "signal"+str(sig_id) @@ -199,7 +193,6 @@ def create_colour_light_signal (canvas, sig_id:int, # All SHARED attributes are signals_common to more than one signal Types signals.signals[str(sig_id)]["overriddenaspect"] = override_aspect # Type-specific - The 'Overridden' aspect signals.signals[str(sig_id)]["subtype"] = signalsubtype # Type-specific - subtype of the signal - signals.signals[str(sig_id)]["refresh"] = refresh_immediately # Type-specific - controls when aspects are updated signals.signals[str(sig_id)]["hasfeathers"] = signal_has_feathers # Type-specific - If there is a Feather Route display signals.signals[str(sig_id)]["featherenabled"] = None # Type-specific - State of the Feather Route display signals.signals[str(sig_id)]["grn"] = grn # Type-specific - drawing object @@ -231,9 +224,6 @@ def create_colour_light_signal (canvas, sig_id:int, if loaded_state["override"]: signals.set_signal_override(sig_id) # If no state was loaded we still need to toggle fully automatic signals to OFF if loaded_state["sigclear"] or fully_automatic: signals.toggle_signal(sig_id) - # Update the signal to show the initial aspect (and send out DCC commands) - # We only refresh the signal if it is set to refresh immediately - if signals.signals[str(sig_id)]["refresh"]: update_colour_light_signal(sig_id) # finally Lock the signal if required if loaded_state["siglocked"]: signals.lock_signal(sig_id) if has_subsidary: @@ -515,9 +505,6 @@ def update_feather_route_indication(sig_id:int,route_to_set): # is at Danger to cater for DCC signal types that automatically enable/disable the route indication dcc_control.update_dcc_signal_route(sig_id, signals.signals[str(sig_id)]["routeset"], signal_change = False, sig_at_danger = True) - # Refresh the signal aspect (a catch-all to ensure the signal displays the correct aspect - # in case the signal is in the middle of a timed sequence for the old route or the new route - if signals.signals[str(sig_id)]["refresh"]: update_colour_light_signal(sig_id) # Update the feathers on the display refresh_feathers(sig_id) return() diff --git a/model_railway_signals/library/text_boxes.py b/model_railway_signals/library/text_boxes.py new file mode 100644 index 00000000..80009e64 --- /dev/null +++ b/model_railway_signals/library/text_boxes.py @@ -0,0 +1,148 @@ +#--------------------------------------------------------------------------------------------------- +# This module is used for creating and managing text box library objects on the canvas +#--------------------------------------------------------------------------------------------------- +# +# External API - classes and functions (used by the Schematic Editor): +# +# create_text_box - Creates a text box and returns a "tag" for the tkinter canvas drawing objects +# This allows the editor to move the text box object on the schematic as required +# Mandatory Parameters: +# Canvas - The Tkinter Drawing canvas on which the text box is to be displayed +# textbox_id:int - The unique ID for the text box +# x:int, y:int - Position of the text box on the canvas (in pixels) +# text:str - The text to display +# Optional Parameters: +# colour:str - Text colour - default="black" +# background:str - Background colour - default="grey85" +# justify: either TK.LEFT, TK.CENTRE, TK.RIGHT - default=TK.CENTER +# borderwidth:int - the width of the border to display - default=0 (no border) +# font:(str,int,str) - the font to apply - default=("Courier",8,"normal") +# hidden:bool - Whether the text box should be 'hidden' in Run Mode - default=False +# +# text_box_exists(textbox_id:int) - returns true if the if a text box object 'exists' +# +# delete_text_box(textbox_id:int) - To delete the specified text box from the schematic +# +# Classes and functions used by the other library modules: +# +# configure_edit_mode(edit_mode:bool) - True for Edit Mode, False for Run Mode +# +#--------------------------------------------------------------------------------------------------- + +import logging +import tkinter as Tk + +#--------------------------------------------------------------------------------------------------- +# Text Boxes are maintained in a global dictionary (with a key of 'textbox_id') +# Each dictionary entry (representing a text box) is a dictionary of key-value pairs: +# 'canvas' - The tkinter canvas (that the drawing objects are created on) +# 'hiddden' - Whether the Text Box should be 'hidden' in Run Mode or not +# 'textbox' - Reference to the Tkinter drawing object +# 'rectangle' - Reference to the Tkinter drawing object +# 'borderwidth' - Borderwidth for the rectangle (so this can be hidden/displayed) +# 'background' - Background colour for the rectangle (so this can be hidden/displayed) +# 'tags' - The tags applied to the text box objects on the canvas +#--------------------------------------------------------------------------------------------------- + +text_boxes = {} + +#------------------------------------------------------------------------------------ +# API function to set/clear Edit Mode (called by the editor on mode change) +# Text Box objects are 'hidden' in Run Mode if this is configured at creation +#------------------------------------------------------------------------------------ + +editing_enabled = False + +def configure_edit_mode(edit_mode:bool): + global editing_enabled + # Maintain a global flag (for creating new library objects) + editing_enabled = edit_mode + for text_box_id in text_boxes: + text_box = text_boxes[text_box_id] + if editing_enabled: + # In Edit Mode - Always display all drawing objects as configured + text_box["canvas"].itemconfig(text_box["textbox"], state="normal") + text_box["canvas"].itemconfig(text_box["rectangle"], width=text_box["borderwidth"], fill=text_box["background"]) + elif text_box["hidden"]: + # In Run Mode - if the object is configured as 'hidden' then we hide the text object + # but set the rectangle object to transparent (fill="") with a border width of zero + text_box["canvas"].itemconfig(text_box["textbox"], state="hidden") + text_box["canvas"].itemconfig(text_box["rectangle"], width=0, fill="") + return() + +#--------------------------------------------------------------------------------------------------- +# API Function to check if a Text Box library object exists (in the dictionary of Text Boxes) +#--------------------------------------------------------------------------------------------------- + +def text_box_exists(textbox_id:int): + if not isinstance(textbox_id, int): + logging.error("Text Box "+str(textbox_id)+": textbox_exists - textbox_id ID must be an int") + textbox_exists = False + else: + textbox_exists = str(textbox_id) in text_boxes.keys() + return(textbox_exists) + +#--------------------------------------------------------------------------------------------------- +# API Function to create a Text Box library object on the schematic +#--------------------------------------------------------------------------------------------------- + +def create_text_box(canvas, textbox_id:int, x:int, y:int, text:str, colour:str="black", + background:str="grey85", justify=Tk.CENTER, borderwidth:int=0, + font=("Courier",8,"normal"), hidden:bool=False): + global text_boxes + # Set a unique 'tag' to reference the tkinter drawing objects + canvas_tag = "textbox"+str(textbox_id) + if not isinstance(textbox_id, int) or textbox_id < 1 or textbox_id > 999: + logging.error("Text Box "+str(textbox_id)+": create_text_box - Textbox ID must be an int (1-999)") + elif text_box_exists(textbox_id): + logging.error("Text Box "+str(textbox_id)+": create_text_box - Textbox ID already exists") + else: + logging.debug("Text Box "+str(textbox_id)+": Creating library object on the schematic") + # Create the new drawing objects (tagged with the canvas_tag) - These are initially created + # assuming we are in Edit Mode (Objects always visible) and hidden later if required. + text_box = canvas.create_text(x, y, fill=colour, text=text, tags=canvas_tag, justify=justify, font=font) + # Find the boundary box and create the rectangle for the background + bbox = canvas.bbox(text_box) + rectangle = canvas.create_rectangle(bbox[0]-3, bbox[1]-3, bbox[2]+3, bbox[3]+2, + width=borderwidth, tags=canvas_tag, fill=background, outline=colour) + # Raise the text item to be in front of the rectangle item + canvas.tag_raise(text_box, rectangle) + # Hide the drawing objects if we are in Run Mode and 'hidden'. Note we can't just make the + # rectangle 'hidden' as the canvas.bbox function (used by the editor to get the selection area) + # would just return zero values when subsequently queried (after the return from this function) + # and so the object would be unselectable when the user toggles back to edit mode. We therefore + # set the rectangle borderwidth to zero and make it transparent (fill="") + if not editing_enabled and hidden: + canvas.itemconfig(text_box, state="hidden") + canvas.itemconfig(rectangle, fill="", width=0) + # Store the details of the Text Box Object in the dictionary of Text boxes + text_boxes[str(textbox_id)] = {} + text_boxes[str(textbox_id)]['canvas'] = canvas + text_boxes[str(textbox_id)]['textbox'] = text_box + text_boxes[str(textbox_id)]['rectangle'] = rectangle + text_boxes[str(textbox_id)]['borderwidth'] = borderwidth + text_boxes[str(textbox_id)]['background'] = background + text_boxes[str(textbox_id)]['hidden'] = hidden + text_boxes[str(textbox_id)]['tags'] = canvas_tag + # Return the canvas_tag for the tkinter drawing objects + return(canvas_tag) + +#--------------------------------------------------------------------------------------------------- +# Function to delete a Text Box library object from the schematic +#--------------------------------------------------------------------------------------------------- + +def delete_text_box(textbox_id:int): + global text_boxes + if not isinstance(textbox_id, int): + logging.error("Text Box "+str(textbox_id)+": delete_text_box - Textbox ID must be an int") + elif not text_box_exists(textbox_id): + logging.error("Text Box "+str(textbox_id)+": delete_text_box - Textbox ID does not exist") + else: + logging.debug("Text Box "+str(textbox_id)+": Deleting Textbox object from the schematic") + # Delete all tkinter drawing objects + text_boxes[str(textbox_id)]['canvas'].delete(text_boxes[str(textbox_id)]["tags"]) + # Delete the track sensor entry from the dictionary of track sensors + del text_boxes[str(textbox_id)] + return() + +##################################################################################################### \ No newline at end of file diff --git a/model_railway_signals/library/track_sections.py b/model_railway_signals/library/track_sections.py index 34989573..dd932ddc 100644 --- a/model_railway_signals/library/track_sections.py +++ b/model_railway_signals/library/track_sections.py @@ -13,6 +13,7 @@ # Optional parameters: # default_label:str - The default label to display when occupied - default = 'OCCUPIED' # editable:bool - If the section can be manually toggled and/or edited - default = True +# hidden:bool - Whether the Track section should be 'hidden' in Run Mode - default = False # mirror_id:str - The ID of another local/remote Section to mirror - default = None # # section_exists(section_id:int/str) - returns true if the Track Section object 'exists' (either the @@ -155,13 +156,18 @@ def configure_edit_mode(edit_mode:bool): if section_id.isdigit(): track_section = sections[section_id] if editing_enabled: + # In Edit Mode - Hide the button window and display all placeholder objects in their normal + # configuration (placeholder1 is the text object and placeholder2 is the rectangle object) track_section["canvas"].itemconfig(track_section["buttonwindow"], state='hidden') track_section["canvas"].itemconfig(track_section["placeholder1"], state='normal') - track_section["canvas"].itemconfig(track_section["placeholder2"], state='normal') + track_section["canvas"].itemconfig(track_section["placeholder2"], fill='black') else: - track_section["canvas"].itemconfig(track_section["buttonwindow"], state='normal') + # In Run Mode - If the object is configured as 'hidden' then we hide the text object but set + # the rectangle object to transparent - effectively hiding it whilst maintaining its 'presence' + if not track_section["hidden"]: + track_section["canvas"].itemconfig(track_section["buttonwindow"], state='normal') track_section["canvas"].itemconfig(track_section["placeholder1"], state='hidden') - track_section["canvas"].itemconfig(track_section["placeholder2"], state='hidden') + track_section["canvas"].itemconfig(track_section["placeholder2"], fill='') return() #--------------------------------------------------------------------------------------------- @@ -309,7 +315,7 @@ def update_mirrored_sections(section_id:int, publish_to_broker:bool=True): #--------------------------------------------------------------------------------------------- def create_section (canvas, section_id:int, x:int, y:int, section_callback, - default_label:str="OCCUPIED", editable:bool=True, mirror_id:str=""): + default_label:str="OCCUPIED", editable:bool=True, hidden=False, mirror_id:str=""): global sections # Set a unique 'tag' to reference the tkinter drawing objects canvas_tag = "section"+str(section_id) @@ -331,41 +337,51 @@ def create_section (canvas, section_id:int, x:int, y:int, section_callback, # Specify the fontsize locally fontsize = 9 # We need the default label width to set the width of the Track section button - label_width = len(default_label) - # Create the button object, callbacks and window to hold it. Note the Mouse button events are - # only bound to the button if the Section is editable - otherwise the button will be disabled + label_length = len(default_label) + # Create the Section Button and its canvas window (for Run Mode operation). Note the Window + # is initially 'hidden', assuming edit mode - but changed later if we are in Run Mode section_button = Tk.Button(canvas, text=default_label, state="normal", relief="raised", - width=label_width, font=('Courier',fontsize,"bold"), - bg="grey", fg="grey40", padx=0, pady=0, - activebackground="grey", activeforeground="grey40") + width=label_length, font=('Courier',fontsize,"bold"), bg="grey", fg="grey40", + padx=3, pady=0, activebackground="grey", activeforeground="grey40") + # Bind the mouse button events to the Track Section - only if the Section is editable + # If not editable we also make the button disabled to prevent responses to clicking if editable: section_button.bind('', lambda event:section_button_event(section_id)) section_button.bind('', lambda event:open_entry_box(section_id)) else: section_button.config(state="disabled") - button_window = canvas.create_window(x, y, window=section_button, tags=canvas_tag) - # Create the 'placeholder' for the button to display in Edit Mode (so it an be selected/moved) - # Note that the canvas Text object width is defined in pixels so we have to use the fointsize - # The Placeholder label is always the Track Section ID so it can be identified on the edit canvas - placeholder1 = canvas.create_text(x, y, text=default_label, width=label_width*fontsize, - font=('Courier',fontsize,"bold"), fill="white", tags=canvas_tag) + button_window = canvas.create_window(x, y, window=section_button, tags=canvas_tag, state='hidden') + # Create the 'placeholders' for the button to display in Edit Mode (so it an be selected/moved) + # Note that we have to create the text object with the default section text in order to set the + # correct width (specifying a 'width' parameter in pixels doesn't seen to work for some reason) + placeholder1 = canvas.create_text(x, y, text=default_label, fill="white", + font=('Courier',fontsize,"bold"), tags=canvas_tag) bbox = canvas.bbox(placeholder1) placeholder2 = canvas.create_rectangle(bbox[0]-4, bbox[1]-2, bbox[2]+4, bbox[3]+0, - tags=canvas_tag, fill="black") + tags=canvas_tag, fill="black", width=0) + # Raise the text item to be in front of the rectangle item canvas.tag_raise(placeholder1,placeholder2) + # Now everything has been creted at the correct width, we can set the text for the placeholder + # which is always the Track Section ID so it can easily be identified on the edit canvas canvas.itemconfigure(placeholder1, text=format(section_id,'02d')) - # Display either the button or button 'placeholder' depending on the mode - if editing_enabled: - canvas.itemconfig(button_window, state='hidden') - else: + # Hide the placeholder objects if we are in Run Mode. Note we can't just make the rectangle + # 'hidden' as the canvas.bbox function (used by the editor to get the selection area) would + # just return zero values when subsequently queried (after the return from this function) and + # so the object would be unselectable when the user toggles back to edit mode. We therefore + # make the rectangle transparent (fill="") to effectively make it hidden. Note we also hide + # the button window if the Track Section itself needs to be 'hidden' in Run Mode. + if not editing_enabled: + if hidden: canvas.itemconfig(button_window, state='hidden') + else: canvas.itemconfig(button_window, state='normal') canvas.itemconfig(placeholder1, state='hidden') - canvas.itemconfig(placeholder2, state='hidden') + canvas.itemconfig(placeholder2, fill='') # Compile a dictionary of everything we need to track sections[str(section_id)] = {} sections[str(section_id)]["canvas"] = canvas # Tkinter canvas object sections[str(section_id)]["extcallback"] = section_callback # External callback to make sections[str(section_id)]["mirror"] = mirror_id # Other Local or Remote section to mirror - sections[str(section_id)]["labellength"] = label_width # The fixed width for the train designator + sections[str(section_id)]["hidden"] = hidden # Display/hide the Track Sensor in Run Mode + sections[str(section_id)]["labellength"] = label_length # The fixed width for the train designator sections[str(section_id)]["occupied"] = False # Current state (occupied/clear) sections[str(section_id)]["labeltext"] = default_label # Current state (train designator) sections[str(section_id)]["statevalid"] = True # State always valid for Local sections diff --git a/model_railway_signals/library/track_sensors.py b/model_railway_signals/library/track_sensors.py index de458820..ad3033c8 100644 --- a/model_railway_signals/library/track_sensors.py +++ b/model_railway_signals/library/track_sensors.py @@ -9,8 +9,10 @@ # Mandatory Parameters: # Canvas - The Tkinter Drawing canvas on which the track sensor is to be displayed # sensor_id:int - The unique ID for the track sensor -# x:int, y:int - Position of the point on the canvas (in pixels) +# x:int, y:int - Position of the track sensor on the canvas (in pixels) # callback - the function to call on track sensor triggered events (returns item_id) +# Optional Parameters: +# hidden:bool - Whether the Track Sensor should be 'hidden' in Run Mode - default = False # # track_sensor_exists(sensor_id:int) - returns true if the if a track sensor object 'exists' # @@ -35,7 +37,9 @@ # Each dictionary entry (representing a track sensor) is a dictionary of key-value pairs: # 'canvas' - The tkinter canvas (that the drawing objects are created on) # 'callback' - The callback function to make on track sensor triggered events +# 'hiddden' - Whether the Track Sensor should be 'hidden' in Run Mode or not # 'button' - A reference to the Tkinter Button object (to simulate 'sensor triggered' events) +# 'buttonwindow' - A reference to the Tkinter Button window (so this can be hidden/displayed) # 'tags' - The tags applied to all canvas drawing objects for the Track Sensor instance # 'circle' - The reference to the Tkinter circle used for "selection" in edit mode # 'label' - The reference to the Tkinter label which is only displayed in edit mode @@ -54,15 +58,20 @@ def configure_edit_mode(edit_mode:bool): global editing_enabled # Maintain a global flag (for creating new library objects) editing_enabled = edit_mode - # Update all existing library objects (according to the current mode) + # Update all canvas objects depending on the mode and whether the sensor should be 'hidden' (in Run Mode) + # In Edit mode - all drawing objects (button, selection circle and Item ID text) are visible + # In Run Mode, only the button is visible (unless this needs to be 'hidden' in Run Mode for track_sensor_id in track_sensors: track_sensor = track_sensors[track_sensor_id] if editing_enabled: track_sensor["canvas"].itemconfig(track_sensor["circle"], width=1) track_sensor["canvas"].itemconfig(track_sensor["label"], state="normal") + track_sensor["canvas"].itemconfig(track_sensor["buttonwindow"], state="normal") else: - track_sensor["canvas"].itemconfig(track_sensor["circle"], width=0) + if track_sensor["hidden"]: + track_sensor["canvas"].itemconfig(track_sensor["buttonwindow"], state="hidden") track_sensor["canvas"].itemconfig(track_sensor["label"], state="hidden") + track_sensor["canvas"].itemconfig(track_sensor["circle"], width=0) return() #--------------------------------------------------------------------------------------------------- @@ -106,7 +115,7 @@ def reset_sensor_button (sensor_id:int): # API Function to create a Track Sensor library object on the schematic #--------------------------------------------------------------------------------------------------- -def create_track_sensor(canvas, sensor_id:int, x:int, y:int, callback): +def create_track_sensor(canvas, sensor_id:int, x:int, y:int, callback, hidden:bool=False): global track_sensors # Set a unique 'tag' to reference the tkinter drawing objects canvas_tag = "sensor"+str(sensor_id) @@ -116,24 +125,30 @@ def create_track_sensor(canvas, sensor_id:int, x:int, y:int, callback): logging.error("Track Sensor "+str(sensor_id)+": create_track_sensor - Sensor ID already exists") else: logging.debug("Track Sensor "+str(sensor_id)+": Creating library object on the schematic") - # Create the new drawing objects (tagged with the canvas_tag) - the oval is to give us - # a reasonable selection area when we subsequently get the bbox of the tagged objects. - # The Sensor identifier is only displayed in Edit mode (to aid configuration) - Note - # that the label is offset to take into account the default font size in 'common' + # Create the new drawing objects (tagged with the canvas_tag) - the Oval object and Sensor ID text are + # only displayed in Edit mode (to aid identification and selection). These are both initially created + # assuming we are in Run Mode (Oval has a width of zero to make it invisible and the text is hidden). + # Similarly the button is created as visible (Changed later if in Run Mode and 'hidden' is selected) sensor_button = Tk.Button(canvas, text="O", padx=1, pady=1, font=('Courier',2,"normal")) sensor_button.config(command=lambda:track_sensor_triggered(sensor_id)) - canvas.create_window(x, y, window=sensor_button, tags=canvas_tag) + button_window = canvas.create_window(x, y, window=sensor_button, tags=canvas_tag) selection_circle = canvas.create_oval(x-20, y-20, x+20, y+20, outline="grey60", tags=canvas_tag, width=0) + # Note that the label is offset to take into account the default font size in 'common' sensor_label = canvas.create_text(x, y+9+(common.fontsize/2), text=format(sensor_id,'02d'), tags=canvas_tag, state="hidden", font=('Courier',common.fontsize,"normal")) - # If we are in edit mode then the selection circle is visible + # In Edit mode - all drawing objects (button, selection circle and Item ID text) are visible + # In Run Mode, only the button is visible (unless this needs to be 'hidden' in Run Mode) if editing_enabled: canvas.itemconfig(selection_circle, width=1) canvas.itemconfig(sensor_label, state="normal") + elif hidden: + canvas.itemconfig(button_window, state='hidden') # Store the details of the Track Sensor Object in the dictionary of Track Sensors track_sensors[str(sensor_id)] = {} track_sensors[str(sensor_id)]['canvas'] = canvas track_sensors[str(sensor_id)]['button'] = sensor_button + track_sensors[str(sensor_id)]['buttonwindow'] = button_window + track_sensors[str(sensor_id)]['hidden'] = hidden track_sensors[str(sensor_id)]['callback'] = callback track_sensors[str(sensor_id)]['tags'] = canvas_tag track_sensors[str(sensor_id)]['circle'] = selection_circle diff --git a/pyproject.toml b/pyproject.toml index a53fff0d..2e04426e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "model-railway-signals" -version = "4.6.1" +version = "4.7.0" authors = [ { name="johnrm174", email="johnrm17418@gmail.com" }, ] maintainers = [ { name="johnrm174", email="johnrm17418@gmail.com" }, ] description = "Create your own DCC model railway signalling scheme" diff --git a/scripts/signalling.desktop b/scripts/signalling.desktop index 1784795c..689bbc8e 100644 --- a/scripts/signalling.desktop +++ b/scripts/signalling.desktop @@ -1,10 +1,14 @@ # Example desktop entry for running up the autostart script (signalling.sh) -# File should be saved as: /home/pi/.config/autostart/signalling.desktop -# This will then autostart the application on Pi startup (user login) -# Useful for remote nodes being used to publish networked track sensors +# This file should be saved as: /home//.config/autostart/signalling.desktop +# This will then autostart the application once the Pi has started up (user login) +# Primarily intended for running up the application on remote track sensor nodes # [Desktop Entry] Type=Application Name=Signalling Terminal=false -Exec=sh /home/pi/.config/autostart/signalling.sh +# +# Remember to change 'signalbox' to your user name to set the correct path +# +Exec=lxterminal -e "sh /home/signalbox/.config/autostart/signalling.sh" + diff --git a/scripts/signalling.sh b/scripts/signalling.sh old mode 100755 new mode 100644 index 4a415e5e..98584076 --- a/scripts/signalling.sh +++ b/scripts/signalling.sh @@ -1,28 +1,30 @@ #!/bin/sh # signalling.sh # -# Example shell script to run up the model railway signalling application +# Shell script to run up the model railway signalling application # Will also re-direct the logs to a file based on the current time and date -# Script should be saved as: /home/pi/.config/autostart/signalling.sh +# Script should be saved as: /home//.config/autostart/signalling.sh +# Remember to change instances of 'signalbox' to your user name in the script below # Permissions should be set to make the script executable: chmod 755 signalling.sh -# +# Note we give ourselves a delay before application startup and shutdown to intervene if required +## +echo "Starting signalling application in 5 seconds - Hit to abort" +sleep 5 filename=signalling.log current_time=$(date "+%Y%m%d-%H%M%S") logfilename=$current_time-$filename -echo $logfilename >> /home/pi/.config/autostart/$logfilename 2>&1 +echo $logfilename >> /home/signalbox/Signalling/Logs/$logfilename 2>&1 # -# Modify the following to use the -f option to load a layout on startup -# eg: python3 -m model_railway_signals -f /home/pi/my_layout.sig -# You can also specify the logging level (DEBUG, INFO, WARNING or ERROR) -# eg: python3 -m model_railway_signals -l DEBUG +# Modify the filename after the -f flag to load a different layout file on startup +# eg: python3 -m model_railway_signals -f /home/signalbox/my_layout.sig +# You can also specify a different logging level if required (DEBUG, INFO, WARNING or ERROR) +# eg: python3 -m model_railway_signals -f /home/signalbox/my_layout.sig -l DEBUG # -python3 -m model_railway_signals >> /home/pi/.config/autostart/$logfilename 2>&1 +python3 -m model_railway_signals -f /home/signalbox/Signalling/Files/sensor_node.sig >> /home/signalbox/Signalling/Logs/$logfilename 2>&1 # -# Uncomment the following lines to shut down the Raspberry Pi on application exit -# Useful for when remote nodes are being used to publish networked track sensors -# Note we give ourselves a minute before shutdown to intervene if things go wrong +# Comment out the following lines if you don't want to shut down the Raspberry Pi on application exit # -#echo "Shutting down in 60 seconds" >> /home/pi/.config/autostart/$logfilename 2>&1 -#sleep 60 -#echo "Shutting down now" >> /home/pi/.config/autostart/$logfilename 2>&1 -#shutdown -h now \ No newline at end of file +echo "Shutting down Raspberry Pi in 5 seconds - Hit to abort" +sleep 5 +echo "Shutting down Raspberry Pi now" +shutdown -h now \ No newline at end of file diff --git a/system_tests/.coverage b/system_tests/.coverage deleted file mode 100644 index f115a341..00000000 Binary files a/system_tests/.coverage and /dev/null differ diff --git a/system_tests/basic_library_tests1.py b/system_tests/basic_library_tests1.py index 9569e8fc..2f0abe10 100644 --- a/system_tests/basic_library_tests1.py +++ b/system_tests/basic_library_tests1.py @@ -289,6 +289,14 @@ def run_pi_sprog_interface_tests(baud_rate): assert pi_sprog_interface.service_mode_read_cv(1) is None assert not pi_sprog_interface.service_mode_write_cv(1,255) assert not pi_sprog_interface.request_dcc_power_off() + print("Library Tests - Edge case testing - send CBUS command - 6 Errors will be generated") + pi_sprog_interface.send_cbus_command (mj_pri=-1, min_pri=2, op_code=8) # Invalid mj_pri + pi_sprog_interface.send_cbus_command (mj_pri=3, min_pri=3, op_code=8) # Invalid mj_pri + pi_sprog_interface.send_cbus_command (mj_pri=2, min_pri=-1, op_code=8) # Invalid min_pri + pi_sprog_interface.send_cbus_command (mj_pri=2, min_pri=4, op_code=8) # Invalid min_pri + pi_sprog_interface.send_cbus_command (mj_pri=2, min_pri=2, op_code=-1) # Invalid op_code + pi_sprog_interface.send_cbus_command (mj_pri=2, min_pri=2, op_code=256) # Invalid op_code + pi_sprog_interface.send_cbus_command (mj_pri=2, min_pri=2, op_code=8) # Valid but port is closed print("----------------------------------------------------------------------------------------") print("") return() @@ -370,7 +378,7 @@ def run_dcc_control_tests(baud_rate): ['2', [[1,True], ["abc",True], [11,"random"], [2048, True], 2047,[1,2,3]]] ] ) assert len(dcc_control.dcc_signal_mappings) == 2 assert len(dcc_control.dcc_address_mappings) == 20 - print("Library Tests - map_dcc_point - Two Debug messages - 8 error messages should be generated") + print("Library Tests - map_dcc_point - Two Debug messages - 7 error messages should be generated") assert len(dcc_control.dcc_point_mappings) == 0 dcc_control.map_dcc_point(1, 30, False) dcc_control.map_dcc_point(2, 31, True) @@ -381,7 +389,6 @@ def run_dcc_control_tests(baud_rate): dcc_control.map_dcc_point(5, 10, False) # Fail - address already in use dcc_control.map_dcc_point(6, "abc", False) # Fail - address not a str dcc_control.map_dcc_point(6, 2048, False) # Fail - Invalid address - dcc_control.map_dcc_point(7, 33, "True") # Fail - Invalid reversed flag assert len(dcc_control.dcc_point_mappings) == 2 assert len(dcc_control.dcc_address_mappings) == 22 print("Library Tests - get_dcc_address_mappings (no errors or warnings should be generated)") @@ -457,6 +464,8 @@ def run_dcc_control_tests(baud_rate): print("Library Tests - set_node_to_publish_dcc_commands - 1 Error will be generated ") dcc_control.set_node_to_publish_dcc_commands("True") # Error dcc_control.set_node_to_publish_dcc_commands(True) + dcc_control.update_dcc_signal_aspects(1, signals.signal_state_type.DANGER) + dcc_control.update_dcc_signal_aspects(1, signals.signal_state_type.PROCEED) print("Library Tests - subscribe_to_dcc_command_feed - 1 Error will be generated") dcc_control.subscribe_to_dcc_command_feed(100) # Error dcc_control.subscribe_to_dcc_command_feed("Box1") diff --git a/system_tests/basic_library_tests2.py b/system_tests/basic_library_tests2.py index 0692893d..6059936d 100644 --- a/system_tests/basic_library_tests2.py +++ b/system_tests/basic_library_tests2.py @@ -12,9 +12,59 @@ from model_railway_signals.library import track_sensors from model_railway_signals.library import block_instruments from model_railway_signals.library import track_sections +from model_railway_signals.library import text_boxes +from model_railway_signals.library import buttons from model_railway_signals.editor import schematic +#--------------------------------------------------------------------------------------------------------- +# Test Text Box Library objects +#--------------------------------------------------------------------------------------------------------- + +def run_text_box_library_tests(): + # Test all functions - including negative tests for parameter validation + print("Library Tests - Text Box Objects") + canvas = schematic.canvas + # create_track_sensor + print("Library Tests - create_text_box - will generate 4 errors:") + assert len(track_sensors.track_sensors) == 0 + text_boxes.create_text_box(canvas, 1, 100, 100, text="Textbox 1") # success + text_boxes.create_text_box(canvas, "2", 200, 100, text="Textbox 1") # Fail - not an int + text_boxes.create_text_box(canvas, 0, 200, 100, text="Textbox 1") # Fail - out of range + text_boxes.create_text_box(canvas, 1000, 200, 100, text="Textbox 1") # Fail - out of range + text_boxes.create_text_box(canvas, 1, 100, 100, text="Textbox 1") # Fail - duplicate + assert len(text_boxes.text_boxes) == 1 + # track_sensor_exists + print("Library Tests - text_box_exists - will generate 1 error:") + assert text_boxes.text_box_exists(1) # True (exists) + assert not text_boxes.text_box_exists("1") # False - with error message (not int) + assert not text_boxes.text_box_exists(0) # False - no error message + assert not text_boxes.text_box_exists(100) # False - no error message + # delete_track_sensor - reset_sensor_button function should not generate any exceptions + print("Library Tests - delete_text_box - will generate 2 errors:") + text_boxes.delete_text_box("1") # Fail - not an int + text_boxes.delete_text_box(100) # Fail - does not exist + text_boxes.delete_text_box(1) # success + assert len(text_boxes.text_boxes) == 0 + assert not text_boxes.text_box_exists(1) + # configure_edit_mode - this is an internal library function + print("Library Tests - configure_edit_mode - No Errors or Warnings") + text_boxes.configure_edit_mode(edit_mode=False) + text_boxes.create_text_box(canvas, 1, 100, 100, text="Textbox 1") + text_boxes.create_text_box(canvas, 2, 200, 100, text="Textbox 1", hidden=True) + text_boxes.configure_edit_mode(edit_mode=True) + text_boxes.create_text_box(canvas, 3, 300, 100, text="Textbox 1") + text_boxes.create_text_box(canvas, 4, 400, 100, text="Textbox 1", hidden=True) + text_boxes.configure_edit_mode(edit_mode=False) + # Clean up + text_boxes.delete_text_box(1) + text_boxes.delete_text_box(2) + text_boxes.delete_text_box(3) + text_boxes.delete_text_box(4) + print("----------------------------------------------------------------------------------------") + print("") + return() + #--------------------------------------------------------------------------------------------------------- # Test Track Sensor Library objects #--------------------------------------------------------------------------------------------------------- @@ -62,8 +112,10 @@ def run_track_sensor_library_tests(): print("Library Tests - configure_edit_mode") track_sensors.configure_edit_mode(edit_mode=False) track_sensors.create_track_sensor(canvas, sensor_id=10, x=100, y=100, callback=track_sensor_callback) # success + track_sensors.create_track_sensor(canvas, sensor_id=11, x=100, y=100, callback=track_sensor_callback, hidden=True) # success track_sensors.configure_edit_mode(edit_mode=True) track_sensors.create_track_sensor(canvas, sensor_id=20, x=200, y=100, callback=track_sensor_callback) # success + track_sensors.create_track_sensor(canvas, sensor_id=21, x=200, y=100, callback=track_sensor_callback, hidden=True) # success track_sensors.configure_edit_mode(edit_mode=False) # Clean up track_sensors.delete_track_sensor(10) # success @@ -90,9 +142,9 @@ def run_track_section_library_tests(): track_sections.configure_edit_mode(False) track_sections.create_section(canvas,1,100,100, track_section_callback, "OCCUPIED", editable=True, mirror_id="box1-50") # Success track_sections.create_section(canvas,2,200,100, track_section_callback, "OCCUPIED", editable=True, mirror_id="box1-51") # Success - track_sections.create_section(canvas,3,300,100, track_section_callback, "OCCUPIED", editable=True, mirror_id="4") # Success + track_sections.create_section(canvas,3,300,100, track_section_callback, "OCCUPIED", editable=True, mirror_id="4", hidden=True) # Success track_sections.configure_edit_mode(True) - track_sections.create_section(canvas,4,400,100, track_section_callback, "OCCUPIED", editable=True, mirror_id="3") # Success + track_sections.create_section(canvas,4,400,100, track_section_callback, "OCCUPIED", editable=True, mirror_id="3", hidden=True) # Success track_sections.create_section(canvas,5,500,100, track_section_callback, "OCCUPIED", editable=False, mirror_id="") # Success track_sections.create_section(canvas,6,600,100, track_section_callback, "OCCUPIED", editable=False, mirror_id="") # Success track_sections.create_section(canvas,0,100,100, track_section_callback, "OCCUPIED", editable=False, mirror_id="4") # Fail - ID out of range @@ -480,6 +532,8 @@ def run_point_library_tests(): print("Library Tests - set_point_colour - will generate 2 errors:") assert not points.point_switched(10) assert not points.point_switched(14) + points.toggle_point(14) + assert points.point_switched(14) assert canvas.itemcget(points.points[str(10)]["blade1"],"fill") == "red" assert canvas.itemcget(points.points[str(10)]["blade2"],"fill") == "red" assert canvas.itemcget(points.points[str(10)]["route1"],"fill") == "red" @@ -496,10 +550,10 @@ def run_point_library_tests(): assert canvas.itemcget(points.points[str(10)]["blade2"],"fill") == "red" assert canvas.itemcget(points.points[str(10)]["route1"],"fill") == "blue" assert canvas.itemcget(points.points[str(10)]["route2"],"fill") == "red" - assert canvas.itemcget(points.points[str(14)]["blade1"],"fill") == "blue" - assert canvas.itemcget(points.points[str(14)]["blade2"],"fill") == "black" - assert canvas.itemcget(points.points[str(14)]["route1"],"fill") == "blue" - assert canvas.itemcget(points.points[str(14)]["route2"],"fill") == "black" + assert canvas.itemcget(points.points[str(14)]["blade1"],"fill") == "black" + assert canvas.itemcget(points.points[str(14)]["blade2"],"fill") == "blue" + assert canvas.itemcget(points.points[str(14)]["route1"],"fill") == "black" + assert canvas.itemcget(points.points[str(14)]["route2"],"fill") == "blue" print("Library Tests - reset_point_colour - will generate 2 errors:") points.reset_point_colour("10") # Point ID not an int points.reset_point_colour(20) # Point ID does not exist @@ -870,13 +924,13 @@ def run_line_library_tests(): print("Library Tests - create_line - will generate 4 errors:") assert len(lines.lines) == 0 lines.create_line(canvas, 10, 100, 100, 200, 100, arrow_type=[20,20,5], arrow_ends=0, colour="red") # success - lines.create_line(canvas, 11, 100, 150, 200, 150, arrow_type=[20,20,5], arrow_ends=1) # success - lines.create_line(canvas, 12, 100, 200, 200, 200, arrow_type=[20,20,5], arrow_ends=2) # success - lines.create_line(canvas, 13, 100, 250, 200, 250, arrow_type=[20,20,5], arrow_ends=3) # success - lines.create_line(canvas, 14, 100, 300, 200, 300, arrow_type=[1,1,1], arrow_ends=0) # success - lines.create_line(canvas, 15, 100, 350, 200, 350, arrow_type=[1,1,1], arrow_ends=1) # success - lines.create_line(canvas, 16, 100, 400, 200, 400, arrow_type=[1,1,1], arrow_ends=2) # success - lines.create_line(canvas, 17, 100, 450, 200, 450, arrow_type=[1,1,1], arrow_ends=3) # success + lines.create_line(canvas, 11, 100, 150, 200, 150, arrow_type=[20,20,5], arrow_ends=1) # success + lines.create_line(canvas, 12, 100, 200, 200, 200, arrow_type=[20,20,5], arrow_ends=2) # success + lines.create_line(canvas, 13, 100, 250, 200, 250, arrow_type=[20,20,5], arrow_ends=3) # success + lines.create_line(canvas, 14, 100, 300, 200, 300, arrow_type=[1,1,1], arrow_ends=0) # success + lines.create_line(canvas, 15, 100, 350, 200, 350, arrow_type=[1,1,1], arrow_ends=1) # success + lines.create_line(canvas, 16, 100, 400, 200, 400, arrow_type=[1,1,1], arrow_ends=2) # success + lines.create_line(canvas, 17, 100, 450, 200, 450, arrow_type=[1,1,1], arrow_ends=3, selected=True) # success lines.create_line(canvas, "18", 100, 100, 200, 100) # Fail (ID not an int) lines.create_line(canvas, 0, 100, 100, 200, 100) # Fail (ID < 1) lines.create_line(canvas, 1000, 100, 100, 200, 100) # Fail (ID > 999) @@ -891,6 +945,10 @@ def run_line_library_tests(): assert canvas.coords(lines.lines[str(10)]["line"]) == [100, 100, 200, 100] lines.move_line_end_1(10, 300, 200) assert canvas.coords(lines.lines[str(10)]["line"]) == [400, 300, 200, 100] + lines.move_line_end_1(10, -200, 200) + assert canvas.coords(lines.lines[str(10)]["line"]) == [200, 500, 200, 100] + lines.move_line_end_1(10, 200, -200) + assert canvas.coords(lines.lines[str(10)]["line"]) == [400, 300, 200, 100] lines.move_line_end_1("10",100,100) # Error - not an int (exists) lines.move_line_end_1(20,100,100) # Error - does not exist print("Library Tests - move_line_end_2 - will generate 2 errors:") @@ -932,16 +990,177 @@ def run_line_library_tests(): print("") return() +#--------------------------------------------------------------------------------------------------------- +# Test 'Button' Library objects +#--------------------------------------------------------------------------------------------------------- + +def selected_callback(button_id): + logging_string="Button Selected Callback from Button "+str(button_id) + logging.info(logging_string) + +def deselected_callback(button_id): + logging_string="Button Deselected Callback from Button "+str(button_id) + logging.info(logging_string) + +def run_button_library_tests(): + # Test all functions - including negative tests for parameter validation + print("Library Tests - Button Objects") + # Ensure we start off in Run Mode + buttons.configure_edit_mode(edit_mode=False) + canvas = schematic.canvas + print("Library Tests - create_button - will generate 5 errors:") + buttontype = buttons.button_type.switched + assert len(buttons.buttons) == 0 + buttons.create_button(canvas,0,buttontype,100,100,selected_callback,deselected_callback) # Error - ID out of range + buttons.create_button(canvas,1000,buttontype,100,100,selected_callback,deselected_callback) # Error - ID out of range + buttons.create_button(canvas,"1",buttontype,100,100,selected_callback,deselected_callback) # Error - ID not an int + buttons.create_button(canvas,1,"buttontype",100,100,selected_callback,deselected_callback) # Error - invalid buttontype + buttons.create_button(canvas,1,buttontype,100,100,selected_callback,deselected_callback) # Success + buttons.create_button(canvas,2,buttontype,200,100,selected_callback,deselected_callback) # Success + buttons.create_button(canvas,1,buttontype,200,100,selected_callback,deselected_callback) # Error - ID already Exists + assert len(buttons.buttons) == 2 + print("Library Tests - button_exists - will generate 1 error:") + assert not buttons.button_exists("1") # Error - not an int + assert buttons.button_exists(1) # Success (exists) + assert buttons.button_exists(2) # Success (exists) + assert not buttons.button_exists(3) # Success (does not exist) + print("Library Tests - button_state - will generate 2 errors:") + assert not buttons.button_state("1") # Error - not an int + assert not buttons.button_state(1) # Success (exists) + assert not buttons.button_state(2) # Success (exists) + assert not buttons.button_state(3) # Error - does not exist + print("Library Tests - enable_button, disable_button - will generate 4 errors:") + assert buttons.buttons["1"]["button"]["state"] == "normal" + assert buttons.buttons["2"]["button"]["state"] == "normal" + buttons.disable_button("1") # Error - not an int + buttons.disable_button(3) # Error - does not exist + buttons.disable_button(1) # Success + assert buttons.buttons["1"]["button"]["state"] == "disabled" + assert buttons.buttons["2"]["button"]["state"] == "normal" + buttons.disable_button(2) # Success + assert buttons.buttons["1"]["button"]["state"] == "disabled" + assert buttons.buttons["2"]["button"]["state"] == "disabled" + buttons.enable_button("1") # Error - not an int + buttons.enable_button(3) # Error - does not exist + buttons.enable_button(1) # Success + assert buttons.buttons["1"]["button"]["state"] == "normal" + assert buttons.buttons["2"]["button"]["state"] == "disabled" + buttons.enable_button(2) # Success + assert buttons.buttons["1"]["button"]["state"] == "normal" + assert buttons.buttons["2"]["button"]["state"] == "normal" + print("Library Tests - select button event, button_state - No Errors:") + assert not buttons.button_state(1) + assert not buttons.button_state(2) + buttons.button_event(1) + assert buttons.button_state(1) + assert not buttons.button_state(2) + buttons.button_event(2) + assert buttons.button_state(1) + assert buttons.button_state(2) + print("Library Tests - lock_button and unlock_button - will generate 4 errors:") + buttons.lock_button("1") # Error - not an int + buttons.lock_button(3) # Error - does not exist + buttons.lock_button(1) # Success + assert buttons.buttons["1"]["button"]["state"] == "normal" + buttons.disable_button(1) + assert buttons.buttons["1"]["button"]["state"] == "disabled" + buttons.enable_button(1) + assert buttons.buttons["1"]["button"]["state"] == "disabled" + buttons.unlock_button("1") # Error - not an int + buttons.unlock_button(3) # Error - does not exist + buttons.unlock_button(1) # Success + assert buttons.buttons["1"]["button"]["state"] == "disabled" + buttons.enable_button(1) + assert buttons.buttons["1"]["button"]["state"] == "normal" + print("Library Tests - deselect button event, button_state - No Errors:") + buttons.button_event(1) + assert not buttons.button_state(1) + assert buttons.button_state(2) + buttons.button_event(2) + assert not buttons.button_state(1) + assert not buttons.button_state(2) + print("Library Tests - toggle_button ON - also button_state - will generate 2 errors") + buttons.toggle_button("1") # Error - not an int + buttons.toggle_button(3) # Error - does not exist + buttons.toggle_button(1) + assert buttons.button_state(1) + assert not buttons.button_state(2) + buttons.toggle_button(2) + assert buttons.button_state(1) + assert buttons.button_state(2) + print("Library Tests - toggle_button OFF - also button_state, button_enabled - No Errors") + buttons.toggle_button(1) + assert not buttons.button_state(1) + assert buttons.button_state(2) + buttons.toggle_button(2) + assert not buttons.button_state(1) + assert not buttons.button_state(2) + print("Library Tests - configure_edit_mode - Creation in Edit Mode - No errors:") + buttons.configure_edit_mode(edit_mode=True) + buttons.create_button(canvas,3,buttontype,300,100,selected_callback,deselected_callback) # Success + assert len(buttons.buttons) == 3 + assert buttons.button_exists(3) + print("Library Tests - configure_edit_mode - Toggling between Run and Edit Mode - No errors:") + buttons.configure_edit_mode(edit_mode=False) + buttons.configure_edit_mode(edit_mode=True) + buttons.configure_edit_mode(edit_mode=False) + print("Library Tests - delete_button - will generate 2 errors:") + buttons.delete_button("1") # Error - not an int + buttons.delete_button(4) # Error - does not exist + assert len(buttons.buttons) == 3 + buttons.delete_button(1) + buttons.delete_button(2) + assert not buttons.button_exists(1) + assert not buttons.button_exists(2) + assert buttons.button_exists(3) + assert len(buttons.buttons) == 1 + buttons.delete_button(3) + assert len(buttons.buttons) == 0 + print("Library Tests - momentary_buttons - No errors:") + buttontype = buttons.button_type.momentary + buttons.create_button(canvas,4,buttontype,300,100,selected_callback,deselected_callback) + assert len(buttons.buttons) == 1 + assert buttons.button_exists(4) + assert not buttons.button_state(4) + assert buttons.buttons["4"]["button"]["state"] == "normal" + buttons.disable_button(4) + assert buttons.buttons["4"]["button"]["state"] == "disabled" + buttons.enable_button(4) + assert buttons.buttons["4"]["button"]["state"] == "normal" + assert not buttons.button_state(4) + buttons.button_event(4) + assert not buttons.button_state(4) + buttons.lock_button(4) + assert buttons.buttons["4"]["button"]["state"] == "normal" + buttons.disable_button(4) + assert buttons.buttons["4"]["button"]["state"] == "disabled" + buttons.enable_button(4) + assert buttons.buttons["4"]["button"]["state"] == "disabled" + buttons.unlock_button(4) + assert buttons.buttons["4"]["button"]["state"] == "disabled" + buttons.enable_button(4) + assert buttons.buttons["4"]["button"]["state"] == "normal" + assert not buttons.button_state(4) + buttons.toggle_button(4) + assert not buttons.button_state(4) + buttons.delete_button(4) + assert len(buttons.buttons) == 0 + print("----------------------------------------------------------------------------------------") + print("") + return() + #--------------------------------------------------------------------------------------------------------- # Run all library Tests #--------------------------------------------------------------------------------------------------------- def run_all_basic_library_tests(): + run_text_box_library_tests() run_track_sensor_library_tests() run_track_section_library_tests() run_point_library_tests() run_instrument_library_tests() run_line_library_tests() + run_button_library_tests() if __name__ == "__main__": system_test_harness.start_application(run_all_basic_library_tests) diff --git a/system_tests/basic_library_tests3.py b/system_tests/basic_library_tests3.py index c83b2140..1a1d5289 100644 --- a/system_tests/basic_library_tests3.py +++ b/system_tests/basic_library_tests3.py @@ -49,20 +49,20 @@ def run_library_api_tests(): create_colour_light_signal(canvas, 1, signals.signal_subtype.home, 100, 100, sig_switched, sub_switched, sig_released, sig_passed, sig_updated, has_subsidary=True) # Success create_colour_light_signal(canvas, "2", signals.signal_subtype.home, 100, 100, - sig_switched, sub_switched, sig_released, sig_passed, sig_updated) # Error - not an int + sig_switched, sub_switched, sig_released, sig_passed, sig_updated) # Error - not an int create_colour_light_signal(canvas, 0, signals.signal_subtype.home, 100, 100, - sig_switched, sub_switched, sig_released, sig_passed, sig_updated) # Error - out of range + sig_switched, sub_switched, sig_released, sig_passed, sig_updated) # Error - out of range create_colour_light_signal(canvas, 1000, signals.signal_subtype.home, 100, 100, - sig_switched, sub_switched, sig_released, sig_passed, sig_updated) # Error - out of range + sig_switched, sub_switched, sig_released, sig_passed, sig_updated) # Error - out of range create_colour_light_signal(canvas, 1, signals.signal_subtype.home, 100, 100, - sig_switched, sub_switched, sig_released, sig_passed, sig_updated) # Error - already exists + sig_switched, sub_switched, sig_released, sig_passed, sig_updated) # Error - already exists create_colour_light_signal(canvas, 2, signals.signal_type.colour_light, 100, 100, sig_switched, sub_switched, sig_released, sig_passed, sig_updated) # Error - invalid subtype create_colour_light_signal(canvas, 3, signals.signal_subtype.home, 100, 100, sig_switched, sub_switched, sig_released, sig_passed, sig_updated, - lhfeather45=True, theatre_route_indicator=True) # Error - Feathers and theatre + lhfeather45=True, theatre_route_indicator=True) # Error - Feathers and theatre create_colour_light_signal(canvas, 4, signals.signal_subtype.distant, 100, 100, - sig_switched, sub_switched, sig_released, sig_passed, sig_updated, has_subsidary=True) # Error - Dist & subsidary + sig_switched, sub_switched, sig_released, sig_passed, sig_updated, has_subsidary=True) # Error - Dist & subsidary create_colour_light_signal(canvas, 5, signals.signal_subtype.distant, 100, 100, sig_switched, sub_switched, sig_released, sig_passed, sig_updated, lhfeather45=True) # Error - Dist & Feathers create_colour_light_signal(canvas, 6, signals.signal_subtype.distant, 100, 100, @@ -231,25 +231,24 @@ def run_library_api_tests(): # Create a fully automatic signal for this test (to fully excersise the code) create_colour_light_signal(canvas, 10, signals.signal_subtype.distant, 100, 250, sig_switched, sub_switched, sig_released, sig_passed, sig_updated, fully_automatic=True) - assert not signals.signals["1"]["siglocked"] - assert not signals.signals["10"]["siglocked"] + assert not signals.signal_locked(1) + assert not signals.signal_locked(10) signals.lock_signal(1) signals.lock_signal(1) signals.lock_signal(10) # Warning as signal 10 is automatic so created OFF - assert signals.signals["1"]["siglocked"] - assert signals.signals["10"]["siglocked"] + assert signals.signal_locked(1) + assert signals.signal_locked(10) signals.lock_signal("2") # Error - not an int signals.lock_signal(6) # Error - does not exist # Test locking of a signal that is off (also tests toggle_signal) - assert not signals.signals["2"]["siglocked"] - assert not signals.signals["2"]["sigclear"] + assert not signals.signal_locked(2) + assert not signals.signal_clear(2) signals.toggle_signal(2) # Toggle to OFF - assert signals.signals["2"]["sigclear"] + assert signals.signal_clear(2) signals.lock_signal(2) # Warning - signal is OFF - locking anyway - assert signals.signals["2"]["siglocked"] print("Library Tests - unlock_signal (also tests toggle_signal) - will generate 2 errors:") - assert signals.signals["1"]["siglocked"] - assert signals.signals["2"]["siglocked"] + assert signals.signal_locked(1) + assert signals.signal_locked(2) signals.unlock_signal(1) signals.unlock_signal(1) signals.unlock_signal(10) @@ -257,33 +256,33 @@ def run_library_api_tests(): signals.unlock_signal("2") # Error - not an int signals.unlock_signal(6) # Error - does not exist signals.toggle_signal(2) # Toggle to ON (revert to normal) - assert not signals.signals["1"]["siglocked"] - assert not signals.signals["10"]["siglocked"] - assert not signals.signals["2"]["siglocked"] - assert not signals.signals["2"]["sigclear"] + assert not signals.signal_locked(1) + assert not signals.signal_locked(2) + assert not signals.signal_locked(10) + assert not signals.signal_clear(2) # Delete the automatic signal we specifically created for this test signals.delete_signal(10) print("Library Tests - lock_subsidary (also tests toggle_subsidary) - will generate 3 errors and 1 warning:") - assert not signals.signals["1"]["sublocked"] + assert not signals.subsidary_locked(1) signals.lock_subsidary(1) signals.lock_subsidary(1) - assert signals.signals["1"]["sublocked"] + assert signals.subsidary_locked(1) signals.lock_subsidary("2") # Error - not an int signals.lock_subsidary(6) # Error - does not exist # Test locking of a signal that does not have a subsidary - assert not signals.signals["3"]["sublocked"] + assert not signals.subsidary_locked(3) signals.lock_subsidary(3) # Error - signal does not have a subsidary - assert not signals.signals["3"]["sublocked"] + assert not signals.subsidary_locked(3) # Test locking of a subsidary that is OFF signals.unlock_subsidary(1) # Unlock subsidary first (to reset for the test) - assert not signals.signals["1"]["sublocked"] - assert not signals.signals["1"]["subclear"] + assert not signals.subsidary_locked(1) + assert not signals.subsidary_clear(1) signals.toggle_subsidary(1) # Toggle subsidary to OFF - assert signals.signals["1"]["subclear"] + assert signals.subsidary_clear(1) signals.lock_subsidary(1) # Warning - subsidary is OFF - locking anyway assert signals.signals["1"]["sublocked"] print("Library Tests - unlock_subsidary (also tests toggle_subsidary) - will generate 3 errors:") - assert signals.signals["1"]["sublocked"] + assert signals.subsidary_locked(1) signals.unlock_subsidary(1) signals.unlock_subsidary(1) signals.unlock_subsidary("2") # Error - not an int @@ -291,8 +290,8 @@ def run_library_api_tests(): # Test unlocking of a signal that does not have a subsidary signals.unlock_subsidary(3) # Error - signal does not have a subsidary signals.toggle_subsidary(1) # Toggle subsidary to ON (revert to normal) - assert not signals.signals["1"]["sublocked"] - assert not signals.signals["2"]["sigclear"] + assert not signals.subsidary_locked(1) + assert not signals.signal_clear(2) print("Library Tests - set_approach_control - will generate 9 errors:") # Create some additional signals for these tests create_colour_light_signal(canvas, 10, signals.signal_subtype.distant, 100, 250, @@ -425,6 +424,10 @@ def run_library_api_tests(): signals.toggle_subsidary(2) assert not signals.subsidary_clear(1) print("Library Tests - signal_state (validation failures) - will generate 2 errors:") + # Colour light signals do not update their aspects on creation (state defaults to 'None' + # they rely on the 'update_colour_light_signal' function being called + assert signals.signal_state("1") == None # Valid - ID str + signals.update_colour_light_signal(1) # Valid - ID must be an int assert signals.signal_state("1") == signals.signal_state_type.DANGER # Valid - ID str assert signals.signal_state("2") == signals.signal_state_type.DANGER # Valid - ID str assert signals.signal_state("3") == signals.signal_state_type.CAUTION # Valid - ID str @@ -444,9 +447,10 @@ def run_library_api_tests(): signals.set_route("1", route=signals.route_type.MAIN) # Error - not an int signals.set_route(1, route=signals.route_type.NONE) # Error - invalid route signals.set_route(6, route=signals.route_type.MAIN) # Error - does not exist - # Theatre Route indication + # Theatre Route indication (note we need to update the signal aspect after creation create_colour_light_signal(canvas, 10, signals.signal_subtype.home, 100, 250, sig_switched, sub_switched, sig_released, sig_passed, sig_updated, theatre_route_indicator=True) + signals.update_colour_light_signal(10) assert signals.signals["10"]["hastheatre"] assert signals.signals["10"]["theatretext"] == "" assert not signals.signals["10"]["theatreenabled"] @@ -454,10 +458,12 @@ def run_library_api_tests(): assert signals.signals["10"]["theatretext"] == "1" assert not signals.signals["10"]["theatreenabled"] signals.toggle_signal(10) + signals.update_colour_light_signal(10) assert signals.signals["10"]["theatreenabled"] signals.set_route(10, theatre_text="2") assert signals.signals["10"]["theatretext"] == "2" signals.toggle_signal(10) + signals.update_colour_light_signal(10) assert not signals.signals["10"]["theatreenabled"] signals.delete_signal(10) signals.set_route(1, theatre_text="1") # Error - does not have a theatre @@ -563,6 +569,17 @@ def run_timed_signal_tests(): sig_switched, sub_switched, sig_released, sig_passed, sig_updated) create_semaphore_signal(canvas, 7, signals.semaphore_subtype.distant, 850, 100, sig_switched, sub_switched, sig_released, sig_passed, sig_updated) + # Note that colour light signals need to be updated to display the correct aspect + assert signals.signal_state(1) == None + assert signals.signal_state(2) == None + assert signals.signal_state(3) == None + assert signals.signal_state(4) == None + assert signals.signal_state(5) == None + signals.update_colour_light_signal(1) + signals.update_colour_light_signal(2) + signals.update_colour_light_signal(3) + signals.update_colour_light_signal(4) + signals.update_colour_light_signal(5) assert signals.signal_state(1) == signals.signal_state_type.DANGER assert signals.signal_state(2) == signals.signal_state_type.DANGER assert signals.signal_state(3) == signals.signal_state_type.DANGER @@ -577,6 +594,11 @@ def run_timed_signal_tests(): signals.toggle_signal(5) signals.toggle_signal(6) signals.toggle_signal(7) + signals.update_colour_light_signal(1) + signals.update_colour_light_signal(2) + signals.update_colour_light_signal(3) + signals.update_colour_light_signal(4) + signals.update_colour_light_signal(5) assert signals.signal_state(1) == signals.signal_state_type.PROCEED assert signals.signal_state(2) == signals.signal_state_type.PROCEED assert signals.signal_state(3) == signals.signal_state_type.CAUTION @@ -689,7 +711,17 @@ def run_signal_aspect_tests(): create_ground_position_signal(canvas, 11, signals.ground_pos_subtype.early_shunt_ahead, 400, 200, sig_switched, sig_passed) create_ground_disc_signal(canvas, 12, signals.ground_disc_subtype.standard, 500, 200, sig_switched, sig_passed) create_ground_disc_signal(canvas, 13, signals.ground_disc_subtype.shunt_ahead, 600, 200, sig_switched, sig_passed) - # All signals ON + # All signals ON (note that colour light signals need to be updated to display the correct aspect + assert signals.signal_state(1) == None + assert signals.signal_state(2) == None + assert signals.signal_state(3) == None + assert signals.signal_state(4) == None + assert signals.signal_state(5) == None + signals.update_colour_light_signal(1) + signals.update_colour_light_signal(2) + signals.update_colour_light_signal(3) + signals.update_colour_light_signal(4) + signals.update_colour_light_signal(5) assert signals.signal_state(1) == signals.signal_state_type.DANGER assert signals.signal_state(2) == signals.signal_state_type.DANGER assert signals.signal_state(3) == signals.signal_state_type.DANGER @@ -717,6 +749,11 @@ def run_signal_aspect_tests(): signals.toggle_signal(11) signals.toggle_signal(12) signals.toggle_signal(13) + signals.update_colour_light_signal(1) + signals.update_colour_light_signal(2) + signals.update_colour_light_signal(3) + signals.update_colour_light_signal(4) + signals.update_colour_light_signal(5) assert signals.signal_state(1) == signals.signal_state_type.PROCEED assert signals.signal_state(2) == signals.signal_state_type.PROCEED assert signals.signal_state(3) == signals.signal_state_type.CAUTION @@ -744,6 +781,11 @@ def run_signal_aspect_tests(): signals.set_signal_override(11) signals.set_signal_override(12) signals.set_signal_override(13) + signals.update_colour_light_signal(1) + signals.update_colour_light_signal(2) + signals.update_colour_light_signal(3) + signals.update_colour_light_signal(4) + signals.update_colour_light_signal(5) assert signals.signal_state(1) == signals.signal_state_type.DANGER assert signals.signal_state(2) == signals.signal_state_type.DANGER assert signals.signal_state(3) == signals.signal_state_type.DANGER @@ -770,6 +812,11 @@ def run_signal_aspect_tests(): signals.clear_signal_override(11) signals.clear_signal_override(12) signals.clear_signal_override(13) + signals.update_colour_light_signal(1) + signals.update_colour_light_signal(2) + signals.update_colour_light_signal(3) + signals.update_colour_light_signal(4) + signals.update_colour_light_signal(5) assert signals.signal_state(1) == signals.signal_state_type.PROCEED assert signals.signal_state(2) == signals.signal_state_type.PROCEED assert signals.signal_state(3) == signals.signal_state_type.CAUTION @@ -789,6 +836,10 @@ def run_signal_aspect_tests(): signals.set_signal_override_caution(3) signals.set_signal_override_caution(4) signals.set_signal_override_caution(7) + signals.update_colour_light_signal(1) + signals.update_colour_light_signal(2) + signals.update_colour_light_signal(3) + signals.update_colour_light_signal(4) assert signals.signal_state(1) == signals.signal_state_type.CAUTION assert signals.signal_state(2) == signals.signal_state_type.CAUTION assert signals.signal_state(3) == signals.signal_state_type.CAUTION @@ -799,6 +850,10 @@ def run_signal_aspect_tests(): signals.clear_signal_override_caution(3) signals.clear_signal_override_caution(4) signals.clear_signal_override_caution(7) + signals.update_colour_light_signal(1) + signals.update_colour_light_signal(2) + signals.update_colour_light_signal(3) + signals.update_colour_light_signal(4) assert signals.signal_state(1) == signals.signal_state_type.PROCEED assert signals.signal_state(2) == signals.signal_state_type.PROCEED assert signals.signal_state(3) == signals.signal_state_type.CAUTION @@ -810,6 +865,10 @@ def run_signal_aspect_tests(): signals.set_approach_control(3,release_on_yellow=False) signals.set_approach_control(5,release_on_yellow=False) signals.set_approach_control(6,release_on_yellow=False) + signals.update_colour_light_signal(1) + signals.update_colour_light_signal(2) + signals.update_colour_light_signal(3) + signals.update_colour_light_signal(5) assert signals.signal_state(1) == signals.signal_state_type.DANGER assert signals.signal_state(2) == signals.signal_state_type.DANGER assert signals.signal_state(3) == signals.signal_state_type.DANGER @@ -820,6 +879,10 @@ def run_signal_aspect_tests(): signals.clear_approach_control(3) signals.clear_approach_control(5) signals.clear_approach_control(6) + signals.update_colour_light_signal(1) + signals.update_colour_light_signal(2) + signals.update_colour_light_signal(3) + signals.update_colour_light_signal(5) assert signals.signal_state(1) == signals.signal_state_type.PROCEED assert signals.signal_state(2) == signals.signal_state_type.PROCEED assert signals.signal_state(3) == signals.signal_state_type.CAUTION @@ -828,10 +891,14 @@ def run_signal_aspect_tests(): # Signals subject to 'Release on Yellow' approach control signals.set_approach_control(1,release_on_yellow=True) signals.set_approach_control(2,release_on_yellow=True) + signals.update_colour_light_signal(1) + signals.update_colour_light_signal(2) assert signals.signal_state(1) == signals.signal_state_type.CAUTION_APP_CNTL assert signals.signal_state(2) == signals.signal_state_type.CAUTION_APP_CNTL signals.clear_approach_control(1) signals.clear_approach_control(2) + signals.update_colour_light_signal(1) + signals.update_colour_light_signal(2) assert signals.signal_state(1) == signals.signal_state_type.PROCEED assert signals.signal_state(2) == signals.signal_state_type.PROCEED # Slotting for secondary distant semaphore signals @@ -1201,10 +1268,12 @@ def run_approach_control_tests(): # Set up the initial test conditions (for Approach Control Release on Red) signals.toggle_signal(10) signals.toggle_signal(11) + signals.update_colour_light_signal(10) assert signals.signal_state(10) == signals.signal_state_type.PROCEED assert signals.signal_state(11) == signals.signal_state_type.PROCEED signals.set_approach_control(10, release_on_yellow=False, force_set=False) signals.set_approach_control(11, release_on_yellow=False, force_set=False) + signals.update_colour_light_signal(10) assert signals.signals["10"]["releaseonred"] assert signals.signals["11"]["releaseonred"] assert not signals.signals["10"]["released"] @@ -1214,6 +1283,7 @@ def run_approach_control_tests(): # Test the signals are released on signal approach events signals.approach_release_button_event(10) signals.approach_release_button_event(11) + signals.update_colour_light_signal(10) assert not signals.signals["10"]["releaseonred"] assert not signals.signals["11"]["releaseonred"] assert signals.signals["10"]["released"] @@ -1223,6 +1293,7 @@ def run_approach_control_tests(): # Test that approach control cannot normally be reset between approach and passed events signals.set_approach_control(10, release_on_yellow=False, force_set=False) signals.set_approach_control(11, release_on_yellow=False, force_set=False) + signals.update_colour_light_signal(10) assert not signals.signals["10"]["releaseonred"] assert not signals.signals["11"]["releaseonred"] assert signals.signals["10"]["released"] @@ -1232,10 +1303,12 @@ def run_approach_control_tests(): # Test that approach control can be normally reset after a signal passed event signals.sig_passed_button_event(10) signals.sig_passed_button_event(11) + signals.update_colour_light_signal(10) assert not signals.signals["10"]["released"] assert not signals.signals["11"]["released"] signals.set_approach_control(10, release_on_yellow=False, force_set=False) signals.set_approach_control(11, release_on_yellow=False, force_set=False) + signals.update_colour_light_signal(10) assert not signals.signals["10"]["released"] assert not signals.signals["11"]["released"] assert signals.signals["10"]["releaseonred"] @@ -1245,6 +1318,7 @@ def run_approach_control_tests(): # Test that approach control can be 'force set' between approach and release events signals.approach_release_button_event(10) signals.approach_release_button_event(11) + signals.update_colour_light_signal(10) assert signals.signals["10"]["released"] assert signals.signals["11"]["released"] assert not signals.signals["10"]["releaseonred"] @@ -1253,17 +1327,20 @@ def run_approach_control_tests(): assert signals.signal_state(11) == signals.signal_state_type.PROCEED signals.set_approach_control(10, release_on_yellow=False, force_set=True) signals.set_approach_control(11, release_on_yellow=False, force_set=True) + signals.update_colour_light_signal(10) assert not signals.signals["10"]["released"] assert not signals.signals["11"]["released"] assert signals.signals["10"]["releaseonred"] assert signals.signals["11"]["releaseonred"] assert signals.signal_state(10) == signals.signal_state_type.DANGER assert signals.signal_state(11) == signals.signal_state_type.DANGER + signals.update_colour_light_signal(10) # Put everything back to normal for the next test signals.approach_release_button_event(10) signals.approach_release_button_event(11) signals.sig_passed_button_event(10) signals.sig_passed_button_event(11) + signals.update_colour_light_signal(10) assert not signals.signals["10"]["releaseonred"] assert not signals.signals["11"]["releaseonred"] assert signals.signal_state(10) == signals.signal_state_type.PROCEED @@ -1273,32 +1350,39 @@ def run_approach_control_tests(): # Set up the initial test conditions (for Approach Control Release on Yellow) # We can only test this for colour light signals (not supported by semaphores) signals.set_approach_control(10, release_on_yellow=True, force_set=False) + signals.update_colour_light_signal(10) assert signals.signals["10"]["releaseonyel"] assert not signals.signals["10"]["released"] assert signals.signal_state(10) == signals.signal_state_type.CAUTION_APP_CNTL # Test the signal is released on signal approach events signals.approach_release_button_event(10) + signals.update_colour_light_signal(10) assert not signals.signals["10"]["releaseonyel"] assert signals.signals["10"]["released"] assert signals.signal_state(10) == signals.signal_state_type.PROCEED # Test that approach control cannot normally be reset between approach and passed events signals.set_approach_control(10, release_on_yellow=True, force_set=False) + signals.update_colour_light_signal(10) assert not signals.signals["10"]["releaseonyel"] assert signals.signals["10"]["released"] assert signals.signal_state(10) == signals.signal_state_type.PROCEED # Test that approach control can be normally reset after a signal passed event signals.sig_passed_button_event(10) + signals.update_colour_light_signal(10) assert not signals.signals["10"]["released"] signals.set_approach_control(10, release_on_yellow=True, force_set=False) + signals.update_colour_light_signal(10) assert not signals.signals["10"]["released"] assert signals.signals["10"]["releaseonyel"] assert signals.signal_state(10) == signals.signal_state_type.CAUTION_APP_CNTL # Test that approach control can be 'force set' between approach and release events signals.approach_release_button_event(10) + signals.update_colour_light_signal(10) assert signals.signals["10"]["released"] assert not signals.signals["10"]["releaseonyel"] assert signals.signal_state(10) == signals.signal_state_type.PROCEED signals.set_approach_control(10, release_on_yellow=True, force_set=True) + signals.update_colour_light_signal(10) assert not signals.signals["10"]["released"] assert signals.signals["10"]["releaseonyel"] assert signals.signal_state(10) == signals.signal_state_type.CAUTION_APP_CNTL diff --git a/system_tests/system_test_harness.py b/system_tests/system_test_harness.py index 7826e2c4..1de2215e 100644 --- a/system_tests/system_test_harness.py +++ b/system_tests/system_test_harness.py @@ -216,7 +216,7 @@ def start_application(callback_function): common.shutdown() def run_function(test_function, delay:float=thread_delay_time): - common.root_window.after(0, test_function) + common.execute_function_in_tkinter_thread(test_function) sleep(delay) # ------------------------------------------------------------------------------ @@ -783,7 +783,7 @@ def assert_buttons_enabled(*buttonids): for buttonid in buttonids: if str(buttonid) not in buttons.buttons.keys(): raise_test_warning ("assert_routes_enabled - Route: "+str(buttonid)+" does not exist") - elif not buttons.button_enabled(buttonid): + elif buttons.buttons[str(buttonid)]["button"]["state"] == "disabled": raise_test_error ("assert_routes_enabled - Route: "+str(buttonid)+" - Test Fail") increment_tests_executed() @@ -791,7 +791,7 @@ def assert_buttons_disabled(*buttonids): for buttonid in buttonids: if str(buttonid) not in buttons.buttons.keys(): raise_test_warning ("assert_routes_disabled - Route: "+str(buttonid)+" does not exist") - elif buttons.button_enabled(buttonid): + elif buttons.buttons[str(buttonid)]["button"]["state"] == "normal": raise_test_error ("assert_routes_disabled - Route: "+str(buttonid)+" - Test Fail") increment_tests_executed() diff --git a/system_tests/test_mqtt_networking.py b/system_tests/test_mqtt_networking.py index 8e9be018..2d1d5870 100644 --- a/system_tests/test_mqtt_networking.py +++ b/system_tests/test_mqtt_networking.py @@ -364,8 +364,8 @@ def run_object_deletion_tests(delay:float=0.0): def run_all_mqtt_networking_tests(delay:float=0.0): initialise_test_harness(filename="./test_mqtt_networking.sig") # Edit/save all schematic objects to give confidence that editing doesn't break the layout configuration -# set_edit_mode() -# test_object_edit_windows.test_all_object_edit_windows(delay) + set_edit_mode() + test_object_edit_windows.test_all_object_edit_windows(delay) set_run_mode() run_basic_networking_tests(delay) run_remote_track_sensor_tests(delay) diff --git a/system_tests/test_mqtt_networking.sig b/system_tests/test_mqtt_networking.sig index 50e24e16..9cdfc455 100644 --- a/system_tests/test_mqtt_networking.sig +++ b/system_tests/test_mqtt_networking.sig @@ -21,9 +21,10 @@ }, "objects": { "07ac0721-6451-43db-b2a4-b114a516d0e5": { - "bbox": 12679, + "bbox": 12988, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 7, "mirror": "", @@ -38,7 +39,7 @@ 0, 0 ], - "bbox": 12685, + "bbox": 12994, "colour": "black", "endx": 850.0, "endy": 475.0, @@ -56,7 +57,7 @@ 20, 5 ], - "bbox": 12691, + "bbox": 13000, "colour": "black", "endx": 350.0, "endy": 200.0, @@ -74,7 +75,7 @@ 20, 5 ], - "bbox": 12697, + "bbox": 13006, "colour": "black", "endx": 350.0, "endy": 75.0, @@ -92,7 +93,7 @@ 0, 0 ], - "bbox": 12703, + "bbox": 13012, "colour": "black", "endx": 575.0, "endy": 150.0, @@ -104,9 +105,10 @@ "tags": "line28" }, "27f5b927-ee11-4e85-b555-08e086cc5799": { - "bbox": 12707, + "bbox": 13016, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 2, "mirror": "Box1-1", @@ -115,9 +117,10 @@ "tags": "section2" }, "2c567416-9c96-4f83-93e4-6071250044f3": { - "bbox": 12711, + "bbox": 13020, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 8, "mirror": "", @@ -132,7 +135,7 @@ 20, 5 ], - "bbox": 12717, + "bbox": 13026, "colour": "black", "endx": 575.0, "endy": 400.0, @@ -155,7 +158,7 @@ false, "" ], - "bbox": 12743, + "bbox": 13052, "dccaspects": [ [ [ @@ -1020,7 +1023,7 @@ 20, 5 ], - "bbox": 12749, + "bbox": 13058, "colour": "black", "endx": 575.0, "endy": 200.0, @@ -1038,7 +1041,7 @@ 20, 5 ], - "bbox": 12755, + "bbox": 13064, "colour": "black", "endx": 575.0, "endy": 75.0, @@ -1061,7 +1064,7 @@ false, "" ], - "bbox": 12772, + "bbox": 13081, "dccaspects": [ [ [ @@ -1920,9 +1923,10 @@ ] }, "5976fd88-31f9-49df-af5b-6b6e7758be00": { - "bbox": 12776, + "bbox": 13085, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 9, "mirror": "", @@ -1942,7 +1946,7 @@ false, "" ], - "bbox": 12793, + "bbox": 13102, "dccaspects": [ [ [ @@ -2812,7 +2816,7 @@ false, "" ], - "bbox": 12810, + "bbox": 13119, "dccaspects": [ [ [ @@ -3682,7 +3686,7 @@ false, "" ], - "bbox": 12827, + "bbox": 13136, "dccaspects": [ [ [ @@ -4552,7 +4556,7 @@ false, "" ], - "bbox": 12876, + "bbox": 13185, "dccaspects": [ [ [ @@ -5417,7 +5421,7 @@ 0, 0 ], - "bbox": 12882, + "bbox": 13191, "colour": "black", "endx": 225.0, "endy": 200.0, @@ -5429,7 +5433,7 @@ "tags": "line26" }, "70fc3e04-4419-42d9-83fe-295ca1e55b72": { - "bbox": 12897, + "bbox": 13206, "bellsound": "bell-ring-01.wav", "item": "instrument", "itemid": 2, @@ -5452,7 +5456,7 @@ false, "" ], - "bbox": 12923, + "bbox": 13232, "dccaspects": [ [ [ @@ -6322,7 +6326,7 @@ false, "" ], - "bbox": 12940, + "bbox": 13249, "dccaspects": [ [ [ @@ -7181,9 +7185,10 @@ ] }, "85be3b55-420f-4776-952c-d1472c4f897b": { - "bbox": 12944, + "bbox": 13253, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 4, "mirror": "2", @@ -7194,7 +7199,7 @@ "93063a54-5b09-4045-985b-7ba45650ff68": { "alsoswitch": 0, "automatic": false, - "bbox": 12950, + "bbox": 13259, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -7224,9 +7229,10 @@ "ybuttonoffset": 0 }, "9a0ce7c0-d387-449f-90b7-ec13375d981a": { - "bbox": 12954, + "bbox": 13263, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 3, "mirror": "1", @@ -7241,7 +7247,7 @@ 20, 5 ], - "bbox": 12960, + "bbox": 13269, "colour": "black", "endx": 350.0, "endy": 300.0, @@ -7264,7 +7270,7 @@ false, "" ], - "bbox": 12986, + "bbox": 13295, "dccaspects": [ [ [ @@ -8134,7 +8140,7 @@ false, "" ], - "bbox": 13003, + "bbox": 13312, "dccaspects": [ [ [ @@ -8999,7 +9005,7 @@ 20, 5 ], - "bbox": 13009, + "bbox": 13318, "colour": "black", "endx": 275.0, "endy": 150.0, @@ -9022,7 +9028,7 @@ true, "" ], - "bbox": 13036, + "bbox": 13345, "dccaspects": [ [ [ @@ -9892,7 +9898,7 @@ false, "" ], - "bbox": 13062, + "bbox": 13371, "dccaspects": [ [ [ @@ -10751,7 +10757,7 @@ ] }, "c488e27b-7a77-49dc-8045-066fb3109702": { - "bbox": 13077, + "bbox": 13386, "bellsound": "bell-ring-01.wav", "item": "instrument", "itemid": 1, @@ -10774,7 +10780,7 @@ false, "" ], - "bbox": 13103, + "bbox": 13412, "dccaspects": [ [ [ @@ -11639,7 +11645,7 @@ 20, 5 ], - "bbox": 13109, + "bbox": 13418, "colour": "black", "endx": 300.0, "endy": 150.0, @@ -11651,9 +11657,10 @@ "tags": "line29" }, "ce73144a-7678-497d-ba28-a42c4c314f86": { - "bbox": 13113, + "bbox": 13422, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 5, "mirror": "", @@ -11673,7 +11680,7 @@ false, "" ], - "bbox": 13139, + "bbox": 13448, "dccaspects": [ [ [ @@ -12532,9 +12539,10 @@ ] }, "d538187e-fba1-4825-9cad-3d7df730bbf3": { - "bbox": 13143, + "bbox": 13452, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 6, "mirror": "", @@ -12549,7 +12557,7 @@ 20, 5 ], - "bbox": 13149, + "bbox": 13458, "colour": "black", "endx": 350.0, "endy": 400.0, @@ -12567,7 +12575,7 @@ 20, 5 ], - "bbox": 13155, + "bbox": 13464, "colour": "black", "endx": 575.0, "endy": 300.0, @@ -12579,9 +12587,10 @@ "tags": "line3" }, "f498f46c-3819-447e-ac88-2ebe032b0b3d": { - "bbox": 13159, + "bbox": 13468, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 1, "mirror": "Box1-2", @@ -12652,11 +12661,12 @@ }, "general": { "automation": true, + "buttonsize": 8, "editmode": false, "filename": "/home/john/model-railway-signalling/system_tests/test_mqtt_networking.sig", "info": "Document your layout here", "spadpopups": false, - "version": "Version 4.6.1" + "version": "Version 4.7.0" }, "gpio": { "portmappings": [ diff --git a/system_tests/test_object_editing.py b/system_tests/test_object_editing.py index 044da7dc..396a7daf 100644 --- a/system_tests/test_object_editing.py +++ b/system_tests/test_object_editing.py @@ -633,70 +633,160 @@ def test_interlocking_and_overrides(delay,sig1,sig2,sig3,sig4,point1,point2,bloc return() #----------------------------------------------------------------------------------- -# These test the Item ID update functions, specifically: -# Update of point interlocking when Signal ID is changed or deleted -# Update of signal ahead value when Signal ID is changed or deleted -# Update of signal interlocking when Signal ID is changed or deleted -# Update of signal interlocking when Point ID is changed or deleted -# Update of signal interlocking when Instrument ID is changed or deleted -# Update of signal interlocking when Track Section ID is changed or deleted -# Update of signal automation (override) when Section ID is changed or deleted -# Update of Track Sensor route tables to reflect change of Point ID -# Update of Track Sensor route tables to reflect deletion of Point -# Update of Track Sensor route tables to reflect change of section ID -# Update of Track Sensor route tables to reflect deletion of section +# These test the 'one-up' allocation of Item IDs for each object #----------------------------------------------------------------------------------- -def run_change_of_item_id_tests(delay:float=0.0): - print("Change of Item Id tests - 2 warnings will be generated for the Block Instruments") +def run_initial_item_id_allocation_tests(delay:float=0.0): + print("Test initial allocation of Item ID on Item Creation") # Add elements to the layout - sleep(delay) l1 = create_line() - sleep(delay) + l2 = create_line() p1 = create_left_hand_point() - sleep(delay) p2 = create_left_hand_point() - sleep(delay) s1 = create_colour_light_signal() - sleep(delay) s2 = create_colour_light_signal() - sleep(delay) - s3 = create_colour_light_signal() - sleep(delay) - s4 = create_colour_light_signal() - sleep(delay) t1 = create_track_section() - sleep(delay) t2 = create_track_section() - sleep(delay) - t3 = create_track_section() - sleep(delay) ts1 = create_track_sensor() - sleep(delay) + ts2 = create_track_sensor() i1 = create_block_instrument() - sleep(delay) i2 = create_block_instrument() - sleep(delay) + r1 = create_route() + r2 = create_route() + tb1 = create_textbox() + tb2 = create_textbox() + select_and_move_objects(i1,600,200,delay=delay) + select_and_move_objects(i2,800,200,delay=delay) # Test the 'one-up' IDs have been correctly generated assert_object_configuration(l1,{"itemid":1}) + assert_object_configuration(l2,{"itemid":2}) assert_object_configuration(p1,{"itemid":1}) assert_object_configuration(p2,{"itemid":2}) assert_object_configuration(s1,{"itemid":1}) assert_object_configuration(s2,{"itemid":2}) - assert_object_configuration(s3,{"itemid":3}) - assert_object_configuration(s4,{"itemid":4}) assert_object_configuration(t1,{"itemid":1}) assert_object_configuration(t2,{"itemid":2}) - assert_object_configuration(t3,{"itemid":3}) + assert_object_configuration(ts1,{"itemid":1}) + assert_object_configuration(ts2,{"itemid":2}) assert_object_configuration(i1,{"itemid":1}) assert_object_configuration(i2,{"itemid":2}) - assert_object_configuration(ts1,{"itemid":1}) - sleep(delay) - select_and_move_objects(i1,500,200,delay=delay) + assert_object_configuration(r1,{"itemid":1}) + assert_object_configuration(r2,{"itemid":2}) + assert_object_configuration(tb1,{"itemid":1}) + assert_object_configuration(tb2,{"itemid":2}) + # Now delete all the ID=1 and try again + deselect_all_objects() + select_or_deselect_objects(l1,p1,s1,t1,ts1,i1,r1,tb1) + delete_selected_objects() sleep(delay) - select_and_move_objects(i2,800,200,delay=delay) - # Link the 2 block instruments (so we can test the interlocking) + l3 = create_line() + p3 = create_left_hand_point() + s3 = create_colour_light_signal() + t3 = create_track_section() + ts3 = create_track_sensor() + i3 = create_block_instrument() + r3 = create_route() + tb3 = create_textbox() + select_and_move_objects(i3,600,200,delay=delay) + assert_object_configuration(l3,{"itemid":1}) + assert_object_configuration(l2,{"itemid":2}) + assert_object_configuration(p3,{"itemid":1}) + assert_object_configuration(p2,{"itemid":2}) + assert_object_configuration(s3,{"itemid":1}) + assert_object_configuration(s2,{"itemid":2}) + assert_object_configuration(t3,{"itemid":1}) + assert_object_configuration(t2,{"itemid":2}) + assert_object_configuration(ts3,{"itemid":1}) + assert_object_configuration(ts2,{"itemid":2}) + assert_object_configuration(i3,{"itemid":1}) + assert_object_configuration(i2,{"itemid":2}) + assert_object_configuration(r3,{"itemid":1}) + assert_object_configuration(r2,{"itemid":2}) + assert_object_configuration(tb3,{"itemid":1}) + assert_object_configuration(tb2,{"itemid":2}) + # clean up + select_all_objects() + delete_selected_objects() + return() + +#----------------------------------------------------------------------------------- +# These test the 'one-up' allocation of Item IDs for each object +#----------------------------------------------------------------------------------- + +def run_basic_change_of_item_id_tests(delay:float=0.0): + print("Test basic update of Item IDs for all object types") + # Add elements to the layout + l1 = create_line() + p1 = create_left_hand_point() + s1 = create_colour_light_signal() + t1 = create_track_section() + ts1 = create_track_sensor() + i1 = create_block_instrument() + r1 = create_route() + tb1 = create_textbox() + select_and_move_objects(i1,600,200,delay=delay) + # Test the 'one-up' IDs have been correctly generated + assert_object_configuration(l1,{"itemid":1}) + assert_object_configuration(p1,{"itemid":1}) + assert_object_configuration(s1,{"itemid":1}) + assert_object_configuration(t1,{"itemid":1}) + assert_object_configuration(ts1,{"itemid":1}) + assert_object_configuration(i1,{"itemid":1}) + assert_object_configuration(r1,{"itemid":1}) + assert_object_configuration(tb1,{"itemid":1}) + # Now update all the Item IDs and check they have been updated + update_object_configuration(l1,{"itemid":10}) + update_object_configuration(p1,{"itemid":20}) + update_object_configuration(s1,{"itemid":30}) + update_object_configuration(t1,{"itemid":40}) + update_object_configuration(ts1,{"itemid":50}) + update_object_configuration(i1,{"itemid":60}) + update_object_configuration(r1,{"itemid":70}) + update_object_configuration(tb1,{"itemid":80}) + assert_object_configuration(l1,{"itemid":10}) + assert_object_configuration(p1,{"itemid":20}) + assert_object_configuration(s1,{"itemid":30}) + assert_object_configuration(t1,{"itemid":40}) + assert_object_configuration(ts1,{"itemid":50}) + assert_object_configuration(i1,{"itemid":60}) + assert_object_configuration(r1,{"itemid":70}) + assert_object_configuration(tb1,{"itemid":80}) + # clean up + deselect_all_objects() + select_or_deselect_objects(l1, p1,s1,t1,ts1,i1,r1,tb1) + delete_selected_objects() + return() + +#----------------------------------------------------------------------------------- +# These test the Item ID update functions for signals, specifically: +# Update of point interlocking when Signal ID is changed or deleted +# Update of signal ahead value when Signal ID is changed or deleted +# Update of signal interlocking when Signal ID is changed or deleted +# Update of signal interlocking when Point ID is changed or deleted +# Update of signal interlocking when Instrument ID is changed or deleted +# Update of signal interlocking when Track Section ID is changed or deleted +# Update of signal automation (override) when Section ID is changed or deleted +#----------------------------------------------------------------------------------- + +def run_signal_config_update_on_change_of_id_tests(delay:float=0.0): + print("Test update of Signal Configuration on change or delete of Item IDs") + print("Note that 2 warnings will be generated for the Block Instruments") + # Add elements to the layout + p1 = create_left_hand_point() + p2 = create_left_hand_point() + s1 = create_colour_light_signal() + s2 = create_colour_light_signal() + s3 = create_colour_light_signal() + s4 = create_colour_light_signal() + t1 = create_track_section() + t2 = create_track_section() + t3 = create_track_section() + i1 = create_block_instrument() + i2 = create_block_instrument() + # Move the instruments out of the way and link them (so we can test the interlocking) # Note the linked instrument is a string (local or remote) + select_and_move_objects(i1,600,200,delay=delay) + select_and_move_objects(i2,800,200,delay=delay) update_object_configuration(i1,{"linkedto":"2"}) update_object_configuration(i2,{"linkedto":"1"}) # Set up the interlocking tables and the automation tables for Signal 1 @@ -760,25 +850,10 @@ def run_change_of_item_id_tests(delay:float=0.0): [2, [False,True,True,True,True] ] ] } ) assert_object_configuration(p2,{ "siginterlock":[ [1, [True,True,True,True,True] ] ] } ) - # Set up the route tables for Track Sensor 1 - update_object_configuration(ts1,{ - "routeahead":[ - [[[1,True],[1,True],[1,True],[1,True],[2,True],[2,True]],1], - [[[1,True],[1,True],[1,True],[1,True],[2,True],[2,True]],1], - [[[1,True],[1,True],[1,True],[1,True],[2,True],[2,True]],1], - [[[1,True],[1,True],[1,True],[1,True],[2,True],[2,True]],1], - [[[1,True],[1,True],[1,True],[1,True],[2,True],[2,True]],1] ], - "routebehind":[ - [[[1,True],[1,True],[1,True],[1,True],[2,True],[2,True]],1], - [[[1,True],[1,True],[1,True],[1,True],[2,True],[2,True]],1], - [[[1,True],[1,True],[1,True],[1,True],[2,True],[2,True]],1], - [[[1,True],[1,True],[1,True],[1,True],[2,True],[2,True]],1], - [[[1,True],[1,True],[1,True],[1,True],[2,True],[2,True]],1] ] } ) # Test the interlocking and override functionality before changing the item IDs test_interlocking_and_overrides(delay,sig1=1,sig2=2,sig3=3,sig4=4, point1=1,point2=2,block1=1,block2=2,sec1=1,sec2=2,sec3=3) - # Change the IDs of Signal 2, Points 1/2, Instrument 1, Track Section 1 and line 1 - update_object_configuration(l1,{"itemid":51}) + # Change the IDs of the signals, points, track sections and instruments update_object_configuration(s1,{"itemid":11}) update_object_configuration(s2,{"itemid":12}) update_object_configuration(s3,{"itemid":13}) @@ -788,21 +863,8 @@ def run_change_of_item_id_tests(delay:float=0.0): update_object_configuration(t1,{"itemid":31}) update_object_configuration(t2,{"itemid":32}) update_object_configuration(t3,{"itemid":33}) - update_object_configuration(ts1,{"itemid":61}) update_object_configuration(i1,{"itemid":41}) update_object_configuration(i2,{"itemid":42}) - # Test the IDs have been changed - assert_object_configuration(l1,{"itemid":51}) - assert_object_configuration(s1,{"itemid":11}) - assert_object_configuration(s2,{"itemid":12}) - assert_object_configuration(s3,{"itemid":13}) - assert_object_configuration(s4,{"itemid":14}) - assert_object_configuration(p1,{"itemid":21}) - assert_object_configuration(p2,{"itemid":22}) - assert_object_configuration(t1,{"itemid":31}) - assert_object_configuration(t2,{"itemid":32}) - assert_object_configuration(t3,{"itemid":33}) - assert_object_configuration(ts1,{"itemid":61}) # Note the linked instrument is a string (local or remote) assert_object_configuration(i1,{"itemid":41,"linkedto":"42"}) assert_object_configuration(i2,{"itemid":42,"linkedto":"41"}) @@ -848,28 +910,12 @@ def run_change_of_item_id_tests(delay:float=0.0): [12, [False,True,True,True,True] ] ] } ) assert_object_configuration(p2,{ "siginterlock":[ [11, [True,True,True,True,True] ] ] } ) - # Test the route tables have been updated correctly for Track Sensor 61 - assert_object_configuration(ts1,{ - "routeahead":[ - [[[21,True],[21,True],[21,True],[21,True],[22,True],[22,True]],31], - [[[21,True],[21,True],[21,True],[21,True],[22,True],[22,True]],31], - [[[21,True],[21,True],[21,True],[21,True],[22,True],[22,True]],31], - [[[21,True],[21,True],[21,True],[21,True],[22,True],[22,True]],31], - [[[21,True],[21,True],[21,True],[21,True],[22,True],[22,True]],31] ], - "routebehind":[ - [[[21,True],[21,True],[21,True],[21,True],[22,True],[22,True]],31], - [[[21,True],[21,True],[21,True],[21,True],[22,True],[22,True]],31], - [[[21,True],[21,True],[21,True],[21,True],[22,True],[22,True]],31], - [[[21,True],[21,True],[21,True],[21,True],[22,True],[22,True]],31], - [[[21,True],[21,True],[21,True],[21,True],[22,True],[22,True]],31] ] } ) # Test the interlocking and override functionality after changing the item IDs test_interlocking_and_overrides(delay,sig1=11,sig2=12,sig3=13,sig4=14, point1=21,point2=22,block1=41,block2=42,sec1=31,sec2=32,sec3=33) - # Delete Point 21 and test all references have been removed from the Signals and track Sensors + # Delete Point 21 and test all references have been removed from the Signals # Note that the point 22 entries will have shuffled down in the list - sleep(delay) select_single_object(p1) - sleep(delay) delete_selected_objects() # Test the route tables have been updated correctly for Signal 11 assert_object_configuration(s1,{ @@ -879,24 +925,8 @@ def run_change_of_item_id_tests(delay:float=0.0): [[[22,True],[22,True],[0,False],[0,False],[0,False],[0,False]],"14",41], [[[22,True],[22,True],[0,False],[0,False],[0,False],[0,False]],"14",41], [[[22,True],[22,True],[0,False],[0,False],[0,False],[0,False]],"14",41] ] }) - # Test the route tables have been updated correctly for Track Sensor 61 - assert_object_configuration(ts1,{ - "routeahead":[ - [[[22,True],[22,True],[0,False],[0,False],[0,False],[0,False]],31], - [[[22,True],[22,True],[0,False],[0,False],[0,False],[0,False]],31], - [[[22,True],[22,True],[0,False],[0,False],[0,False],[0,False]],31], - [[[22,True],[22,True],[0,False],[0,False],[0,False],[0,False]],31], - [[[22,True],[22,True],[0,False],[0,False],[0,False],[0,False]],31] ], - "routebehind":[ - [[[22,True],[22,True],[0,False],[0,False],[0,False],[0,False]],31], - [[[22,True],[22,True],[0,False],[0,False],[0,False],[0,False]],31], - [[[22,True],[22,True],[0,False],[0,False],[0,False],[0,False]],31], - [[[22,True],[22,True],[0,False],[0,False],[0,False],[0,False]],31], - [[[22,True],[22,True],[0,False],[0,False],[0,False],[0,False]],31] ] } ) - # Delete Point 22 and test all references have been removed from Signal 11 and Track Sensor 61 - sleep(delay) + # Delete Point 22 and test all references have been removed from Signal 11 select_single_object(p2) - sleep(delay) delete_selected_objects() # Test the interlocking tables have been updated correctly for Signal 11 assert_object_configuration(s1,{ @@ -906,24 +936,8 @@ def run_change_of_item_id_tests(delay:float=0.0): [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],"14",41], [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],"14",41], [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],"14",41] ] }) - # Test the route tables have been updated correctly for Track Sensor 61 - assert_object_configuration(ts1,{ - "routeahead":[ - [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],31], - [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],31], - [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],31], - [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],31], - [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],31] ], - "routebehind":[ - [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],31], - [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],31], - [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],31], - [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],31], - [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],31] ] } ) # Delete signal 12 and test all references have been removed from Signal 11 - sleep(delay) select_single_object(s2) - sleep(delay) delete_selected_objects() assert_object_configuration(s1,{ "siginterlock":[ @@ -943,9 +957,7 @@ def run_change_of_item_id_tests(delay:float=0.0): [False,0,0,0], [False,0,0,0] ] } ) # Delete signal 13 and test all references have been removed from Signal 11 - sleep(delay) select_single_object(s3) - sleep(delay) delete_selected_objects() assert_object_configuration(s1,{ "siginterlock":[ [], [], [], [], [], ], @@ -955,9 +967,7 @@ def run_change_of_item_id_tests(delay:float=0.0): [False,0,0,0], [False,0,0,0] ] } ) # Delete signal 14 and test all references have been removed from Signal 11 - sleep(delay) select_single_object(s4) - sleep(delay) delete_selected_objects() assert_object_configuration(s1,{ "pointinterlock":[ @@ -967,9 +977,7 @@ def run_change_of_item_id_tests(delay:float=0.0): [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],"",41], [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],"",41] ] } ) # Delete Instrument 1 and test all references have been removed from Signal 11 - sleep(delay) select_single_object(i1) - sleep(delay) delete_selected_objects() assert_object_configuration(s1,{ "pointinterlock":[ @@ -978,16 +986,131 @@ def run_change_of_item_id_tests(delay:float=0.0): [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],"",0], [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],"",0], [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],"",0] ] }) - # Delete Track Section 31 and test all references have been removed from Signal 1 and Track Sensor 61 - sleep(delay) + # Delete Track Section 31 and test all references have been removed from Signal 11 select_single_object(t1) - sleep(delay) delete_selected_objects() # Test the route and interlocking tables have been updated correctly for Signal 11 assert_object_configuration(s1,{ "tracksections":[0,[[0,32,33],[0,32,33],[0,32,33],[0,32,33],[0,32,33]] ], "trackinterlock":[[0,32,33],[0,32,33],[0,32,33],[0,32,33],[0,32,33]] } ) - # Test the route tables have been updated correctly for Track Sensor 61 + # Delete Section 32 and test all references have been removed from Signal 11 + select_single_object(t2) + delete_selected_objects() + assert_object_configuration(s1,{ + "tracksections":[0,[[0,0,33],[0,0,33],[0,0,33],[0,0,33],[0,0,33]] ], + "trackinterlock":[[0,0,33],[0,0,33],[0,0,33],[0,0,33],[0,0,33]] } ) + # Delete Section 33 and test all references have been removed from Signal 11 + select_single_object(t3) + delete_selected_objects() + assert_object_configuration(s1,{ + "tracksections":[0,[[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0]] ], + "trackinterlock":[[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0]] } ) + # clean up + select_all_objects() + delete_selected_objects() + return() + +#----------------------------------------------------------------------------------- +# These test the Item ID update functions for track sensors, specifically: +# Update of Track Sensor route tables to reflect change of Point ID +# Update of Track Sensor route tables to reflect deletion of Point +# Update of Track Sensor route tables to reflect change of section ID +# Update of Track Sensor route tables to reflect deletion of section +#----------------------------------------------------------------------------------- + +def run_sensor_config_update_on_change_of_id_tests(delay:float=0.0): + print("Test update of Track Sensor Configuration on change or delete of Item IDs") + # Add elements to the layout + p1 = create_left_hand_point() + p2 = create_left_hand_point() + t1 = create_track_section() + t2 = create_track_section() + ts1 = create_track_sensor() + # Set up the route tables for Track Sensor 1 + update_object_configuration(ts1,{ + "routeahead":[ + [[[1,True],[1,True],[1,True],[1,True],[2,True],[2,True]],1], + [[[1,True],[1,True],[1,True],[1,True],[2,True],[2,True]],1], + [[[1,True],[1,True],[1,True],[1,True],[2,True],[2,True]],1], + [[[1,True],[1,True],[1,True],[1,True],[2,True],[2,True]],1], + [[[1,True],[1,True],[1,True],[1,True],[2,True],[2,True]],1] ], + "routebehind":[ + [[[1,True],[1,True],[1,True],[1,True],[2,True],[2,True]],2], + [[[1,True],[1,True],[1,True],[1,True],[2,True],[2,True]],2], + [[[1,True],[1,True],[1,True],[1,True],[2,True],[2,True]],2], + [[[1,True],[1,True],[1,True],[1,True],[2,True],[2,True]],2], + [[[1,True],[1,True],[1,True],[1,True],[2,True],[2,True]],2] ] } ) + # Change the IDs of Points 1/2, Track Sections 1/2 + update_object_configuration(p1,{"itemid":21}) + update_object_configuration(p2,{"itemid":22}) + update_object_configuration(t1,{"itemid":31}) + update_object_configuration(t2,{"itemid":32}) + # Test the route tables have been updated correctly for Track Sensor + assert_object_configuration(ts1,{ + "routeahead":[ + [[[21,True],[21,True],[21,True],[21,True],[22,True],[22,True]],31], + [[[21,True],[21,True],[21,True],[21,True],[22,True],[22,True]],31], + [[[21,True],[21,True],[21,True],[21,True],[22,True],[22,True]],31], + [[[21,True],[21,True],[21,True],[21,True],[22,True],[22,True]],31], + [[[21,True],[21,True],[21,True],[21,True],[22,True],[22,True]],31] ], + "routebehind":[ + [[[21,True],[21,True],[21,True],[21,True],[22,True],[22,True]],32], + [[[21,True],[21,True],[21,True],[21,True],[22,True],[22,True]],32], + [[[21,True],[21,True],[21,True],[21,True],[22,True],[22,True]],32], + [[[21,True],[21,True],[21,True],[21,True],[22,True],[22,True]],32], + [[[21,True],[21,True],[21,True],[21,True],[22,True],[22,True]],32] ] } ) + # Delete Point 21 and test all references have been removed from the track Sensor + # Note that the point 22 entries will have shuffled down in the list + select_single_object(p1) + delete_selected_objects() + assert_object_configuration(ts1,{ + "routeahead":[ + [[[22,True],[22,True],[0,False],[0,False],[0,False],[0,False]],31], + [[[22,True],[22,True],[0,False],[0,False],[0,False],[0,False]],31], + [[[22,True],[22,True],[0,False],[0,False],[0,False],[0,False]],31], + [[[22,True],[22,True],[0,False],[0,False],[0,False],[0,False]],31], + [[[22,True],[22,True],[0,False],[0,False],[0,False],[0,False]],31] ], + "routebehind":[ + [[[22,True],[22,True],[0,False],[0,False],[0,False],[0,False]],32], + [[[22,True],[22,True],[0,False],[0,False],[0,False],[0,False]],32], + [[[22,True],[22,True],[0,False],[0,False],[0,False],[0,False]],32], + [[[22,True],[22,True],[0,False],[0,False],[0,False],[0,False]],32], + [[[22,True],[22,True],[0,False],[0,False],[0,False],[0,False]],32] ] } ) + # Delete Point 22 and test all references have been removed from the Track Sensor + select_single_object(p2) + delete_selected_objects() + assert_object_configuration(ts1,{ + "routeahead":[ + [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],31], + [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],31], + [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],31], + [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],31], + [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],31] ], + "routebehind":[ + [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],32], + [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],32], + [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],32], + [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],32], + [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],32] ] } ) + # Delete Track Section 31 and test all references have been removed from the Track Sensor + select_single_object(t1) + delete_selected_objects() + assert_object_configuration(ts1,{ + "routeahead":[ + [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],0], + [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],0], + [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],0], + [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],0], + [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],0] ], + "routebehind":[ + [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],32], + [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],32], + [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],32], + [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],32], + [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],32] ] } ) + # Delete Track Section 32 and test all references have been removed from the Track Sensor + select_single_object(t2) + delete_selected_objects() assert_object_configuration(ts1,{ "routeahead":[ [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],0], @@ -1001,49 +1124,109 @@ def run_change_of_item_id_tests(delay:float=0.0): [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],0], [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],0], [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],0] ] } ) - # Delete Section 32 and test all references have been removed from Signal 11 - sleep(delay) - select_single_object(t2) - sleep(delay) + # clean up + select_all_objects() delete_selected_objects() - assert_object_configuration(s1,{ - "tracksections":[0,[[0,0,33],[0,0,33],[0,0,33],[0,0,33],[0,0,33]] ], - "trackinterlock":[[0,0,33],[0,0,33],[0,0,33],[0,0,33],[0,0,33]] } ) - # Delete Section 33 and test all references have been removed from Signal 11 - sleep(delay) - select_single_object(t3) - sleep(delay) + return() + +#----------------------------------------------------------------------------------- +# These test the Item ID update functions for Schematic Routes, specifically: +# Update of Schematic route configuration to reflect change of Point ID +# Update of Schematic route configuration to reflect change of Signal ID +# Update of Schematic route configuration to reflect change of line ID +# Update of Schematic route configuration to reflect change of Track Sensor ID +# Update of Schematic route configuration to reflect deletion of point ID +# Update of Schematic route configuration to reflect deletion of signal ID +# Update of Schematic route configuration to reflect deletion of line ID +# Update of Schematic route configuration to reflect deletion of Track Sensor ID +#----------------------------------------------------------------------------------- + +def run_route_config_update_on_change_of_id_tests(delay:float=0.0): + print("Test update of Schematic Route Configuration on change or delete of Item IDs") + # Add elements to the layout + p1 = create_left_hand_point() + p2 = create_left_hand_point() + s1 = create_colour_light_signal() + s2 = create_colour_light_signal() + s3 = create_colour_light_signal() + s4 = create_colour_light_signal() + l1 = create_line() + l2 = create_line() + ts1 = create_track_sensor() + ts2 = create_track_sensor() + r1 = create_route() + # Set up the Configuration for the Schematic Route + update_object_configuration(r1,{ + "signalsonroute":[1,2], + "subsidariesonroute":[3,4], + "pointsonroute":{"1":True, "2":False}, + "linestohighlight":[1,2], + "pointstohighlight":[1,2], + "tracksensor":1, + "setupsensor":2 } ) + # Change the IDs of All the schematic objects + update_object_configuration(s1,{"itemid":11}) + update_object_configuration(s2,{"itemid":12}) + update_object_configuration(s3,{"itemid":13}) + update_object_configuration(s4,{"itemid":14}) + update_object_configuration(p1,{"itemid":21}) + update_object_configuration(p2,{"itemid":22}) + update_object_configuration(l1,{"itemid":31}) + update_object_configuration(l2,{"itemid":32}) + update_object_configuration(ts1,{"itemid":41}) + update_object_configuration(ts2,{"itemid":42}) + # Test the configuration has been updated correctly for the Route + assert_object_configuration(r1,{ + "signalsonroute":[11,12], + "subsidariesonroute":[13,14], + "pointsonroute":{"21":True, "22":False}, + "linestohighlight":[31,32], + "pointstohighlight":[21,22], + "tracksensor":41, + "setupsensor":42 } ) + # Delete half of the objects and Test the configuration has been updated correctly for the Route + deselect_all_objects() + select_or_deselect_objects(s1,s3,p1,l1,ts1) delete_selected_objects() - assert_object_configuration(s1,{ - "tracksections":[0,[[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0]] ], - "trackinterlock":[[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0]] } ) + assert_object_configuration(r1,{ + "signalsonroute":[12], + "subsidariesonroute":[14], + "pointsonroute":{"22":False}, + "linestohighlight":[32], + "pointstohighlight":[22], + "tracksensor":0, + "setupsensor":42 } ) + # Delete everything else and Test the configuration has been updated correctly for the Route + select_or_deselect_objects(s2,s4,p2,l2,ts2) + delete_selected_objects() + assert_object_configuration(r1,{ + "signalsonroute":[], + "subsidariesonroute":[], + "pointsonroute":{}, + "linestohighlight":[], + "pointstohighlight":[], + "tracksensor":0, + "setupsensor":0 } ) # clean up - sleep(delay) select_all_objects() delete_selected_objects() return() #----------------------------------------------------------------------------------- # These test the reset objects functions, specifically: -# All points, sections, instruments and signals are returned to their default states +# All points, sections, instruments signals and buttons are returned to their default states #----------------------------------------------------------------------------------- def run_reset_objects_tests(delay:float=0.0): print("Reset objects back to default state tests") # Add elements to the layout - sleep(delay) p1 = create_left_hand_point() - sleep(delay) s1 = create_colour_light_signal() - sleep(delay) t1 = create_track_section() - sleep(delay) i1 = create_block_instrument() - sleep(delay) i2 = create_block_instrument() - sleep(delay) + r1 = create_route() select_and_move_objects(i1,500,200,delay=delay) - sleep(delay) select_and_move_objects(i2,800,200,delay=delay) # Link the 2 block instruments (so we can test the interlocking) # Note the linked instrument is a string (local or remote) @@ -1053,51 +1236,45 @@ def run_reset_objects_tests(delay:float=0.0): # set run mode to configure state (for sections) set_run_mode() # set them to their non-default states - sleep(delay) set_signals_off(1) - sleep(delay) set_points_switched(1) - sleep(delay) set_sections_occupied(1) - sleep(delay) set_instrument_clear(2) + simulate_buttons_clicked(1) # Test reset in Run Mode assert_signals_PROCEED(1) assert_points_switched(1) assert_sections_occupied(1) assert_block_section_ahead_clear(1) assert_block_section_ahead_not_clear(2) - sleep(delay) + assert_buttons_selected(1) reset_layout() assert_signals_DANGER(1) assert_points_normal(1) assert_sections_clear(1) assert_block_section_ahead_not_clear(1,2) + assert_buttons_deselected(1) # Now set to non default states again - sleep(delay) set_signals_off(1) - sleep(delay) set_points_switched(1) - sleep(delay) set_sections_occupied(1) - sleep(delay) set_instrument_occupied(1) + simulate_buttons_clicked(1) # Test Reset in edit mode assert_signals_PROCEED(1) assert_points_switched(1) assert_sections_occupied(1) assert_block_section_ahead_not_clear(1,2) - sleep(delay) + assert_buttons_selected(1) set_edit_mode() - sleep(delay) reset_layout() set_run_mode() assert_signals_DANGER(1) assert_points_normal(1) assert_sections_clear(1) assert_block_section_ahead_not_clear(1,2) + assert_buttons_deselected(1) # clean up - sleep(delay) select_all_objects() delete_selected_objects() return() @@ -1107,11 +1284,15 @@ def run_reset_objects_tests(delay:float=0.0): def run_all_object_editing_tests(delay:float=0.0): initialise_test_harness() set_edit_mode() - run_point_chaining_tests(delay) run_instrument_linking_tests(delay) + run_point_chaining_tests(delay) run_mirrored_section_tests(delay) run_mode_change_tests(delay) - run_change_of_item_id_tests(delay) + run_initial_item_id_allocation_tests() + run_basic_change_of_item_id_tests() + run_signal_config_update_on_change_of_id_tests() + run_sensor_config_update_on_change_of_id_tests() + run_route_config_update_on_change_of_id_tests() run_reset_objects_tests(delay) report_results() diff --git a/system_tests/test_run_layout.sig b/system_tests/test_run_layout.sig index b30c5384..0d2b4c5a 100644 --- a/system_tests/test_run_layout.sig +++ b/system_tests/test_run_layout.sig @@ -15,7 +15,7 @@ "00df7795-1ac5-4b0a-8400-0ae0f64e3cf7": { "alsoswitch": 0, "automatic": false, - "bbox": 13253, + "bbox": 13562, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -65,9 +65,10 @@ "ybuttonoffset": 0 }, "014637ab-1cc0-47e4-8e2e-d6799882d586": { - "bbox": 13257, + "bbox": 13566, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 33, "mirror": "", @@ -76,9 +77,10 @@ "tags": "section33" }, "059e8b30-21fe-4a68-8fde-644d31f5963f": { - "bbox": 13261, + "bbox": 13570, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 2, "mirror": "", @@ -93,7 +95,7 @@ 0, 0 ], - "bbox": 13267, + "bbox": 13576, "colour": "black", "endx": 175.0, "endy": 800.0, @@ -111,7 +113,7 @@ 0, 0 ], - "bbox": 13273, + "bbox": 13582, "colour": "black", "endx": 750.0, "endy": 600.0, @@ -123,7 +125,8 @@ "tags": "line20" }, "07bad1d2-4308-4c99-b240-dc819e80a7de": { - "bbox": 13277, + "bbox": 13586, + "hidden": false, "item": "tracksensor", "itemid": 3, "passedsensor": "24", @@ -426,9 +429,10 @@ "tags": "sensor3" }, "0d5da466-b04e-402a-aa88-1afa3c1a0ed1": { - "bbox": 13281, + "bbox": 13590, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 18, "mirror": "", @@ -438,18 +442,19 @@ }, "12add760-8012-462a-b09f-07b831af55b5": { "background": "grey85", - "bbox": 13284, + "bbox": 13593, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 1, "justify": 2, "posx": 432.0, "posy": 686.0, - "tags": "12add760-8012-462a-b09f-07b831af55b5", + "tags": "textbox1", "text": "Intermediate Track Sensor Tests" }, "14c21c57-208a-4bdb-bc9e-b59169b5cd0e": { @@ -459,7 +464,7 @@ 0, 0 ], - "bbox": 13290, + "bbox": 13599, "colour": "black", "endx": 400.0, "endy": 600.0, @@ -477,7 +482,7 @@ 0, 0 ], - "bbox": 13296, + "bbox": 13605, "colour": "black", "endx": 225.0, "endy": 750.0, @@ -495,7 +500,7 @@ 0, 0 ], - "bbox": 13302, + "bbox": 13611, "colour": "black", "endx": 650.0, "endy": 825.0, @@ -507,7 +512,7 @@ "tags": "line34" }, "15e208d0-1738-407c-adac-0119c52527d6": { - "bbox": 13317, + "bbox": 13626, "bellsound": "bell-ring-01.wav", "item": "instrument", "itemid": 1, @@ -520,24 +525,25 @@ }, "1691d3ee-af50-44d0-af70-e400407393aa": { "background": "grey85", - "bbox": 13320, + "bbox": 13629, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 2, "justify": 2, "posx": 982.0, "posy": 361.0, - "tags": "1691d3ee-af50-44d0-af70-e400407393aa", + "tags": "textbox2", "text": "Section ahead or section behind tests\n Distant and shunt-ahead signal types" }, "1752a0a1-b276-4db6-9f2f-12b15748c742": { "alsoswitch": 0, "automatic": false, - "bbox": 13326, + "bbox": 13635, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -556,9 +562,10 @@ "ybuttonoffset": 0 }, "1ccdf967-6bae-4665-a682-489a7b7542ee": { - "bbox": 13330, + "bbox": 13639, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 4, "mirror": "", @@ -567,9 +574,10 @@ "tags": "section4" }, "1cfdad25-a454-44a3-b440-988209da6738": { - "bbox": 13334, + "bbox": 13643, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 12, "mirror": "", @@ -584,7 +592,7 @@ 0, 0 ], - "bbox": 13340, + "bbox": 13649, "colour": "black", "endx": 900.0, "endy": 200.0, @@ -602,7 +610,7 @@ 0, 0 ], - "bbox": 13346, + "bbox": 13655, "colour": "black", "endx": 425.0, "endy": 725.0, @@ -625,7 +633,7 @@ false, "" ], - "bbox": 13363, + "bbox": 13672, "dccaspects": [ [ [ @@ -1495,9 +1503,10 @@ ] }, "263cd888-5471-4263-91a7-f957c7ba98fa": { - "bbox": 13367, + "bbox": 13676, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 21, "mirror": "", @@ -1512,7 +1521,7 @@ 0, 0 ], - "bbox": 13373, + "bbox": 13682, "colour": "black", "endx": 400.0, "endy": 550.0, @@ -1524,9 +1533,10 @@ "tags": "line28" }, "27215a1d-9b38-4e57-80af-f67a4ee7448e": { - "bbox": 13377, + "bbox": 13686, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 25, "mirror": "", @@ -1546,7 +1556,7 @@ false, "" ], - "bbox": 13388, + "bbox": 13697, "dccaspects": [ [ [ @@ -2411,7 +2421,7 @@ 0, 0 ], - "bbox": 13394, + "bbox": 13703, "colour": "black", "endx": 425.0, "endy": 100.0, @@ -2425,7 +2435,7 @@ "29205350-455c-41d2-9996-af81b7cb3292": { "alsoswitch": 0, "automatic": false, - "bbox": 13400, + "bbox": 13709, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -2444,9 +2454,10 @@ "ybuttonoffset": 0 }, "294ffd9a-e658-4095-9446-9608c6565ea5": { - "bbox": 13404, + "bbox": 13713, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 11, "mirror": "", @@ -2466,7 +2477,7 @@ false, "" ], - "bbox": 13415, + "bbox": 13724, "dccaspects": [ [ [ @@ -3331,7 +3342,7 @@ 0, 0 ], - "bbox": 13421, + "bbox": 13730, "colour": "black", "endx": 825.0, "endy": 750.0, @@ -3349,7 +3360,7 @@ 0, 0 ], - "bbox": 13427, + "bbox": 13736, "colour": "black", "endx": 1100.0, "endy": 825.0, @@ -3361,9 +3372,10 @@ "tags": "line12" }, "30b8cd94-2eb6-46f7-9d8f-b29f93974a16": { - "bbox": 13431, + "bbox": 13740, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 37, "mirror": "", @@ -3372,9 +3384,10 @@ "tags": "section37" }, "30f44eb7-a19e-4797-9002-4ae97f36f5e6": { - "bbox": 13435, + "bbox": 13744, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 34, "mirror": "", @@ -3394,7 +3407,7 @@ false, "" ], - "bbox": 13452, + "bbox": 13761, "dccaspects": [ [ [ @@ -4264,9 +4277,10 @@ ] }, "3487c94a-82a2-4c9c-aea4-6ce463344c8d": { - "bbox": 13456, + "bbox": 13765, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 35, "mirror": "", @@ -4286,7 +4300,7 @@ false, "" ], - "bbox": 13467, + "bbox": 13776, "dccaspects": [ [ [ @@ -5145,9 +5159,10 @@ ] }, "36a17429-1e73-4970-bc0e-04d7763bd528": { - "bbox": 13471, + "bbox": 13780, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 32, "mirror": "", @@ -5162,7 +5177,7 @@ 0, 0 ], - "bbox": 13477, + "bbox": 13786, "colour": "black", "endx": 825.0, "endy": 900.0, @@ -5175,18 +5190,19 @@ }, "3921c74d-11e9-4a28-baac-22741f00f5f6": { "background": "grey85", - "bbox": 13480, + "bbox": 13789, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 3, "justify": 2, "posx": 175.0, "posy": 59.0, - "tags": "3921c74d-11e9-4a28-baac-22741f00f5f6", + "tags": "textbox3", "text": "Basic track occupancy tests\nBasic interlocking/override tests\n(Tests include manual triggering\nof signal passed events and\nsimulated triggering of GPIO inputs)" }, "3a89c624-8a39-495b-b22f-ea0d3bf2ae6f": { @@ -5196,7 +5212,7 @@ 0, 0 ], - "bbox": 13486, + "bbox": 13795, "colour": "black", "endx": 625.0, "endy": 700.0, @@ -5210,7 +5226,7 @@ "3ad18aaf-2eb4-4251-86e3-337c07f23f13": { "alsoswitch": 0, "automatic": false, - "bbox": 13492, + "bbox": 13801, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -5235,7 +5251,7 @@ 0, 0 ], - "bbox": 13498, + "bbox": 13807, "colour": "black", "endx": 1125.0, "endy": 475.0, @@ -5247,9 +5263,10 @@ "tags": "line14" }, "40e9f8d6-763e-463c-bd9a-5cc7601ebaae": { - "bbox": 13502, + "bbox": 13811, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 7, "mirror": "", @@ -5269,7 +5286,7 @@ false, "" ], - "bbox": 13519, + "bbox": 13828, "dccaspects": [ [ [ @@ -6150,7 +6167,7 @@ false, "" ], - "bbox": 13527, + "bbox": 13836, "dccaspects": [ [ [ @@ -7015,7 +7032,7 @@ 0, 0 ], - "bbox": 13533, + "bbox": 13842, "colour": "black", "endx": 350.0, "endy": 800.0, @@ -7027,7 +7044,8 @@ "tags": "line51" }, "48f66d15-6c33-42bd-8189-135b00fd0b01": { - "bbox": 13537, + "bbox": 13846, + "hidden": false, "item": "tracksensor", "itemid": 2, "passedsensor": "23", @@ -7336,7 +7354,7 @@ 0, 0 ], - "bbox": 13543, + "bbox": 13852, "colour": "black", "endx": 900.0, "endy": 150.0, @@ -7359,7 +7377,7 @@ false, "" ], - "bbox": 13560, + "bbox": 13869, "dccaspects": [ [ [ @@ -8231,7 +8249,7 @@ "52028ce3-d790-4b02-86c2-7600c81905d5": { "alsoswitch": 0, "automatic": false, - "bbox": 13566, + "bbox": 13875, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -8252,7 +8270,7 @@ "551bcd35-606a-45ef-ac41-a399f74bed80": { "alsoswitch": 0, "automatic": false, - "bbox": 13572, + "bbox": 13881, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -8277,7 +8295,7 @@ 0, 0 ], - "bbox": 13578, + "bbox": 13887, "colour": "black", "endx": 525.0, "endy": 800.0, @@ -8289,9 +8307,10 @@ "tags": "line53" }, "58a3b159-ccd8-46fa-b40e-cb8e0f6dfe7b": { - "bbox": 13582, + "bbox": 13891, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 9, "mirror": "", @@ -8300,9 +8319,10 @@ "tags": "section9" }, "59728ad9-d204-4ca9-8c8d-a3e340bc7e72": { - "bbox": 13586, + "bbox": 13895, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 5, "mirror": "", @@ -8317,7 +8337,7 @@ 0, 0 ], - "bbox": 13592, + "bbox": 13901, "colour": "black", "endx": 300.0, "endy": 350.0, @@ -8335,7 +8355,7 @@ 0, 0 ], - "bbox": 13598, + "bbox": 13907, "colour": "black", "endx": 350.0, "endy": 400.0, @@ -8358,7 +8378,7 @@ false, "" ], - "bbox": 13624, + "bbox": 13933, "dccaspects": [ [ [ @@ -9223,7 +9243,7 @@ 0, 0 ], - "bbox": 13630, + "bbox": 13939, "colour": "black", "endx": 725.0, "endy": 900.0, @@ -9235,9 +9255,10 @@ "tags": "line32" }, "67ab0983-6537-46c7-95e5-8e6f48075319": { - "bbox": 13634, + "bbox": 13943, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 13, "mirror": "", @@ -9252,7 +9273,7 @@ 0, 0 ], - "bbox": 13640, + "bbox": 13949, "colour": "black", "endx": 825.0, "endy": 850.0, @@ -9270,7 +9291,7 @@ 0, 0 ], - "bbox": 13646, + "bbox": 13955, "colour": "black", "endx": 575.0, "endy": 750.0, @@ -9288,7 +9309,7 @@ 0, 0 ], - "bbox": 13652, + "bbox": 13961, "colour": "black", "endx": 1125.0, "endy": 425.0, @@ -9311,7 +9332,7 @@ false, "" ], - "bbox": 13701, + "bbox": 14010, "dccaspects": [ [ [ @@ -10172,7 +10193,7 @@ "6e5bb0e2-e690-4589-b788-061a06c387ab": { "alsoswitch": 0, "automatic": false, - "bbox": 13707, + "bbox": 14016, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -10197,7 +10218,7 @@ 0, 0 ], - "bbox": 13713, + "bbox": 14022, "colour": "black", "endx": 200.0, "endy": 600.0, @@ -10215,7 +10236,7 @@ 0, 0 ], - "bbox": 13719, + "bbox": 14028, "colour": "black", "endx": 375.0, "endy": 825.0, @@ -10233,7 +10254,7 @@ 0, 0 ], - "bbox": 13725, + "bbox": 14034, "colour": "black", "endx": 475.0, "endy": 50.0, @@ -10245,9 +10266,10 @@ "tags": "line5" }, "792e40e8-1303-49a8-b2d7-1e1e255cbff0": { - "bbox": 13729, + "bbox": 14038, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 23, "mirror": "", @@ -10267,7 +10289,7 @@ false, "" ], - "bbox": 13746, + "bbox": 14055, "dccaspects": [ [ [ @@ -11138,18 +11160,19 @@ }, "815e5a81-0614-4783-bd22-e0d6cfb055d0": { "background": "grey85", - "bbox": 13749, + "bbox": 14058, "border": 2, "colour": "black", "font": "Courier", "fontsize": 16, "fontstyle": "bold ", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 4, "justify": 2, "posx": 250.0, "posy": 225.0, - "tags": "815e5a81-0614-4783-bd22-e0d6cfb055d0", + "tags": "textbox4", "text": " Layout file must be saves in \n EDIT Mode with Automation ON " }, "88daa56e-c0d6-4b39-8949-89c0eace8cb0": { @@ -11159,7 +11182,7 @@ 0, 0 ], - "bbox": 13755, + "bbox": 14064, "colour": "black", "endx": 575.0, "endy": 250.0, @@ -11171,9 +11194,10 @@ "tags": "line11" }, "8bae3fe9-f026-416d-8d53-cbaf1017b776": { - "bbox": 13759, + "bbox": 14068, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 31, "mirror": "", @@ -11182,9 +11206,10 @@ "tags": "section31" }, "95c43eba-116d-461f-acf5-2942ee2de9a0": { - "bbox": 13763, + "bbox": 14072, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 10, "mirror": "", @@ -11193,7 +11218,7 @@ "tags": "section10" }, "97f92095-a77f-4a82-ac03-58020be13cda": { - "bbox": 13778, + "bbox": 14087, "bellsound": "bell-ring-01.wav", "item": "instrument", "itemid": 2, @@ -11216,7 +11241,7 @@ false, "" ], - "bbox": 13806, + "bbox": 14115, "dccaspects": [ [ [ @@ -12075,9 +12100,10 @@ ] }, "99942f6d-a746-459b-84db-e3fb2474ac4c": { - "bbox": 13810, + "bbox": 14119, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 39, "mirror": "", @@ -12086,9 +12112,10 @@ "tags": "section39" }, "9a297b55-f32f-427e-97f3-d29601155e06": { - "bbox": 13814, + "bbox": 14123, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 24, "mirror": "", @@ -12097,9 +12124,10 @@ "tags": "section24" }, "9dc022bc-753e-4da7-8965-7a9d3dfe572f": { - "bbox": 13818, + "bbox": 14127, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 38, "mirror": "", @@ -12108,9 +12136,10 @@ "tags": "section38" }, "9f044ede-2ea9-4576-b088-bcf66e039359": { - "bbox": 13822, + "bbox": 14131, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 36, "mirror": "", @@ -12130,7 +12159,7 @@ false, "" ], - "bbox": 13848, + "bbox": 14157, "dccaspects": [ [ [ @@ -12990,24 +13019,26 @@ }, "a168039e-a91a-4d28-8f9c-c9847dddcb6d": { "background": "grey85", - "bbox": 13851, + "bbox": 14160, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 5, "justify": 2, "posx": 582.0, "posy": 486.0, - "tags": "a168039e-a91a-4d28-8f9c-c9847dddcb6d", + "tags": "textbox5", "text": "Shunt-ahead signal route tests" }, "a2a63569-61ec-4286-806c-3fb2b9669cae": { - "bbox": 13855, + "bbox": 14164, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 27, "mirror": "", @@ -13022,7 +13053,7 @@ 0, 0 ], - "bbox": 13861, + "bbox": 14170, "colour": "black", "endx": 900.0, "endy": 50.0, @@ -13035,24 +13066,25 @@ }, "a37162c4-c2da-42a6-9d5d-2ded06f14cc5": { "background": "grey85", - "bbox": 13864, + "bbox": 14173, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 6, "justify": 2, "posx": 257.0, "posy": 286.0, - "tags": "a37162c4-c2da-42a6-9d5d-2ded06f14cc5", + "tags": "textbox6", "text": "Interlock/Override distant signal\non home signals ahead" }, "a46c7541-5009-4243-b51e-8a885c07a46f": { "alsoswitch": 0, "automatic": false, - "bbox": 13870, + "bbox": 14179, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -13073,7 +13105,7 @@ "a4a3951a-8365-413c-a29f-001a13e6ef21": { "alsoswitch": 0, "automatic": false, - "bbox": 13876, + "bbox": 14185, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -13103,9 +13135,10 @@ "ybuttonoffset": 0 }, "a6510982-7180-4a78-a46e-55ef46497bef": { - "bbox": 13880, + "bbox": 14189, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 6, "mirror": "", @@ -13116,7 +13149,7 @@ "a7754934-0d4a-428d-ab57-5599d44e8d85": { "alsoswitch": 0, "automatic": false, - "bbox": 13887, + "bbox": 14196, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -13202,7 +13235,7 @@ 0, 0 ], - "bbox": 13893, + "bbox": 14202, "colour": "black", "endx": 475.0, "endy": 150.0, @@ -13225,7 +13258,7 @@ false, "" ], - "bbox": 13919, + "bbox": 14228, "dccaspects": [ [ [ @@ -14084,9 +14117,10 @@ ] }, "ab1c75b8-5187-4e29-89aa-1ad8efff6e41": { - "bbox": 13923, + "bbox": 14232, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 15, "mirror": "", @@ -14101,7 +14135,7 @@ 0, 0 ], - "bbox": 13929, + "bbox": 14238, "colour": "black", "endx": 150.0, "endy": 875.0, @@ -14113,9 +14147,10 @@ "tags": "line46" }, "ab65202c-8dbc-4a49-9df4-b2a210d1b773": { - "bbox": 13933, + "bbox": 14242, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 1, "mirror": "", @@ -14130,7 +14165,7 @@ 0, 0 ], - "bbox": 13939, + "bbox": 14248, "colour": "black", "endx": 750.0, "endy": 550.0, @@ -14142,7 +14177,8 @@ "tags": "line18" }, "aeadfd4e-edcf-427f-a4e9-1305637e679e": { - "bbox": 13943, + "bbox": 14252, + "hidden": false, "item": "tracksensor", "itemid": 1, "passedsensor": "22", @@ -14456,7 +14492,7 @@ false, "" ], - "bbox": 13991, + "bbox": 14300, "dccaspects": [ [ [ @@ -15315,9 +15351,10 @@ ] }, "afd35369-6f78-4dab-be3c-62460b51f8bd": { - "bbox": 13995, + "bbox": 14304, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 20, "mirror": "", @@ -15327,24 +15364,25 @@ }, "b1357b6e-7b98-476e-ae22-39d22c08122c": { "background": "grey85", - "bbox": 13998, + "bbox": 14307, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 7, "justify": 2, "posx": 1007.0, "posy": 661.0, - "tags": "b1357b6e-7b98-476e-ae22-39d22c08122c", + "tags": "textbox7", "text": "Section ahead or section behind tests\n Intermediate Track Sections" }, "b149ba67-d83f-4f67-ac37-6ac099d886d1": { "alsoswitch": 0, "automatic": false, - "bbox": 14004, + "bbox": 14313, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -15405,7 +15443,7 @@ false, "" ], - "bbox": 14023, + "bbox": 14332, "dccaspects": [ [ [ @@ -16325,7 +16363,7 @@ 0, 0 ], - "bbox": 14029, + "bbox": 14338, "colour": "black", "endx": 225.0, "endy": 700.0, @@ -16343,7 +16381,7 @@ 20, 5 ], - "bbox": 14035, + "bbox": 14344, "colour": "black", "endx": 250.0, "endy": 550.0, @@ -16361,7 +16399,7 @@ 0, 0 ], - "bbox": 14041, + "bbox": 14350, "colour": "black", "endx": 125.0, "endy": 900.0, @@ -16373,9 +16411,10 @@ "tags": "line41" }, "be3430e8-fa0a-4ed5-8e15-bbe161956db4": { - "bbox": 14045, + "bbox": 14354, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 3, "mirror": "", @@ -16390,7 +16429,7 @@ 0, 0 ], - "bbox": 14051, + "bbox": 14360, "colour": "black", "endx": 450.0, "endy": 800.0, @@ -16413,7 +16452,7 @@ false, "" ], - "bbox": 14077, + "bbox": 14386, "dccaspects": [ [ [ @@ -17278,7 +17317,7 @@ 0, 0 ], - "bbox": 14083, + "bbox": 14392, "colour": "black", "endx": 650.0, "endy": 600.0, @@ -17296,7 +17335,7 @@ 0, 0 ], - "bbox": 14089, + "bbox": 14398, "colour": "black", "endx": 1100.0, "endy": 775.0, @@ -17309,22 +17348,24 @@ }, "c27d25b7-5b4c-44d3-bd35-e5a1449de1ec": { "background": "grey85", - "bbox": 14092, + "bbox": 14401, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 8, "justify": 2, "posx": 232.0, "posy": 486.0, - "tags": "c27d25b7-5b4c-44d3-bd35-e5a1449de1ec", + "tags": "textbox8", "text": "Override secondary distant arms\non distant signal ahead (i.e\n distant arms mirror distant ahead)" }, "c34c24e1-e64e-4e32-8b92-d5decbbfb361": { - "bbox": 14096, + "bbox": 14405, + "hidden": false, "item": "tracksensor", "itemid": 4, "passedsensor": "25", @@ -17629,7 +17670,7 @@ "c47af54d-b826-4360-996c-22dc8ef0b906": { "alsoswitch": 0, "automatic": false, - "bbox": 14103, + "bbox": 14412, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -17654,7 +17695,7 @@ 0, 0 ], - "bbox": 14109, + "bbox": 14418, "colour": "black", "endx": 750.0, "endy": 400.0, @@ -17666,9 +17707,10 @@ "tags": "line23" }, "c5e61491-9bc4-4afd-bbda-ef1d44ab3a85": { - "bbox": 14113, + "bbox": 14422, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 28, "mirror": "", @@ -17677,9 +17719,10 @@ "tags": "section28" }, "c6bcd861-e896-4b6c-9d38-d472b7d12d67": { - "bbox": 14117, + "bbox": 14426, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 26, "mirror": "", @@ -17694,7 +17737,7 @@ 20, 5 ], - "bbox": 14123, + "bbox": 14432, "colour": "black", "endx": 275.0, "endy": 550.0, @@ -17708,7 +17751,7 @@ "c9be255e-8c79-46ae-813c-80cd31db2961": { "alsoswitch": 0, "automatic": false, - "bbox": 14129, + "bbox": 14438, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -17729,7 +17772,7 @@ "cd6f0f22-d678-4aec-91a0-2b54c188c73e": { "alsoswitch": 0, "automatic": false, - "bbox": 14135, + "bbox": 14444, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -17761,7 +17804,7 @@ "cd84d65b-c1fb-48f9-abfe-e37d2799986c": { "alsoswitch": 0, "automatic": false, - "bbox": 14142, + "bbox": 14451, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -17817,7 +17860,7 @@ 0, 0 ], - "bbox": 14148, + "bbox": 14457, "colour": "black", "endx": 900.0, "endy": 100.0, @@ -17835,7 +17878,7 @@ 0, 0 ], - "bbox": 14154, + "bbox": 14463, "colour": "black", "endx": 200.0, "endy": 825.0, @@ -17847,9 +17890,10 @@ "tags": "line47" }, "cfa18894-c904-43dc-9659-e5bb376873b0": { - "bbox": 14158, + "bbox": 14467, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 30, "mirror": "", @@ -17860,7 +17904,7 @@ "cff06336-0304-4d14-aee0-3281912bf82c": { "alsoswitch": 0, "automatic": false, - "bbox": 14165, + "bbox": 14474, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -17881,7 +17925,7 @@ "d210a466-1fdb-417c-b9a4-b190c54b8c0d": { "alsoswitch": 0, "automatic": false, - "bbox": 14172, + "bbox": 14481, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -17947,7 +17991,7 @@ 0, 0 ], - "bbox": 14178, + "bbox": 14487, "colour": "black", "endx": 900.0, "endy": 250.0, @@ -17970,7 +18014,7 @@ false, "" ], - "bbox": 14204, + "bbox": 14513, "dccaspects": [ [ [ @@ -18835,7 +18879,7 @@ 0, 0 ], - "bbox": 14210, + "bbox": 14519, "colour": "black", "endx": 825.0, "endy": 700.0, @@ -18853,7 +18897,7 @@ 0, 0 ], - "bbox": 14216, + "bbox": 14525, "colour": "black", "endx": 375.0, "endy": 150.0, @@ -18876,7 +18920,7 @@ false, "" ], - "bbox": 14244, + "bbox": 14553, "dccaspects": [ [ [ @@ -19741,7 +19785,7 @@ 0, 0 ], - "bbox": 14250, + "bbox": 14559, "colour": "black", "endx": 125.0, "endy": 850.0, @@ -19759,7 +19803,7 @@ 0, 0 ], - "bbox": 14256, + "bbox": 14565, "colour": "black", "endx": 600.0, "endy": 550.0, @@ -19771,9 +19815,10 @@ "tags": "line17" }, "e1b0ee5f-12f4-45de-8fc6-7d4615c50a8d": { - "bbox": 14260, + "bbox": 14569, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 19, "mirror": "", @@ -19784,7 +19829,7 @@ "e2b503f9-ac5b-4e55-8fc5-d15c72b63297": { "alsoswitch": 0, "automatic": false, - "bbox": 14266, + "bbox": 14575, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -19814,7 +19859,7 @@ false, "" ], - "bbox": 14292, + "bbox": 14601, "dccaspects": [ [ [ @@ -20679,7 +20724,7 @@ 0, 0 ], - "bbox": 14298, + "bbox": 14607, "colour": "black", "endx": 275.0, "endy": 750.0, @@ -20691,9 +20736,10 @@ "tags": "line48" }, "ea7fe3f2-f03e-46b4-b081-a9a7fd72c579": { - "bbox": 14302, + "bbox": 14611, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 22, "mirror": "", @@ -20708,7 +20754,7 @@ 0, 0 ], - "bbox": 14308, + "bbox": 14617, "colour": "black", "endx": 500.0, "endy": 175.0, @@ -20722,7 +20768,7 @@ "ecdbe7e9-941c-45d4-9054-be1fc4e9bf85": { "alsoswitch": 0, "automatic": false, - "bbox": 14314, + "bbox": 14623, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -20768,7 +20814,7 @@ 0, 0 ], - "bbox": 14320, + "bbox": 14629, "colour": "black", "endx": 1125.0, "endy": 525.0, @@ -20780,9 +20826,10 @@ "tags": "line16" }, "ee1cb09a-869b-4445-a027-105f24cc1685": { - "bbox": 14324, + "bbox": 14633, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 17, "mirror": "", @@ -20791,9 +20838,10 @@ "tags": "section17" }, "ef6b7696-ab25-4bba-95c5-a3c885155f3c": { - "bbox": 14328, + "bbox": 14637, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 29, "mirror": "", @@ -20808,7 +20856,7 @@ 0, 0 ], - "bbox": 14334, + "bbox": 14643, "colour": "black", "endx": 825.0, "endy": 800.0, @@ -20826,7 +20874,7 @@ 0, 0 ], - "bbox": 14340, + "bbox": 14649, "colour": "black", "endx": 275.0, "endy": 800.0, @@ -20844,7 +20892,7 @@ 0, 0 ], - "bbox": 14346, + "bbox": 14655, "colour": "black", "endx": 625.0, "endy": 800.0, @@ -20856,9 +20904,10 @@ "tags": "line33" }, "f4a7c3e0-db3e-4bdb-a195-537ce01e07c2": { - "bbox": 14350, + "bbox": 14659, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 8, "mirror": "", @@ -20873,7 +20922,7 @@ 0, 0 ], - "bbox": 14356, + "bbox": 14665, "colour": "black", "endx": 750.0, "endy": 350.0, @@ -20891,7 +20940,7 @@ 0, 0 ], - "bbox": 14362, + "bbox": 14671, "colour": "black", "endx": 225.0, "endy": 700.0, @@ -20909,7 +20958,7 @@ 0, 0 ], - "bbox": 14368, + "bbox": 14677, "colour": "black", "endx": 1100.0, "endy": 725.0, @@ -20921,9 +20970,10 @@ "tags": "line55" }, "f82bb5bd-b0b1-4fe1-bb06-3bb60e528e30": { - "bbox": 14372, + "bbox": 14681, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 14, "mirror": "", @@ -20932,9 +20982,10 @@ "tags": "section14" }, "f8dae4ee-8601-441b-8d76-7056a2289fa1": { - "bbox": 14376, + "bbox": 14685, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 16, "mirror": "", @@ -20954,7 +21005,7 @@ false, "" ], - "bbox": 14402, + "bbox": 14711, "dccaspects": [ [ [ @@ -22077,11 +22128,12 @@ }, "general": { "automation": true, + "buttonsize": 8, "editmode": true, "filename": "/home/john/model-railway-signalling/system_tests/test_run_layout.sig", "info": "Document your layout here", "spadpopups": false, - "version": "Version 4.6.1" + "version": "Version 4.7.0" }, "gpio": { "portmappings": [ diff --git a/system_tests/test_schematic_routes.sig b/system_tests/test_schematic_routes.sig index fa93896d..8cdce1c0 100644 --- a/system_tests/test_schematic_routes.sig +++ b/system_tests/test_schematic_routes.sig @@ -38,9 +38,10 @@ }, "objects": { "0924c57d-1f88-4c4f-9a85-88c502e9aedd": { - "bbox": 14467, + "bbox": 14776, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 5, "mirror": "", @@ -55,7 +56,7 @@ 0, 0 ], - "bbox": 14473, + "bbox": 14782, "colour": "black", "endx": 400.0, "endy": 400.0, @@ -68,24 +69,26 @@ }, "1231bb63-e727-4e5a-bad9-14c09f9b0f11": { "background": "grey85", - "bbox": 14476, + "bbox": 14785, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 1, "justify": 2, "posx": 675.0, "posy": 175.0, - "tags": "1231bb63-e727-4e5a-bad9-14c09f9b0f11", + "tags": "textbox1", "text": "R3" }, "12db2eb4-7d45-452e-ae19-47b8532b48ef": { - "bbox": 14480, + "bbox": 14789, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 1, "mirror": "", @@ -100,7 +103,7 @@ 0, 0 ], - "bbox": 14486, + "bbox": 14795, "colour": "black", "endx": 275.0, "endy": 175.0, @@ -113,18 +116,19 @@ }, "1a8672c8-8635-4e64-af85-50b58a598119": { "background": "grey85", - "bbox": 14489, + "bbox": 14798, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 2, "justify": 2, "posx": 675.0, "posy": 125.0, - "tags": "1a8672c8-8635-4e64-af85-50b58a598119", + "tags": "textbox2", "text": "R2" }, "1acc91ea-c516-40c0-80ce-d9db3895abac": { @@ -139,7 +143,7 @@ false, "" ], - "bbox": 14508, + "bbox": 14817, "dccaspects": [ [ [ @@ -1020,7 +1024,7 @@ false, "" ], - "bbox": 14527, + "bbox": 14836, "dccaspects": [ [ [ @@ -1890,7 +1894,8 @@ ] }, "280d74dc-dffb-402d-a51d-1e80f29a3a10": { - "bbox": 14531, + "bbox": 14840, + "buttoncolour": "SeaGreen3", "buttonwidth": 10, "item": "route", "itemid": 7, @@ -1910,6 +1915,7 @@ "routecolour": "#00ff00", "routedescription": "Route description (Run Mode tooltip)", "routename": "Branch", + "setupsensor": 0, "signalsonroute": [ 5 ], @@ -1925,7 +1931,7 @@ 0, 0 ], - "bbox": 14537, + "bbox": 14846, "colour": "black", "endx": 650.0, "endy": 125.0, @@ -1937,7 +1943,7 @@ "tags": "line4" }, "372e382f-2dad-44b3-b825-e174d4cdb259": { - "bbox": 14552, + "bbox": 14861, "bellsound": "bell-ring-01.wav", "item": "instrument", "itemid": 1, @@ -1960,7 +1966,7 @@ false, "" ], - "bbox": 14571, + "bbox": 14880, "dccaspects": [ [ [ @@ -2836,7 +2842,7 @@ 0, 0 ], - "bbox": 14577, + "bbox": 14886, "colour": "black", "endx": 650.0, "endy": 175.0, @@ -2854,7 +2860,7 @@ 0, 0 ], - "bbox": 14583, + "bbox": 14892, "colour": "black", "endx": 650.0, "endy": 75.0, @@ -2867,22 +2873,24 @@ }, "420c4fa6-0479-4e63-85b6-32c16cfcce4a": { "background": "grey85", - "bbox": 14586, + "bbox": 14895, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 3, "justify": 2, "posx": 725.0, "posy": 375.0, - "tags": "420c4fa6-0479-4e63-85b6-32c16cfcce4a", + "tags": "textbox3", "text": "This Layout File should be saved in RUN Mode\nwith Automation ON and 'Route 3 Main' active" }, "43dc9897-b2c9-419c-98c7-0fce8c3179af": { - "bbox": 14590, + "bbox": 14899, + "buttoncolour": "SeaGreen3", "buttonwidth": 15, "item": "route", "itemid": 2, @@ -2903,6 +2911,7 @@ "routecolour": "#00ff00", "routedescription": "Route description (Run Mode tooltip)", "routename": "Route 1 Shunt", + "setupsensor": 0, "signalsonroute": [], "subsidariesonroute": [ 1 @@ -2912,9 +2921,10 @@ "tracksensor": 0 }, "462b2667-f649-4486-b7a2-2679d7599a22": { - "bbox": 14594, + "bbox": 14903, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 7, "mirror": "", @@ -2925,7 +2935,7 @@ "466fdbb1-49a5-4389-ba22-b56e86e3a59b": { "alsoswitch": 0, "automatic": false, - "bbox": 14600, + "bbox": 14909, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -2981,7 +2991,7 @@ 0, 0 ], - "bbox": 14606, + "bbox": 14915, "colour": "black", "endx": 300.0, "endy": 400.0, @@ -3004,7 +3014,7 @@ false, "" ], - "bbox": 14623, + "bbox": 14932, "dccaspects": [ [ [ @@ -3869,7 +3879,7 @@ 0, 0 ], - "bbox": 14629, + "bbox": 14938, "colour": "black", "endx": 400.0, "endy": 375.0, @@ -3881,7 +3891,8 @@ "tags": "line7" }, "6925181f-7e58-4997-b225-794c2d3324cd": { - "bbox": 14633, + "bbox": 14942, + "buttoncolour": "SeaGreen3", "buttonwidth": 10, "item": "route", "itemid": 8, @@ -3901,6 +3912,7 @@ "routecolour": "#00ff00", "routedescription": "Route description (Run Mode tooltip)", "routename": "Main", + "setupsensor": 0, "signalsonroute": [ 5 ], @@ -3911,22 +3923,24 @@ }, "6fa5a660-bbf1-40d3-9466-a5e1c32a07ac": { "background": "grey85", - "bbox": 14636, + "bbox": 14945, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 4, "justify": 2, "posx": 675.0, "posy": 75.0, - "tags": "6fa5a660-bbf1-40d3-9466-a5e1c32a07ac", + "tags": "textbox4", "text": "R1" }, "704ba590-fd9e-4cb0-9175-32af75278e82": { - "bbox": 14640, + "bbox": 14949, + "buttoncolour": "SeaGreen3", "buttonwidth": 15, "item": "route", "itemid": 3, @@ -3948,6 +3962,7 @@ "routecolour": "#ff0000", "routedescription": "Route description (Run Mode tooltip)", "routename": "Route 2 Main", + "setupsensor": 0, "signalsonroute": [ 1 ], @@ -3963,7 +3978,7 @@ 0, 0 ], - "bbox": 14646, + "bbox": 14955, "colour": "black", "endx": 175.0, "endy": 125.0, @@ -3986,7 +4001,7 @@ false, "" ], - "bbox": 14665, + "bbox": 14974, "dccaspects": [ [ [ @@ -4856,7 +4871,7 @@ ] }, "87f3f4a6-5fce-4ec3-ad51-7dfddf217e1b": { - "bbox": 14680, + "bbox": 14989, "bellsound": "bell-ring-01.wav", "item": "instrument", "itemid": 2, @@ -4870,7 +4885,7 @@ "88495a44-2ae1-478e-a569-1fcc3e5752d5": { "alsoswitch": 0, "automatic": false, - "bbox": 14686, + "bbox": 14995, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -4889,7 +4904,8 @@ "ybuttonoffset": 0 }, "918adc91-218c-457c-a775-1ee41f83cc68": { - "bbox": 14690, + "bbox": 14999, + "buttoncolour": "SeaGreen3", "buttonwidth": 15, "item": "route", "itemid": 6, @@ -4912,6 +4928,7 @@ "routecolour": "#00ff00", "routedescription": "Route description (Run Mode tooltip)", "routename": "Route 3 Shunt", + "setupsensor": 0, "signalsonroute": [], "subsidariesonroute": [ 1 @@ -4932,7 +4949,7 @@ false, "" ], - "bbox": 14709, + "bbox": 15018, "dccaspects": [ [ [ @@ -5802,9 +5819,10 @@ ] }, "9547b313-2571-4ea6-a026-7d9434e05137": { - "bbox": 14713, + "bbox": 15022, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 8, "mirror": "", @@ -5813,7 +5831,8 @@ "tags": "section8" }, "971da1aa-52b9-42dd-b2ca-40db4c8a513b": { - "bbox": 14717, + "bbox": 15026, + "buttoncolour": "SeaGreen3", "buttonwidth": 15, "item": "route", "itemid": 4, @@ -5835,6 +5854,7 @@ "routecolour": "#00ff00", "routedescription": "Route description (Run Mode tooltip)", "routename": "Route 2 Shunt", + "setupsensor": 0, "signalsonroute": [], "subsidariesonroute": [ 1 @@ -5850,7 +5870,7 @@ 0, 0 ], - "bbox": 14723, + "bbox": 15032, "colour": "black", "endx": 225.0, "endy": 75.0, @@ -5873,7 +5893,7 @@ false, "" ], - "bbox": 14742, + "bbox": 15051, "dccaspects": [ [ [ @@ -6743,9 +6763,10 @@ ] }, "ad6c5d81-9864-4204-afc9-5c47ca23937e": { - "bbox": 14746, + "bbox": 15055, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 3, "mirror": "", @@ -6765,7 +6786,7 @@ false, "" ], - "bbox": 14765, + "bbox": 15074, "dccaspects": [ [ [ @@ -7689,7 +7710,7 @@ "c066da43-4a4e-4839-8e88-d65e842f1195": { "alsoswitch": 0, "automatic": false, - "bbox": 14771, + "bbox": 15080, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -7750,24 +7771,26 @@ }, "c25738d9-8763-45a3-b4d4-0d22959570f8": { "background": "grey85", - "bbox": 14774, + "bbox": 15083, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 5, "justify": 2, "posx": 825.0, "posy": 25.0, - "tags": "c25738d9-8763-45a3-b4d4-0d22959570f8", + "tags": "textbox5", "text": "Instruments controlling Route 1" }, "c2d0102b-7032-4fa3-92b5-a9698485399f": { - "bbox": 14778, + "bbox": 15087, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 4, "mirror": "", @@ -7776,9 +7799,10 @@ "tags": "section4" }, "c6ee6d5e-87fd-4685-bf52-559af959aa45": { - "bbox": 14782, + "bbox": 15091, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 9, "mirror": "", @@ -7787,9 +7811,10 @@ "tags": "section9" }, "e2f14aba-d776-4f1e-91f0-ea653bb150fa": { - "bbox": 14786, + "bbox": 15095, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 2, "mirror": "", @@ -7798,9 +7823,10 @@ "tags": "section2" }, "eecbdb5e-04c4-4fc8-97f7-b0b7ebfce322": { - "bbox": 14790, + "bbox": 15099, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 6, "mirror": "", @@ -7809,7 +7835,8 @@ "tags": "section6" }, "f9d2ee11-4010-46e4-948e-0a4e1c062b33": { - "bbox": 14794, + "bbox": 15103, + "buttoncolour": "SeaGreen3", "buttonwidth": 15, "item": "route", "itemid": 1, @@ -7830,6 +7857,7 @@ "routecolour": "#ff0000", "routedescription": "Route description (Run Mode tooltip)", "routename": "Route 1 Main", + "setupsensor": 0, "signalsonroute": [ 1 ], @@ -7839,7 +7867,8 @@ "tracksensor": 0 }, "fdb9c32e-4cee-4264-b5d3-5d30da73ee8b": { - "bbox": 14798, + "bbox": 15107, + "buttoncolour": "SeaGreen3", "buttonwidth": 15, "item": "route", "itemid": 5, @@ -7862,6 +7891,7 @@ "routecolour": "#ff0000", "routedescription": "Route description (Run Mode tooltip)", "routename": "Route 3 Main", + "setupsensor": 0, "signalsonroute": [ 1 ], @@ -7935,11 +7965,12 @@ }, "general": { "automation": true, + "buttonsize": 8, "editmode": false, "filename": "/home/john/model-railway-signalling/system_tests/test_schematic_routes.sig", "info": "Document your layout here", "spadpopups": false, - "version": "Version 4.6.1" + "version": "Version 4.7.0" }, "gpio": { "portmappings": [], diff --git a/user_guide/networking1-node1.sig b/user_guide/networking1-node1.sig index a7c0241b..928b8e4f 100644 --- a/user_guide/networking1-node1.sig +++ b/user_guide/networking1-node1.sig @@ -19,7 +19,7 @@ 20, 5 ], - "bbox": 11266, + "bbox": 11334, "colour": "black", "endx": 600.0, "endy": 125.0, @@ -37,7 +37,7 @@ 20, 5 ], - "bbox": 11272, + "bbox": 11340, "colour": "black", "endx": 125.0, "endy": 125.0, @@ -60,7 +60,7 @@ false, "" ], - "bbox": 11298, + "bbox": 11366, "dccaspects": [ [ [ @@ -930,9 +930,10 @@ ] }, "5119d487-7752-469d-addb-5aa3be1bedb6": { - "bbox": 11302, + "bbox": 11370, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 1, "mirror": "", @@ -952,7 +953,7 @@ false, "" ], - "bbox": 11328, + "bbox": 11396, "dccaspects": [ [ [ @@ -1833,18 +1834,19 @@ }, "6408cbee-f763-41ad-8fca-3130f53d2519": { "background": "grey85", - "bbox": 11331, + "bbox": 11399, "border": 0, "colour": "black", "font": "Courier", "fontsize": 12, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 1, "justify": 2, "posx": 350.0, "posy": 50.0, - "tags": "6408cbee-f763-41ad-8fca-3130f53d2519", + "tags": "textbox1", "text": "Node1 - Example 1 (Signals and Track Sections)" }, "6946db0c-1014-4d65-9cd1-92a92fa030c3": { @@ -1859,7 +1861,7 @@ false, "" ], - "bbox": 11348, + "bbox": 11416, "dccaspects": [ [ [ @@ -2724,7 +2726,7 @@ 20, 5 ], - "bbox": 11354, + "bbox": 11422, "colour": "black", "endx": 475.0, "endy": 300.0, @@ -2736,9 +2738,10 @@ "tags": "line3" }, "70194f30-ef69-484a-b947-66951acf8a72": { - "bbox": 11358, + "bbox": 11426, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 2, "mirror": "", @@ -2758,7 +2761,7 @@ false, "" ], - "bbox": 11375, + "bbox": 11443, "dccaspects": [ [ [ @@ -3617,9 +3620,10 @@ ] }, "73be5b7a-289a-4a48-959b-0f06dc39c0e9": { - "bbox": 11379, + "bbox": 11447, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 3, "mirror": "Node2-4", @@ -3629,38 +3633,40 @@ }, "862f860b-d823-4a1a-9de9-1b2236bcc453": { "background": "grey85", - "bbox": 11382, + "bbox": 11450, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 2, "justify": 2, "posx": 475.0, "posy": 350.0, - "tags": "862f860b-d823-4a1a-9de9-1b2236bcc453", + "tags": "textbox2", "text": "To\nNode2" }, "9a8b2db4-dadb-4a28-bf0f-5ce1baaa363f": { "background": "grey85", - "bbox": 11385, + "bbox": 11453, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 3, "justify": 2, "posx": 625.0, "posy": 125.0, - "tags": "9a8b2db4-dadb-4a28-bf0f-5ce1baaa363f", + "tags": "textbox3", "text": "To\nNode2" }, "a271c006-6796-436a-b85f-c805554d8237": { - "bbox": 11400, + "bbox": 11468, "bellsound": "bell-ring-01.wav", "item": "instrument", "itemid": 1, @@ -3673,18 +3679,19 @@ }, "aad6451e-e1e2-4a4d-8f93-064d331acafd": { "background": "grey85", - "bbox": 11403, + "bbox": 11471, "border": 0, "colour": "black", "font": "Courier", "fontsize": 12, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 4, "justify": 2, "posx": 300.0, "posy": 225.0, - "tags": "aad6451e-e1e2-4a4d-8f93-064d331acafd", + "tags": "textbox4", "text": "Node1 - Example 2 (Block Instruments)" }, "bd893a3c-d4bc-4486-95ac-64b2e8c89bec": { @@ -3699,7 +3706,7 @@ false, "" ], - "bbox": 11429, + "bbox": 11497, "dccaspects": [ [ [ @@ -4597,11 +4604,12 @@ }, "general": { "automation": true, + "buttonsize": 8, "editmode": false, "filename": "/home/john/model-railway-signalling/user_guide/networking1-node1.sig", "info": "Document your layout here", "spadpopups": false, - "version": "Version 4.6.1" + "version": "Version 4.7.0" }, "gpio": { "portmappings": [ diff --git a/user_guide/networking1-node2.sig b/user_guide/networking1-node2.sig index c0dbac2d..cc8d5665 100644 --- a/user_guide/networking1-node2.sig +++ b/user_guide/networking1-node2.sig @@ -24,7 +24,7 @@ false, "" ], - "bbox": 11499, + "bbox": 11567, "dccaspects": [ [ [ @@ -889,7 +889,7 @@ 20, 5 ], - "bbox": 11505, + "bbox": 11573, "colour": "black", "endx": 625.0, "endy": 300.0, @@ -902,18 +902,19 @@ }, "3992df93-f016-42e7-a710-f0bddf9b1627": { "background": "grey85", - "bbox": 11508, + "bbox": 11576, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 1, "justify": 2, "posx": 225.0, "posy": 350.0, - "tags": "3992df93-f016-42e7-a710-f0bddf9b1627", + "tags": "textbox1", "text": "To\nNode1" }, "426d97b4-5064-44c8-9c11-dade5f9b900e": { @@ -923,7 +924,7 @@ 20, 5 ], - "bbox": 11514, + "bbox": 11582, "colour": "black", "endx": 625.0, "endy": 125.0, @@ -935,7 +936,7 @@ "tags": "line1" }, "6085fa37-fea7-42cd-a7a9-f15aeb1ec731": { - "bbox": 11529, + "bbox": 11597, "bellsound": "bell-ring-01.wav", "item": "instrument", "itemid": 2, @@ -947,9 +948,10 @@ "tags": "instrument2" }, "6e4f0867-155b-4ed8-ae84-38e1cc667131": { - "bbox": 11533, + "bbox": 11601, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 5, "mirror": "", @@ -959,18 +961,19 @@ }, "70238cfa-4556-4ed9-9a92-46ac169573de": { "background": "grey85", - "bbox": 11536, + "bbox": 11604, "border": 0, "colour": "black", "font": "Courier", "fontsize": 12, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 2, "justify": 2, "posx": 425.0, "posy": 225.0, - "tags": "70238cfa-4556-4ed9-9a92-46ac169573de", + "tags": "textbox2", "text": "Node2 - Example 2 (Block Instruments)" }, "87306a7f-df1e-4d21-8a00-eb8cd5b96e34": { @@ -980,7 +983,7 @@ 20, 5 ], - "bbox": 11542, + "bbox": 11610, "colour": "black", "endx": 225.0, "endy": 125.0, @@ -1003,7 +1006,7 @@ false, "" ], - "bbox": 11568, + "bbox": 11636, "dccaspects": [ [ [ @@ -1874,18 +1877,19 @@ }, "9ac66673-362c-4529-a451-6dba32782869": { "background": "grey85", - "bbox": 11571, + "bbox": 11639, "border": 0, "colour": "black", "font": "Courier", "fontsize": 12, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 3, "justify": 2, "posx": 375.0, "posy": 50.0, - "tags": "9ac66673-362c-4529-a451-6dba32782869", + "tags": "textbox3", "text": "Node2 - Example 1 (signals and Track Sections)" }, "c5a50644-7d55-4530-afbb-ea118ea602e9": { @@ -1900,7 +1904,7 @@ false, "" ], - "bbox": 11588, + "bbox": 11656, "dccaspects": [ [ [ @@ -2759,9 +2763,10 @@ ] }, "e26bc73f-b67e-4fed-ae67-8971af1d7b92": { - "bbox": 11592, + "bbox": 11660, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 4, "mirror": "Node1-3", @@ -2771,18 +2776,19 @@ }, "e497d420-ad7a-4cb5-877c-6dbd0d849296": { "background": "grey85", - "bbox": 11595, + "bbox": 11663, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 4, "justify": 2, "posx": 150.0, "posy": 125.0, - "tags": "e497d420-ad7a-4cb5-877c-6dbd0d849296", + "tags": "textbox4", "text": "From\nNode1" }, "ed441189-b4ae-4d79-a839-6fc46ba4aa51": { @@ -2797,7 +2803,7 @@ false, "" ], - "bbox": 11621, + "bbox": 11689, "dccaspects": [ [ [ @@ -3678,7 +3684,7 @@ false, "" ], - "bbox": 11647, + "bbox": 11715, "dccaspects": [ [ [ @@ -4582,11 +4588,12 @@ }, "general": { "automation": true, + "buttonsize": 8, "editmode": false, "filename": "/home/john/model-railway-signalling/user_guide/networking1-node2.sig", "info": "Document your layout here", "spadpopups": false, - "version": "Version 4.6.1" + "version": "Version 4.7.0" }, "gpio": { "portmappings": [], diff --git a/user_guide/quickstart_example1.sig b/user_guide/quickstart_example1.sig index 0347de55..8ecab9f7 100644 --- a/user_guide/quickstart_example1.sig +++ b/user_guide/quickstart_example1.sig @@ -17,7 +17,8 @@ "instruments": {}, "objects": { "03f32844-9771-4717-91a8-ef160bada1df": { - "bbox": 11712, + "bbox": 12021, + "hidden": false, "item": "tracksensor", "itemid": 3, "passedsensor": "8", @@ -326,7 +327,7 @@ 1, 1 ], - "bbox": 11718, + "bbox": 12027, "colour": "black", "endx": 550.0, "endy": 200.0, @@ -338,7 +339,8 @@ "tags": "line4" }, "30bdbf8d-ab42-46a5-b86c-632d3bad5dd2": { - "bbox": 11722, + "bbox": 12031, + "hidden": false, "item": "tracksensor", "itemid": 2, "passedsensor": "7", @@ -641,9 +643,10 @@ "tags": "sensor2" }, "338a06f5-049d-454e-9e8f-e032ab9ca9cb": { - "bbox": 11726, + "bbox": 12035, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 4, "mirror": "", @@ -653,22 +656,24 @@ }, "3495a936-bc8d-4393-9abe-3520edcb0d9d": { "background": "#18bbd9", - "bbox": 11729, + "bbox": 12038, "border": 1, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 1, "justify": 2, "posx": 800.0, "posy": 175.0, - "tags": "3495a936-bc8d-4393-9abe-3520edcb0d9d", + "tags": "textbox1", "text": " Platform 2 " }, "353b45c7-5cea-4783-81eb-7fe275398deb": { - "bbox": 11733, + "bbox": 12042, + "hidden": false, "item": "tracksensor", "itemid": 1, "passedsensor": "", @@ -972,50 +977,53 @@ }, "40efa244-6879-4bbd-b880-9eeb70373504": { "background": "grey85", - "bbox": 11736, + "bbox": 12045, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 2, "justify": 2, "posx": 100.0, "posy": 250.0, - "tags": "40efa244-6879-4bbd-b880-9eeb70373504", + "tags": "textbox2", "text": "The rest of \nthe world" }, "43fd9649-cff7-437a-b405-2811fc777a5f": { "background": "grey85", - "bbox": 11739, + "bbox": 12048, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 3, "justify": 2, "posx": 150.0, "posy": 181.0, - "tags": "43fd9649-cff7-437a-b405-2811fc777a5f", + "tags": "textbox3", "text": "Next Train" }, "4576da32-1464-4dad-92d8-5e3e06ee59e3": { "background": "#18bbd9", - "bbox": 11742, + "bbox": 12051, "border": 1, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 4, "justify": 2, "posx": 800.0, "posy": 275.0, - "tags": "4576da32-1464-4dad-92d8-5e3e06ee59e3", + "tags": "textbox4", "text": " Platform 1 " }, "4faab254-a7db-48cb-bcdd-187c9702c92b": { @@ -1030,7 +1038,7 @@ false, "" ], - "bbox": 11759, + "bbox": 12068, "dccaspects": [ [ [ @@ -1895,7 +1903,7 @@ 20, 5 ], - "bbox": 11765, + "bbox": 12074, "colour": "black", "endx": 550.0, "endy": 250.0, @@ -1907,9 +1915,10 @@ "tags": "line1" }, "6788feaa-a049-4d62-978c-faa6f4dfd967": { - "bbox": 11769, + "bbox": 12078, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 1, "mirror": "", @@ -1918,7 +1927,8 @@ "tags": "section1" }, "6f705b1a-e264-4bb7-a979-4520901eec1b": { - "bbox": 11773, + "bbox": 12082, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 4, @@ -1939,6 +1949,7 @@ "routecolour": "#1bb420", "routedescription": "Platform 2 departure\nto the rest of the world", "routename": "Depart Platform 2", + "setupsensor": 0, "signalsonroute": [ 2 ], @@ -1949,18 +1960,19 @@ }, "87f435df-8da5-4999-b3ef-e7ea67977c03": { "background": "grey85", - "bbox": 11776, + "bbox": 12085, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 5, "justify": 2, "posx": 375.0, "posy": 175.0, - "tags": "87f435df-8da5-4999-b3ef-e7ea67977c03", + "tags": "textbox5", "text": "Siding" }, "93406d3b-2a78-455f-b5cd-3fcd1fcdf640": { @@ -1970,7 +1982,7 @@ 1, 1 ], - "bbox": 11782, + "bbox": 12091, "colour": "black", "endx": 900.0, "endy": 250.0, @@ -1982,9 +1994,10 @@ "tags": "line3" }, "970e8948-a116-4023-847b-2e9269c0fa74": { - "bbox": 11786, + "bbox": 12095, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 2, "mirror": "", @@ -1995,7 +2008,7 @@ "990769c5-58eb-435b-befb-21b9263321b4": { "alsoswitch": 0, "automatic": true, - "bbox": 11791, + "bbox": 12100, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -2025,7 +2038,7 @@ false, "" ], - "bbox": 11802, + "bbox": 12111, "dccaspects": [ [ [ @@ -2895,9 +2908,10 @@ ] }, "abc1ad1d-e1b2-4129-a1eb-e33844a0166d": { - "bbox": 11806, + "bbox": 12115, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 5, "mirror": "", @@ -2917,7 +2931,7 @@ false, "" ], - "bbox": 11825, + "bbox": 12134, "dccaspects": [ [ [ @@ -3800,7 +3814,7 @@ "b3241207-0377-4e6e-aeac-6cb08691c888": { "alsoswitch": 2, "automatic": false, - "bbox": 11832, + "bbox": 12141, "colour": "black", "dccaddress": 1000, "dccreversed": false, @@ -3870,7 +3884,8 @@ "ybuttonoffset": 0 }, "c622073d-27c0-4f32-aecb-f95dfebb93a0": { - "bbox": 11836, + "bbox": 12145, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 2, @@ -3891,6 +3906,7 @@ "routecolour": "#2916d9", "routedescription": "From the rest of the World\ninto Platform 2", "routename": "Into Platform 2", + "setupsensor": 0, "signalsonroute": [ 1 ], @@ -3911,7 +3927,7 @@ false, "" ], - "bbox": 11853, + "bbox": 12162, "dccaspects": [ [ [ @@ -4792,7 +4808,8 @@ ] }, "da49f189-a72b-4e08-969a-d222c5609644": { - "bbox": 11857, + "bbox": 12166, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 3, @@ -4812,6 +4829,7 @@ "routecolour": "#1bb420", "routedescription": "Platform 1 departure\nto rest of the world", "routename": "Depart Platform 1", + "setupsensor": 0, "signalsonroute": [ 3 ], @@ -4821,7 +4839,8 @@ "tracksensor": 1 }, "df3dcf3e-6882-4e86-84d9-f07049ac7129": { - "bbox": 11861, + "bbox": 12170, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 1, @@ -4841,6 +4860,7 @@ "routecolour": "#2916d9", "routedescription": "From the rest of the world \ninto platform 1", "routename": "Into Platform 1", + "setupsensor": 0, "signalsonroute": [ 1 ], @@ -4861,7 +4881,7 @@ false, "" ], - "bbox": 11878, + "bbox": 12187, "dccaspects": [ [ [ @@ -5737,7 +5757,7 @@ 1, 1 ], - "bbox": 11884, + "bbox": 12193, "colour": "black", "endx": 900.0, "endy": 200.0, @@ -5749,9 +5769,10 @@ "tags": "line2" }, "fc5c18e4-b05c-4941-9c64-476d0ae651a2": { - "bbox": 11888, + "bbox": 12197, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 3, "mirror": "", @@ -5803,11 +5824,12 @@ }, "general": { "automation": true, + "buttonsize": 8, "editmode": true, "filename": "/home/john/model-railway-signalling/user_guide/quickstart_example1.sig", "info": "Document your layout here", "spadpopups": false, - "version": "Version 4.6.1" + "version": "Version 4.7.0" }, "gpio": { "portmappings": [ diff --git a/user_guide/quickstart_example2.sig b/user_guide/quickstart_example2.sig index 39cd1897..c16b144a 100644 --- a/user_guide/quickstart_example2.sig +++ b/user_guide/quickstart_example2.sig @@ -17,7 +17,8 @@ "instruments": {}, "objects": { "03f32844-9771-4717-91a8-ef160bada1df": { - "bbox": 11953, + "bbox": 12262, + "hidden": false, "item": "tracksensor", "itemid": 3, "passedsensor": "8", @@ -326,7 +327,7 @@ 1, 1 ], - "bbox": 11959, + "bbox": 12268, "colour": "black", "endx": 550.0, "endy": 200.0, @@ -338,7 +339,8 @@ "tags": "line4" }, "30bdbf8d-ab42-46a5-b86c-632d3bad5dd2": { - "bbox": 11963, + "bbox": 12272, + "hidden": false, "item": "tracksensor", "itemid": 2, "passedsensor": "7", @@ -641,9 +643,10 @@ "tags": "sensor2" }, "338a06f5-049d-454e-9e8f-e032ab9ca9cb": { - "bbox": 11967, + "bbox": 12276, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 4, "mirror": "", @@ -653,22 +656,24 @@ }, "3495a936-bc8d-4393-9abe-3520edcb0d9d": { "background": "#18bbd9", - "bbox": 11970, + "bbox": 12279, "border": 1, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 1, "justify": 2, "posx": 800.0, "posy": 175.0, - "tags": "3495a936-bc8d-4393-9abe-3520edcb0d9d", + "tags": "textbox1", "text": " Platform 2 " }, "353b45c7-5cea-4783-81eb-7fe275398deb": { - "bbox": 11974, + "bbox": 12283, + "hidden": false, "item": "tracksensor", "itemid": 1, "passedsensor": "", @@ -972,50 +977,53 @@ }, "40efa244-6879-4bbd-b880-9eeb70373504": { "background": "grey85", - "bbox": 11977, + "bbox": 12286, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 2, "justify": 2, "posx": 100.0, "posy": 250.0, - "tags": "40efa244-6879-4bbd-b880-9eeb70373504", + "tags": "textbox2", "text": "The rest of \nthe world" }, "43fd9649-cff7-437a-b405-2811fc777a5f": { "background": "grey85", - "bbox": 11980, + "bbox": 12289, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 3, "justify": 2, "posx": 150.0, "posy": 181.0, - "tags": "43fd9649-cff7-437a-b405-2811fc777a5f", + "tags": "textbox3", "text": "Next Train" }, "4576da32-1464-4dad-92d8-5e3e06ee59e3": { "background": "#18bbd9", - "bbox": 11983, + "bbox": 12292, "border": 1, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 4, "justify": 2, "posx": 800.0, "posy": 275.0, - "tags": "4576da32-1464-4dad-92d8-5e3e06ee59e3", + "tags": "textbox4", "text": " Platform 1 " }, "4faab254-a7db-48cb-bcdd-187c9702c92b": { @@ -1030,7 +1038,7 @@ false, "" ], - "bbox": 12009, + "bbox": 12318, "dccaspects": [ [ [ @@ -1895,7 +1903,7 @@ 20, 5 ], - "bbox": 12015, + "bbox": 12324, "colour": "black", "endx": 550.0, "endy": 250.0, @@ -1907,9 +1915,10 @@ "tags": "line1" }, "6788feaa-a049-4d62-978c-faa6f4dfd967": { - "bbox": 12019, + "bbox": 12328, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 1, "mirror": "", @@ -1918,7 +1927,8 @@ "tags": "section1" }, "6f705b1a-e264-4bb7-a979-4520901eec1b": { - "bbox": 12023, + "bbox": 12332, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 4, @@ -1939,6 +1949,7 @@ "routecolour": "#1bb420", "routedescription": "Platform 2 departure\nto the rest of the world", "routename": "Depart Platform 2", + "setupsensor": 0, "signalsonroute": [ 2 ], @@ -1949,18 +1960,19 @@ }, "87f435df-8da5-4999-b3ef-e7ea67977c03": { "background": "grey85", - "bbox": 12026, + "bbox": 12335, "border": 0, "colour": "black", "font": "Courier", "fontsize": 10, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 5, "justify": 2, "posx": 375.0, "posy": 175.0, - "tags": "87f435df-8da5-4999-b3ef-e7ea67977c03", + "tags": "textbox5", "text": "Siding" }, "93406d3b-2a78-455f-b5cd-3fcd1fcdf640": { @@ -1970,7 +1982,7 @@ 1, 1 ], - "bbox": 12032, + "bbox": 12341, "colour": "black", "endx": 900.0, "endy": 250.0, @@ -1982,9 +1994,10 @@ "tags": "line3" }, "970e8948-a116-4023-847b-2e9269c0fa74": { - "bbox": 12036, + "bbox": 12345, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 2, "mirror": "", @@ -1995,7 +2008,7 @@ "990769c5-58eb-435b-befb-21b9263321b4": { "alsoswitch": 0, "automatic": true, - "bbox": 12041, + "bbox": 12350, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -2025,7 +2038,7 @@ false, "" ], - "bbox": 12049, + "bbox": 12358, "dccaspects": [ [ [ @@ -2895,9 +2908,10 @@ ] }, "abc1ad1d-e1b2-4129-a1eb-e33844a0166d": { - "bbox": 12053, + "bbox": 12362, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 5, "mirror": "", @@ -2917,7 +2931,7 @@ false, "" ], - "bbox": 12082, + "bbox": 12391, "dccaspects": [ [ [ @@ -3800,7 +3814,7 @@ "b3241207-0377-4e6e-aeac-6cb08691c888": { "alsoswitch": 2, "automatic": false, - "bbox": 12089, + "bbox": 12398, "colour": "black", "dccaddress": 1000, "dccreversed": false, @@ -3870,7 +3884,8 @@ "ybuttonoffset": 0 }, "c622073d-27c0-4f32-aecb-f95dfebb93a0": { - "bbox": 12093, + "bbox": 12402, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 2, @@ -3891,6 +3906,7 @@ "routecolour": "#2916d9", "routedescription": "From the rest of the World\ninto Platform 2", "routename": "Into Platform 2", + "setupsensor": 0, "signalsonroute": [ 1 ], @@ -3911,7 +3927,7 @@ false, "" ], - "bbox": 12121, + "bbox": 12430, "dccaspects": [ [ [ @@ -4792,7 +4808,8 @@ ] }, "da49f189-a72b-4e08-969a-d222c5609644": { - "bbox": 12125, + "bbox": 12434, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 3, @@ -4812,6 +4829,7 @@ "routecolour": "#1bb420", "routedescription": "Platform 1 departure\nto rest of the world", "routename": "Depart Platform 1", + "setupsensor": 0, "signalsonroute": [ 3 ], @@ -4821,7 +4839,8 @@ "tracksensor": 1 }, "df3dcf3e-6882-4e86-84d9-f07049ac7129": { - "bbox": 12129, + "bbox": 12438, + "buttoncolour": "SeaGreen3", "buttonwidth": 20, "item": "route", "itemid": 1, @@ -4841,6 +4860,7 @@ "routecolour": "#2916d9", "routedescription": "From the rest of the world \ninto platform 1", "routename": "Into Platform 1", + "setupsensor": 0, "signalsonroute": [ 1 ], @@ -4861,7 +4881,7 @@ false, "" ], - "bbox": 12155, + "bbox": 12464, "dccaspects": [ [ [ @@ -5737,7 +5757,7 @@ 1, 1 ], - "bbox": 12161, + "bbox": 12470, "colour": "black", "endx": 900.0, "endy": 200.0, @@ -5749,9 +5769,10 @@ "tags": "line2" }, "fc5c18e4-b05c-4941-9c64-476d0ae651a2": { - "bbox": 12165, + "bbox": 12474, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 3, "mirror": "", @@ -5803,11 +5824,12 @@ }, "general": { "automation": true, + "buttonsize": 8, "editmode": false, "filename": "/home/john/model-railway-signalling/user_guide/quickstart_example2.sig", "info": "Document your layout here", "spadpopups": false, - "version": "Version 4.6.1" + "version": "Version 4.7.0" }, "gpio": { "portmappings": [ diff --git a/user_guide/quickstart_example3.sig b/user_guide/quickstart_example3.sig index c2a8f574..1dddf77d 100644 --- a/user_guide/quickstart_example3.sig +++ b/user_guide/quickstart_example3.sig @@ -5,18 +5,19 @@ "objects": { "027167c4-575e-4384-9e79-d97b56ae3e01": { "background": "grey85", - "bbox": 12241, + "bbox": 12550, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 1, "justify": 2, "posx": 1146.0, "posy": 62.0, - "tags": "027167c4-575e-4384-9e79-d97b56ae3e01", + "tags": "textbox1", "text": "TT-DS10" }, "07cd6ea1-f297-4487-867f-b3d735e1fc41": { @@ -26,7 +27,7 @@ 20, 5 ], - "bbox": 12247, + "bbox": 12556, "colour": "black", "endx": 1275.0, "endy": 225.0, @@ -40,7 +41,7 @@ "07d52e15-c961-4ee9-8227-5c7e7ade9d7c": { "alsoswitch": 0, "automatic": false, - "bbox": 12254, + "bbox": 12563, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -81,7 +82,7 @@ false, "" ], - "bbox": 12271, + "bbox": 12580, "dccaspects": [ [ [ @@ -941,24 +942,26 @@ }, "1cd0d6ee-bf33-4e85-ad10-162a5114e499": { "background": "grey85", - "bbox": 12274, + "bbox": 12583, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 2, "justify": 2, "posx": 1170.0, "posy": 239.0, - "tags": "1cd0d6ee-bf33-4e85-ad10-162a5114e499", + "tags": "textbox2", "text": "TT-DS10" }, "1d324970-3029-401b-8c66-8425a680ff42": { - "bbox": 12278, + "bbox": 12587, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 13, "mirror": "", @@ -978,7 +981,7 @@ false, "" ], - "bbox": 12295, + "bbox": 12604, "dccaspects": [ [ [ @@ -1843,7 +1846,7 @@ 0, 0 ], - "bbox": 12301, + "bbox": 12610, "colour": "black", "endx": 900.0, "endy": 100.0, @@ -1866,7 +1869,7 @@ false, "" ], - "bbox": 12318, + "bbox": 12627, "dccaspects": [ [ [ @@ -2736,7 +2739,7 @@ false, "" ], - "bbox": 12335, + "bbox": 12644, "dccaspects": [ [ [ @@ -3607,18 +3610,19 @@ }, "33bf5593-6030-4282-a957-c96d2a12548f": { "background": "grey85", - "bbox": 12338, + "bbox": 12647, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 3, "justify": 2, "posx": 1126.0, "posy": 314.0, - "tags": "33bf5593-6030-4282-a957-c96d2a12548f", + "tags": "textbox3", "text": "TT-DS10" }, "3861465f-82fc-4911-853a-558f4bf91a71": { @@ -3628,7 +3632,7 @@ 20, 5 ], - "bbox": 12344, + "bbox": 12653, "colour": "black", "endx": 800.0, "endy": 275.0, @@ -3651,7 +3655,7 @@ false, "" ], - "bbox": 12361, + "bbox": 12670, "dccaspects": [ [ [ @@ -4521,7 +4525,7 @@ false, "" ], - "bbox": 12378, + "bbox": 12687, "dccaspects": [ [ [ @@ -5380,9 +5384,10 @@ ] }, "55eab425-03d7-40a2-8b77-2c7d23b8e933": { - "bbox": 12382, + "bbox": 12691, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 8, "mirror": "", @@ -5402,7 +5407,7 @@ false, "" ], - "bbox": 12399, + "bbox": 12708, "dccaspects": [ [ [ @@ -6272,7 +6277,7 @@ false, "" ], - "bbox": 12416, + "bbox": 12725, "dccaspects": [ [ [ @@ -7142,7 +7147,7 @@ false, "" ], - "bbox": 12433, + "bbox": 12742, "dccaspects": [ [ [ @@ -8001,9 +8006,10 @@ ] }, "628c161f-b9f5-46fd-837c-4f36eecd60ab": { - "bbox": 12437, + "bbox": 12746, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 11, "mirror": "", @@ -8012,9 +8018,10 @@ "tags": "section11" }, "65c6b251-6622-4fd2-958f-b28a6778dc76": { - "bbox": 12441, + "bbox": 12750, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 5, "mirror": "", @@ -8024,40 +8031,42 @@ }, "6966a516-b636-4321-ae7b-3a733356e215": { "background": "grey85", - "bbox": 12444, + "bbox": 12753, "border": 0, "colour": "black", "font": "Courier", "fontsize": 14, "fontstyle": "bold ", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 4, "justify": 2, "posx": 444.0, "posy": 115.0, - "tags": "6966a516-b636-4321-ae7b-3a733356e215", + "tags": "textbox4", "text": "Approach Control Example (Signals 4, 3 and 2)" }, "6b03065c-be75-450c-8af5-af5dc644b14f": { "background": "grey85", - "bbox": 12447, + "bbox": 12756, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 5, "justify": 2, "posx": 951.0, "posy": 313.0, - "tags": "6b03065c-be75-450c-8af5-af5dc644b14f", + "tags": "textbox5", "text": "TT-DS10" }, "719f6d7b-e39c-4d3a-8b22-f232dff5443e": { "alsoswitch": 0, "automatic": false, - "bbox": 12453, + "bbox": 12762, "colour": "black", "dccaddress": 0, "dccreversed": false, @@ -8098,24 +8107,26 @@ }, "7c1562c4-2d75-46cf-b559-bef25efa2c9e": { "background": "grey85", - "bbox": 12456, + "bbox": 12765, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 6, "justify": 2, "posx": 325.0, "posy": 314.0, - "tags": "7c1562c4-2d75-46cf-b559-bef25efa2c9e", + "tags": "textbox6", "text": "TT-DS10" }, "7d95452f-2bb7-48b8-8418-6d83598356d4": { - "bbox": 12460, + "bbox": 12769, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 2, "mirror": "", @@ -8130,7 +8141,7 @@ 20, 5 ], - "bbox": 12466, + "bbox": 12775, "colour": "black", "endx": 1275.0, "endy": 100.0, @@ -8142,9 +8153,10 @@ "tags": "line8" }, "a6541a21-387c-4ebc-93dd-3b86a950f45d": { - "bbox": 12470, + "bbox": 12779, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 1, "mirror": "", @@ -8154,24 +8166,26 @@ }, "a6d29d1d-43c7-4b82-bca6-0fd6d90d6c89": { "background": "grey85", - "bbox": 12473, + "bbox": 12782, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 7, "justify": 2, "posx": 175.0, "posy": 187.0, - "tags": "a6d29d1d-43c7-4b82-bca6-0fd6d90d6c89", + "tags": "textbox7", "text": "TT-DS10" }, "a6e61479-8736-4f06-b351-c8e99ff870f5": { - "bbox": 12477, + "bbox": 12786, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 6, "mirror": "", @@ -8181,18 +8195,19 @@ }, "a96bd506-67b8-4312-a415-169eae42e657": { "background": "grey85", - "bbox": 12480, + "bbox": 12789, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 8, "justify": 2, "posx": 1130.0, "posy": 136.0, - "tags": "a96bd506-67b8-4312-a415-169eae42e657", + "tags": "textbox8", "text": "TT-DS10" }, "aa736f3d-7877-4615-b1e7-bc50b39db7f0": { @@ -8202,7 +8217,7 @@ 0, 0 ], - "bbox": 12486, + "bbox": 12795, "colour": "black", "endx": 1275.0, "endy": 275.0, @@ -8225,7 +8240,7 @@ false, "" ], - "bbox": 12503, + "bbox": 12812, "dccaspects": [ [ [ @@ -9084,9 +9099,10 @@ ] }, "b1288a67-ed1d-427f-98d5-f7963edaf7f4": { - "bbox": 12507, + "bbox": 12816, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 12, "mirror": "", @@ -9101,7 +9117,7 @@ 0, 0 ], - "bbox": 12513, + "bbox": 12822, "colour": "black", "endx": 925.0, "endy": 150.0, @@ -9113,9 +9129,10 @@ "tags": "line6" }, "b62548e4-2291-454e-9f01-4c3163fb178a": { - "bbox": 12517, + "bbox": 12826, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 3, "mirror": "", @@ -9124,9 +9141,10 @@ "tags": "section3" }, "bc423a55-8a1d-42b1-82ee-24b38662af63": { - "bbox": 12521, + "bbox": 12830, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 7, "mirror": "", @@ -9141,7 +9159,7 @@ 0, 0 ], - "bbox": 12527, + "bbox": 12836, "colour": "black", "endx": 1275.0, "endy": 150.0, @@ -9154,50 +9172,53 @@ }, "d6a52ed1-1079-413c-ba46-7d8bb9b44dfd": { "background": "grey85", - "bbox": 12530, + "bbox": 12839, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 9, "justify": 2, "posx": 525.0, "posy": 187.0, - "tags": "d6a52ed1-1079-413c-ba46-7d8bb9b44dfd", + "tags": "textbox9", "text": "TT-DS5HS" }, "db5992bd-ce35-492b-9453-b8a860f4a69b": { "background": "grey85", - "bbox": 12533, + "bbox": 12842, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 10, "justify": 2, "posx": 149.0, "posy": 313.0, - "tags": "db5992bd-ce35-492b-9453-b8a860f4a69b", + "tags": "textbox10", "text": "TT-DS10" }, "e5810a77-a225-4247-acd8-0df551fe4184": { "background": "grey85", - "bbox": 12536, + "bbox": 12845, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 11, "justify": 2, "posx": 351.0, "posy": 187.0, - "tags": "e5810a77-a225-4247-acd8-0df551fe4184", + "tags": "textbox11", "text": "TT-DS5HS" }, "e9341909-f8c6-4799-9f1f-c68dfa243425": { @@ -9212,7 +9233,7 @@ false, "" ], - "bbox": 12553, + "bbox": 12862, "dccaspects": [ [ [ @@ -10082,7 +10103,7 @@ true, "14" ], - "bbox": 12571, + "bbox": 12880, "dccaspects": [ [ [ @@ -10952,9 +10973,10 @@ ] }, "eca7185e-bfed-4cfb-8430-a3153bc29573": { - "bbox": 12575, + "bbox": 12884, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 10, "mirror": "", @@ -10964,18 +10986,19 @@ }, "edf730b4-253d-4ac9-a473-0a3ba0e72183": { "background": "grey85", - "bbox": 12578, + "bbox": 12887, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 12, "justify": 2, "posx": 748.0, "posy": 185.0, - "tags": "edf730b4-253d-4ac9-a473-0a3ba0e72183", + "tags": "textbox12", "text": "TT-DS10L" }, "f49fbf77-4587-42fb-b0a8-45d646a96a95": { @@ -10990,7 +11013,7 @@ false, "" ], - "bbox": 12595, + "bbox": 12904, "dccaspects": [ [ [ @@ -11849,9 +11872,10 @@ ] }, "f85047ff-ffac-4342-835a-98803dee39b6": { - "bbox": 12599, + "bbox": 12908, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 4, "mirror": "", @@ -11866,7 +11890,7 @@ 0, 0 ], - "bbox": 12605, + "bbox": 12914, "colour": "black", "endx": 775.0, "endy": 225.0, @@ -11878,9 +11902,10 @@ "tags": "line1" }, "fdcafbec-2869-438c-ba72-2a8d5d849f58": { - "bbox": 12609, + "bbox": 12918, "defaultlabel": "XXXXX", "editable": true, + "hidden": false, "item": "section", "itemid": 9, "mirror": "", @@ -11890,18 +11915,19 @@ }, "ff002fef-4577-4b37-8b4c-c09a758ae29f": { "background": "grey85", - "bbox": 12612, + "bbox": 12921, "border": 0, "colour": "black", "font": "Courier", "fontsize": 8, "fontstyle": "", + "hidden": false, "item": "textbox", - "itemid": 0, + "itemid": 13, "justify": 2, "posx": 501.0, "posy": 313.0, - "tags": "ff002fef-4577-4b37-8b4c-c09a758ae29f", + "tags": "textbox13", "text": "TT-DS10" } }, @@ -11980,11 +12006,12 @@ }, "general": { "automation": true, + "buttonsize": 8, "editmode": false, "filename": "/home/john/model-railway-signalling/user_guide/quickstart_example3.sig", "info": "Document your layout here", "spadpopups": false, - "version": "Version 4.6.1" + "version": "Version 4.7.0" }, "gpio": { "portmappings": [ diff --git a/user_guide/sensor_node_guide_v2.pdf b/user_guide/sensor_node_guide_v2.pdf new file mode 100644 index 00000000..2051a9ef Binary files /dev/null and b/user_guide/sensor_node_guide_v2.pdf differ diff --git a/user_guide/setup_guide_v5.pdf b/user_guide/setup_guide_v5.pdf deleted file mode 100644 index 0e115eaf..00000000 Binary files a/user_guide/setup_guide_v5.pdf and /dev/null differ diff --git a/user_guide/setup_guide_v6.odt b/user_guide/setup_guide_v6.odt new file mode 100644 index 00000000..92db272c Binary files /dev/null and b/user_guide/setup_guide_v6.odt differ