Coverage for /home/pi/Software/model-railway-signalling/model_railway_signals/editor/objects/objects_signals.py: 99%
268 statements
« prev ^ index » next coverage.py v7.2.7, created at 2024-04-10 15:08 +0100
« prev ^ index » next coverage.py v7.2.7, created at 2024-04-10 15:08 +0100
1#------------------------------------------------------------------------------------
2# This module contains all the functions for managing Signal objects. And also
3# Track Sensors as they are inherently linked to signal passed/approached events
4#------------------------------------------------------------------------------------
5#
6# External API functions / objects intended for use by other editor modules:
7# create_signal(type,subtype) - Create a default signal object on the schematic
8# delete_signal(object_id) - Hard Delete an object when deleted from the schematic
9# update_signal(obj_id,new_obj) - Update the configuration of an existing signal object
10# paste_signal(object) - Paste a copy of an object to create a new one (returns new object_id)
11# delete_signal_object(object_id) - soft delete the drawing object (prior to recreating)
12# redraw_signal_object(object_id) - Redraw the object on the canvas following an update
13# default_signal_object - The dictionary of default values for the object
14# mqtt_update_signals(pub_list, sub_list) - Configure MQTT publish/subscribe
15# mqtt_update_sensors(pub_list, sub_list) - Configure MQTT publish/subscribe
16# update_local_sensors(trigger,timeout,mappings) - Configure local track sensors
17# remove_references_to_point (point_id) - remove point references from the interlocking tables
18# update_references_to_point(old_pt_id, new_pt_id) - update point_id in the interlocking tables
19# remove_references_to_section (sec_id) - remove section references from the interlocking tables
20# update_references_to_section(old_id, new_id) - update section_id in the interlocking tables
21# remove_references_to_instrument (inst_id) - remove instr references from the interlocking tables
22# update_references_to_instrument(old_id, new_id) - update inst_id in the interlocking tables
23#
24# Makes the following external API calls to other editor modules:
25# objects_common.set_bbox - to create/update the boundary box for the schematic object
26# objects_common.find_initial_canvas_position - to find the next 'free' canvas position
27# objects_common.new_item_id - to find the next 'free' item ID when creating objects
28# objects_common.signal - To get The Object_ID for a given Item_ID
29# objects_points.reset_point_interlocking_tables() - recalculate interlocking tables
30#
31# Accesses the following external editor objects directly:
32# run_layout.schematic_callback - setting the object callbacks when created/recreated
33# objects_common.schematic_objects - the master dictionary of Schematic Objects
34# objects_common.signal_index - The Index of Signal Objects (for iterating)
35# objects_common.default_object - The common dictionary element for all objects
36# objects_common.object_type - The Enumeration of supported objects
37# objects_common.canvas - Reference to the Tkinter drawing canvas
38#
39# Accesses the following external library objects directly:
40# signals_common.sig_exists - Common function to see if a given item exists
41# signals_common.sig_type - for setting the enum value when creating the object
42# signals_colour_lights.signal_sub_type - for setting the enum value when creating the object
43# signals_semaphores.semaphore_sub_type - for setting the enum value when creating the object
44# signals_ground_position.ground_pos_sub_type - for setting the enum value when creating the object
45# signals_ground_disc.ground_disc_sub_type - for setting the enum value when creating the object
46#
47# Makes the following external API calls to library modules:
48# signals.update_signal(id) - To set the initial colour light signal aspect following creation
49# signals.set_route(id,route) - To set the initial route for a signal following creation
50# signals_colour_lights.create_colour_light_signal - To create the library object (create or redraw)
51# signals_semaphores.create_semaphore_signal - To create the library object (create or redraw)
52# signals_ground_position.create_ground_position_signal - To create the library object (create or redraw)
53# signals_ground_disc.create_ground_disc_signal - To create the library object (create or redraw)
54# signals_common.get_tags(id) - get the canvas 'tags' for the signal drawing objects
55# signals_common.delete_signal(id) - delete library drawing object (part of soft delete)
56# signals.reset_mqtt_configuration - reset MQTT networking prior to reconfiguration
57# signals.set_signals_to_publish_state(IDs) - configure MQTT networking
58# signals.subscribe_to_remote_signal(ID,callback) - configure MQTT networking
59# dcc_control.delete_signal_mapping - delete the existing DCC mapping for the signal
60# dcc_control.map_dcc_signal - to create a new DCC mapping for the signal
61# dcc_control.map_semaphore_signal - to create a new DCC mapping for the signal
62# gpio_sensors.add_gpio_sensor_callback - To set up a GPIO Sensor triggered callback
63# gpio_sensors.remove_gpio_sensor_callback - To remove any GPIO Sensor triggered callbacks
64#
65#------------------------------------------------------------------------------------
67import uuid
68import copy
70from ...library import signals
71from ...library import signals_common
72from ...library import signals_colour_lights
73from ...library import signals_semaphores
74from ...library import signals_ground_position
75from ...library import signals_ground_disc
76from ...library import dcc_control
77from ...library import gpio_sensors
79from . import objects_common
80from . import objects_points
81from .. import run_layout
83#------------------------------------------------------------------------------------
84# Default Signal Objects (i.e. state at creation)
85#------------------------------------------------------------------------------------
87# This is the default signal object definition
88default_signal_object = copy.deepcopy(objects_common.default_object)
89default_signal_object["item"] = objects_common.object_type.signal
90default_signal_object["itemtype"] = signals_common.sig_type.colour_light.value
91default_signal_object["itemsubtype"] = signals_colour_lights.signal_sub_type.four_aspect.value
92default_signal_object["orientation"] = 0
93default_signal_object["subsidary"] = [False,0] # [has_subsidary, dcc_address]
94default_signal_object["theatreroute"] = False
95default_signal_object["feathers"] = [False,False,False,False,False] # [MAIN,LH1,LH2,RH1,RH2]
96default_signal_object["dccautoinhibit"] = False
97# Interlock a distant signal with all home signals ahead
98default_signal_object["interlockahead"] = False
99# The signal arms table comprises a list of route elements: [main, LH1, LH2, RH1, RH2]
100# Each Route element comprises a list of signal elements: [sig, sub, dist]
101# Each signal element comprises [enabled/disabled, associated DCC address]
102default_signal_object["sigarms"] = [
103 [ [True,0],[False,0],[False,0] ],
104 [ [False,0],[False,0],[False,0] ],
105 [ [False,0],[False,0],[False,0] ],
106 [ [False,0],[False,0],[False,0] ],
107 [ [False,0],[False,0],[False,0] ] ]
108# The DCC aspects table comprises a list of DCC command sequences: [grn, red, ylw, dylw, fylw, fdylw]
109# Each DCC command sequence comprises a list of DCC commands [dcc1, dcc2, dcc3, dcc4, dcc5, dcc6]
110# Each DCC command comprises: [DCC address, DCC state]
111default_signal_object["dccaspects"] = [
112 [[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],
113 [[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],
114 [[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],
115 [[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],
116 [[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],
117 [[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]] ]
118# The DCC Feathers table comprises a list of DCC command sequences: [dark, main, lh1, lh2, rh1, rh2]
119# Note that 'dark' is the DCC command sequence to inhibit all route indications
120# Each DCC command sequence comprises a list of DCC commands: [dcc1, dcc2, dcc3, dcc4, dcc5, dcc6]
121# Each DCC command comprises: [DCC address, DCC state]
122default_signal_object["dccfeathers"] = [
123 [[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],
124 [[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],
125 [[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],
126 [[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],
127 [[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],
128 [[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]] ]
129# The DCC Theatre table comprises a list of route elements: [dark, main, lh1, lh2, rh1, rh2]
130# Note that 'dark' is the DCC route element to inhibit all route indications ('#')
131# Each route element comprises: [character to be displayed, associated DCC command sequence]
132# Each DCC command sequence comprises a list of DCC commands: [dcc1, dcc2, dcc3, dcc4, dcc5, dcc6]
133# Each DCC command comprises: [DCC address, DCC state]
134default_signal_object["dcctheatre"] = [
135 ["#", [[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]]],
136 ["", [[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]]],
137 ["", [[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]]],
138 ["", [[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]]],
139 ["", [[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]]],
140 ["", [[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]]] ]
141# This is the default point interlocking table for a signal
142# The table comprises a list of route elements: [main, lh1, lh2, rh1, rh2]
143# Each route element comprises: [[p1, p2, p3, p4, p5, p6] sig_id, block_id]
144# Where Each point element (in the list of points) comprises [point_id, point_state]
145# Note that Sig IDs in this case are strings (local or remote IDs)
146default_signal_object["pointinterlock"] = [
147 [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],"",0],
148 [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],"",0],
149 [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],"",0],
150 [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],"",0],
151 [[[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]],"",0] ]
152# This is the default Track Section interlocking table for a signal
153# Track Section interlocking table comprises a list of routes: [MAIN, LH1, LH2, RH1, RH2]
154# Each route element contains a list of interlocked sections for that route [t1,t2,t3]
155# Each entry is the ID of a (loacl) track section the signal is to be interlocked with
156default_signal_object["trackinterlock"] = [ [0,0,0], [0,0,0], [0,0,0], [0,0,0], [0,0,0] ]
157# This is the default opposing signal interlocking table for a signal
158# The table comprises a list of route elements [main, lh1, lh2, rh1, rh2]
159# Each route element comprises a list of signals [sig1, sig2, sig3, sig4]
160# Each signal element comprises [sig_id, [main, lh1, lh2, rh1, rh2]]
161# Where each route element is a boolean value (True or False)
162default_signal_object["siginterlock"] = [
163 [ [0, [False, False, False, False, False]],
164 [0, [False, False, False, False, False]],
165 [0, [False, False, False, False, False]],
166 [0, [False, False, False, False, False]] ],
167 [ [0, [False, False, False, False, False]],
168 [0, [False, False, False, False, False]],
169 [0, [False, False, False, False, False]],
170 [0, [False, False, False, False, False]] ],
171 [ [0, [False, False, False, False, False]],
172 [0, [False, False, False, False, False]],
173 [0, [False, False, False, False, False]],
174 [0, [False, False, False, False, False]] ],
175 [ [0, [False, False, False, False, False]],
176 [0, [False, False, False, False, False]],
177 [0, [False, False, False, False, False]],
178 [0, [False, False, False, False, False]] ],
179 [ [0, [False, False, False, False, False]],
180 [0, [False, False, False, False, False]],
181 [0, [False, False, False, False, False]],
182 [0, [False, False, False, False, False]] ] ]
183# Set the default route selections for the signal
184default_signal_object["sigroutes"] = [True,False,False,False,False]
185default_signal_object["subroutes"] = [False,False,False,False,False]
186# Set the default automation tables for the signal
187default_signal_object["passedsensor"] = [True,""] # [button, linked track sensor]
188default_signal_object["approachsensor"] = [False,""] # [button, linked track sensor]
189# Track sections is a list of [section_behind, sections_ahead]
190# sections_ahead is a list of the available signal routes [MAIN,LH1,LH2,RH1,RH2]
191# each route element comprises a list of track sections [T1, T2, T3]
192default_signal_object["tracksections"] = [0, [ [0,0,0], [0,0,0], [0,0,0], [0,0,0], [0,0,0]]]
193# General automation settings for the signal
194# 'overrideahead' will override distant if any home signals ahead are at DANGER
195default_signal_object["fullyautomatic"] = False # Main signal is automatic (no button)
196default_signal_object["distautomatic"] = False # Semaphore associated distant is automatic
197default_signal_object["overrideahead"] = False
198default_signal_object["overridesignal"] = False
199# Approach_Control comprises a list of routes [MAIN, LH1, LH2, RH1, RH2]
200# Each element represents the approach control mode that has been set
201# release_on_red=1, release_on_yel=2, released_on_red_home_ahead=3
202default_signal_object["approachcontrol"] = [0, 0, 0, 0, 0]
203# A timed_sequence comprises a list of routes [MAIN, LH1, LH2, RH1, RH2]
204# Each route comprises a list of [selected, sig_id,start_delay, time_delay)
205default_signal_object["timedsequences"] = [ [False, 0, 0, 0],
206 [False, 0, 0, 0],
207 [False, 0, 0, 0],
208 [False, 0, 0, 0],
209 [False, 0, 0, 0] ]
211#------------------------------------------------------------------------------------
212# Internal Helper function to test if a semaphore has an associated distant signal
213#------------------------------------------------------------------------------------
215def has_associated_distant(object_id):
216 return ( objects_common.schematic_objects[object_id]["sigarms"][0][2][0] or
217 objects_common.schematic_objects[object_id]["sigarms"][1][2][0] or
218 objects_common.schematic_objects[object_id]["sigarms"][2][2][0] or
219 objects_common.schematic_objects[object_id]["sigarms"][3][2][0] or
220 objects_common.schematic_objects[object_id]["sigarms"][4][2][0] )
222#------------------------------------------------------------------------------------
223# Internal function to Remove any references to a signal (on deletion)
224# Signal 'pointinterlock' comprises a list of routes: [main, lh1, lh2, rh1, rh2]
225# Each route element comprises: [[p1, p2, p3, p4, p5, p6, p7], sig_id, block_id]
226# Where sig_id in this case is a string (for local or remote signals)
227# Signal 'siginterlock' comprises a list of routes [main, lh1, lh2, rh1, rh2]
228# Each route element comprises a list of signals [sig1, sig2, sig3, sig4]
229# Each signal element comprises [sig_id, [main, lh1, lh2, rh1, rh2]]
230# Where each route element is a boolean value (True or False)
231#------------------------------------------------------------------------------------
233def remove_references_to_signal(deleted_sig_id:int):
234 for signal_id in objects_common.signal_index:
235 # Get the Object ID for the signal
236 sig_object = objects_common.signal(signal_id)
237 # Remove any references from the signal ahead on the interlocked routes
238 list_of_interlocked_point_routes = objects_common.schematic_objects[sig_object]["pointinterlock"]
239 for index1, interlocked_route in enumerate(list_of_interlocked_point_routes):
240 if interlocked_route[1] == str(deleted_sig_id):
241 objects_common.schematic_objects[sig_object]["pointinterlock"][index1][1] = ""
242 # Remove and references from the conflicting signals
243 list_of_interlocked_signal_routes = objects_common.schematic_objects[sig_object]["siginterlock"]
244 # Iterate through the list of routes in the interlocking table
245 for index1, interlocked_route in enumerate(list_of_interlocked_signal_routes):
246 # Each route contains a list of up to 4 conflicting signals
247 list_of_conflicting_signals = list_of_interlocked_signal_routes[index1]
248 # Create a new 'blank' list for copying the signals (that haven't been deleted) across
249 # We do this to 'tidy up' the list (i.e. remove the 'blanks' caused by signal removals)
250 null_entry = [0, [False, False, False, False, False]]
251 new_list_of_conflicting_signals = [null_entry, null_entry, null_entry, null_entry]
252 index2 = 0
253 # Iterate through each signal on the route in the interlocking table
254 # to build up the new list of signals (that are to be retained)
255 for conflicting_signal in list_of_conflicting_signals:
256 if conflicting_signal[0] != deleted_sig_id:
257 new_list_of_conflicting_signals[index2] = conflicting_signal
258 index2 = index2 + 1
259 # replace the list of conflicting signals
260 objects_common.schematic_objects[sig_object]["siginterlock"][index1] = new_list_of_conflicting_signals
261 # Remove any "Trigger Timed signal" references to the signal
262 list_of_timed_sequences = objects_common.schematic_objects[sig_object]["timedsequences"]
263 for index1, timed_sequence in enumerate(list_of_timed_sequences):
264 if timed_sequence[1] == deleted_sig_id:
265 objects_common.schematic_objects[sig_object]["timedsequences"][index1] = [False,0,0,0]
266 return()
268#------------------------------------------------------------------------------------
269# Internal Function to Update any signal references when signal ID is changed
270# Signal 'pointinterlock' comprises: [main, lh1, lh2, rh1, rh2]
271# Each route comprises: [[p1, p2, p3, p4, p5, p6, p7], sig_id, inst_id]
272# Note that the sig_id in this case is a string (for local or remote signals)
273# Signal 'siginterlock' comprises a list of routes [main, lh1, lh2, rh1, rh2]
274# Each route element comprises a list of signals [sig1, sig2, sig3, sig4]
275# Each signal element comprises [sig_id, [main, lh1, lh2, rh1, rh2]]
276# Where each route element is a boolean value (True or False)
277#------------------------------------------------------------------------------------
279def update_references_to_signal(old_sig_id:int, new_sig_id:int):
280 # Iterate through all the signals on the schematic
281 for signal_id in objects_common.signal_index:
282 # Get the Object ID for the signal
283 sig_object = objects_common.signal(signal_id)
284 # Update any references for the signal ahead (on the interlocked routes)
285 # We use strings as the signal ahead IDs support local or remote sections
286 list_of_interlocked_point_routes = objects_common.schematic_objects[sig_object]["pointinterlock"]
287 for index1, interlocked_route in enumerate (list_of_interlocked_point_routes):
288 if interlocked_route[1] == str(old_sig_id):
289 objects_common.schematic_objects[sig_object]["pointinterlock"][index1][1] = str(new_sig_id)
290 # Update any references for conflicting signals
291 list_of_interlocked_signal_routes = objects_common.schematic_objects[sig_object]["siginterlock"]
292 for index1, interlocked_route in enumerate(list_of_interlocked_signal_routes):
293 list_of_conflicting_signals = list_of_interlocked_signal_routes[index1]
294 for index2, conflicting_signal in enumerate(list_of_conflicting_signals):
295 if conflicting_signal[0] == old_sig_id:
296 objects_common.schematic_objects[sig_object]["siginterlock"][index1][index2][0] = new_sig_id
297 # Update any "Trigger Timed signal" references to the signal (either from
298 # the current signal or another signal on the schematic (ahead of the signal)
299 list_of_timed_sequences = objects_common.schematic_objects[sig_object]["timedsequences"]
300 for index1, timed_sequence in enumerate(list_of_timed_sequences):
301 if timed_sequence[1] == old_sig_id:
302 objects_common.schematic_objects[sig_object]["timedsequences"][index1][1] = new_sig_id
303 return()
305#------------------------------------------------------------------------------------
306# Function to remove all references to a point from the signal interlocking tables
307# Signal 'pointinterlock' comprises a list of routes: [main, lh1, lh2, rh1, rh2]
308# Each route element comprises: [[p1, p2, p3, p4, p5, p6, p7], sig_id, block_id]
309# Where sig_id in this case is a string (for local or remote signals)
310#------------------------------------------------------------------------------------
312def remove_references_to_point(point_id:int):
313 # Iterate through all the signals on the schematic
314 for signal_id in objects_common.signal_index:
315 # Get the Object ID of the signal
316 sig_object = objects_common.signal(signal_id)
317 # Iterate through each route in the interlocking table
318 interlocking_table = objects_common.schematic_objects[sig_object]["pointinterlock"]
319 for index1, interlocked_route in enumerate(interlocking_table):
320 list_of_interlocked_points = interlocked_route[0]
321 # Create a new 'blank' list for copying the points (that haven't been deleted) across
322 # We do this to 'tidy up' the list (i.e. remove the 'blanks' caused by the point removal)
323 new_list_of_interlocked_points = [[0,False],[0,False],[0,False],[0,False],[0,False],[0,False]]
324 index2 = 0
325 # Iterate through each point on the route in the interlocking table
326 # to build up the new list of points (that are to be retained)
327 for interlocked_point in list_of_interlocked_points:
328 if interlocked_point[0] != point_id:
329 new_list_of_interlocked_points[index2] = interlocked_point
330 index2 = index2 +1
331 # Replace the list of interlocked points
332 objects_common.schematic_objects[sig_object]["pointinterlock"][index1][0]= new_list_of_interlocked_points
333 return()
335#------------------------------------------------------------------------------------
336# Function to update any references to a Point in the signal interlocking tables
337#------------------------------------------------------------------------------------
339def update_references_to_point(old_point_id:int, new_point_id:int):
340 # Iterate through all the signals on the schematic
341 for signal_id in objects_common.signal_index:
342 # Get the Object ID of the signal
343 sig_object = objects_common.signal(signal_id)
344 # Iterate through each route in the interlocking table and then the points on each route
345 interlocking_table = objects_common.schematic_objects[sig_object]["pointinterlock"]
346 for index1, interlocked_route in enumerate(interlocking_table):
347 list_of_interlocked_points = interlocked_route[0]
348 for index2, interlocked_point in enumerate(list_of_interlocked_points):
349 if interlocked_point[0] == old_point_id:
350 objects_common.schematic_objects[sig_object]["pointinterlock"][index1][0][index2][0] = new_point_id
351 return()
353#------------------------------------------------------------------------------------
354# Function to remove references to a Track Section from the signal automation tables
355# 'tracksections' is a list of [section_behind, sections_ahead]
356# where sections_ahead is a list of [MAIN,LH1,LH2,RH1,RH2]
357#------------------------------------------------------------------------------------
359def remove_references_to_section(section_id:int):
360 # Iterate through all the signals on the schematic
361 for signal_id in objects_common.signal_index:
362 # Get the Object ID of the signal
363 sig_object = objects_common.signal(signal_id)
364 # Get the table of track sections behind/ahead of the signal
365 track_sections = objects_common.schematic_objects[sig_object]["tracksections"]
366 # Check the track section behind the signal
367 if track_sections[0] == section_id:
368 track_sections[0] = 0
369 # Check the track sections in front of the signal
370 for index1, list_of_sections_ahead in enumerate(track_sections[1]):
371 for index2, section_ahead in enumerate (list_of_sections_ahead):
372 if section_ahead == section_id:
373 objects_common.schematic_objects[sig_object]["tracksections"][1][index1][index2] = 0
374 # Check the track interlocking table
375 track_interlocking = objects_common.schematic_objects[sig_object]["trackinterlock"]
376 for index1, route in enumerate(track_interlocking):
377 for index2, track_section in enumerate(route):
378 if track_section == section_id:
379 objects_common.schematic_objects[sig_object]["trackinterlock"][index1][index2] = 0
380 return()
382#------------------------------------------------------------------------------------
383# Function to update any references to a Track Section in the signal automation tables
384#------------------------------------------------------------------------------------
386def update_references_to_section(old_section_id:int, new_section_id:int):
387 # Iterate through all the signals on the schematic
388 for signal_id in objects_common.signal_index:
389 # Get the Object ID of the signal
390 sig_object = objects_common.signal(signal_id)
391 # Get the table of track sections behind/ahead of the signal
392 track_sections = objects_common.schematic_objects[sig_object]["tracksections"]
393 # Check the track section behind the signal
394 if track_sections[0] == old_section_id: track_sections[0] = new_section_id
395 # Check the track sections in front of the signal
396 for index1, list_of_sections_ahead in enumerate(track_sections[1]):
397 for index2, section_ahead in enumerate (list_of_sections_ahead):
398 if section_ahead == old_section_id:
399 objects_common.schematic_objects[sig_object]["tracksections"][1][index1][index2] = new_section_id
400 # Check the track interlocking table
401 track_interlocking = objects_common.schematic_objects[sig_object]["trackinterlock"]
402 for index1, route in enumerate(track_interlocking):
403 for index2, track_section in enumerate(route):
404 if track_section == old_section_id:
405 objects_common.schematic_objects[sig_object]["trackinterlock"][index1][index2] = new_section_id
406 return()
408#------------------------------------------------------------------------------------
409# Function to remove references to a Block Instrument from the signal interlocking tables
410# Signal 'pointinterlock' comprises a list of routes: [main, lh1, lh2, rh1, rh2]
411# Each route element comprises: [[p1, p2, p3, p4, p5, p6, p7], sig_id, block_id]
412# Where sig_id in this case is a string (for local or remote signals)
413#------------------------------------------------------------------------------------
415def remove_references_to_instrument(inst_id:int):
416 # Iterate through all the signals on the schematic
417 for signal_id in objects_common.signal_index:
418 # Get the Object ID of the signal
419 sig_object = objects_common.signal(signal_id)
420 # Iterate through each route in the interlocking table
421 interlocking_table = objects_common.schematic_objects[sig_object]["pointinterlock"]
422 for index1, interlocked_route in enumerate(interlocking_table):
423 if interlocked_route[2] == inst_id:
424 objects_common.schematic_objects[sig_object]["pointinterlock"][index1][2] = 0
425 return()
427#------------------------------------------------------------------------------------
428# Function to update any references to a Block Instrument in the signal interlocking tables
429#------------------------------------------------------------------------------------
431def update_references_to_instrument(old_inst_id:int, new_inst_id:int):
432 # Iterate through all the signals on the schematic
433 for signal_id in objects_common.signal_index:
434 # Get the Object ID of the signal
435 sig_object = objects_common.signal(signal_id)
436 # Iterate through each route in the interlocking table
437 interlocking_table = objects_common.schematic_objects[sig_object]["pointinterlock"]
438 for index, interlocked_route in enumerate (interlocking_table):
439 if interlocked_route[2] == old_inst_id:
440 objects_common.schematic_objects[sig_object]["pointinterlock"][index][2] = new_inst_id
441 return()
443#------------------------------------------------------------------------------------
444# Function to to update a signal object after a configuration change
445#------------------------------------------------------------------------------------
447def update_signal(object_id, new_object_configuration):
448 # We need to track whether the Item ID has changed
449 old_item_id = objects_common.schematic_objects[object_id]["itemid"]
450 new_item_id = new_object_configuration["itemid"]
451 # Delete the existing signal object, copy across the new configuration and redraw
452 # Note that the delete_signal_object function will also delete any DCC or sensor mappings
453 delete_signal_object(object_id)
454 objects_common.schematic_objects[object_id] = copy.deepcopy(new_object_configuration)
455 redraw_signal_object(object_id)
456 # Check to see if the Type-specific ID has been changed
457 if old_item_id != new_item_id:
458 # Update the type-specific index
459 del objects_common.signal_index[str(old_item_id)]
460 objects_common.signal_index[str(new_item_id)] = object_id
461 # Update any "signal Ahead" references when signal ID is changed
462 update_references_to_signal(old_item_id, new_item_id)
463 # Recalculate point interlocking tables in case they are affected
464 objects_points.reset_point_interlocking_tables()
465 return()
467#------------------------------------------------------------------------------------
468# Function to redraw a Signal object on the schematic. Called when the object is first
469# created or after the object configuration has been updated.
470#------------------------------------------------------------------------------------
472def redraw_signal_object(object_id):
473 # Turn the signal type value back into the required enumeration type
474 sig_type = signals_common.sig_type(objects_common.schematic_objects[object_id]["itemtype"])
475 # Update the sensor mapping callbacks for the signal (if any have been specified)
476 if objects_common.schematic_objects[object_id]["passedsensor"][1] != "":
477 gpio_sensors.add_gpio_sensor_callback(objects_common.schematic_objects[object_id]["passedsensor"][1],
478 signal_passed = objects_common.schematic_objects[object_id]["itemid"] )
479 if objects_common.schematic_objects[object_id]["approachsensor"][1] != "":
480 gpio_sensors.add_gpio_sensor_callback(objects_common.schematic_objects[object_id]["approachsensor"][1],
481 signal_approach = objects_common.schematic_objects[object_id]["itemid"] )
482 # Create the DCC Mappings for the signal (depending on signal type)
483 if (sig_type == signals_common.sig_type.colour_light or
484 sig_type == signals_common.sig_type.ground_position):
485 # Create the new DCC Mapping for the Colour Light Signal
486 dcc_control.map_dcc_signal (objects_common.schematic_objects[object_id]["itemid"],
487 auto_route_inhibit = objects_common.schematic_objects[object_id]["dccautoinhibit"],
488 proceed = objects_common.schematic_objects[object_id]["dccaspects"][0],
489 danger = objects_common.schematic_objects[object_id]["dccaspects"][1],
490 caution = objects_common.schematic_objects[object_id]["dccaspects"][2],
491 prelim_caution = objects_common.schematic_objects[object_id]["dccaspects"][3],
492 flash_caution = objects_common.schematic_objects[object_id]["dccaspects"][4],
493 flash_prelim_caution = objects_common.schematic_objects[object_id]["dccaspects"][5],
494 NONE = objects_common.schematic_objects[object_id]["dccfeathers"][0],
495 MAIN = objects_common.schematic_objects[object_id]["dccfeathers"][1],
496 LH1 = objects_common.schematic_objects[object_id]["dccfeathers"][2],
497 LH2 = objects_common.schematic_objects[object_id]["dccfeathers"][3],
498 RH1 = objects_common.schematic_objects[object_id]["dccfeathers"][4],
499 RH2 = objects_common.schematic_objects[object_id]["dccfeathers"][5],
500 subsidary = objects_common.schematic_objects[object_id]["subsidary"][1],
501 THEATRE = objects_common.schematic_objects[object_id]["dcctheatre"] )
502 elif (sig_type == signals_common.sig_type.semaphore or 502 ↛ 526line 502 didn't jump to line 526, because the condition on line 502 was never false
503 sig_type == signals_common.sig_type.ground_disc):
504 # Create the new DCC Mapping for the Semaphore Signal
505 dcc_control.map_semaphore_signal (objects_common.schematic_objects[object_id]["itemid"],
506 main_signal = objects_common.schematic_objects[object_id]["sigarms"][0][0][1],
507 lh1_signal = objects_common.schematic_objects[object_id]["sigarms"][1][0][1],
508 lh2_signal = objects_common.schematic_objects[object_id]["sigarms"][2][0][1],
509 rh1_signal = objects_common.schematic_objects[object_id]["sigarms"][3][0][1],
510 rh2_signal = objects_common.schematic_objects[object_id]["sigarms"][4][0][1],
511 main_subsidary = objects_common.schematic_objects[object_id]["sigarms"][0][1][1],
512 lh1_subsidary = objects_common.schematic_objects[object_id]["sigarms"][1][1][1],
513 lh2_subsidary = objects_common.schematic_objects[object_id]["sigarms"][2][1][1],
514 rh1_subsidary = objects_common.schematic_objects[object_id]["sigarms"][3][1][1],
515 rh2_subsidary = objects_common.schematic_objects[object_id]["sigarms"][4][1][1],
516 THEATRE = objects_common.schematic_objects[object_id]["dcctheatre"] )
517 # Create the new DCC Mapping for the associated distant Signal if there is one
518 if has_associated_distant(object_id):
519 dcc_control.map_semaphore_signal (objects_common.schematic_objects[object_id]["itemid"]+100,
520 main_signal = objects_common.schematic_objects[object_id]["sigarms"][0][2][1],
521 lh1_signal = objects_common.schematic_objects[object_id]["sigarms"][1][2][1],
522 lh2_signal = objects_common.schematic_objects[object_id]["sigarms"][2][2][1],
523 rh1_signal = objects_common.schematic_objects[object_id]["sigarms"][3][2][1],
524 rh2_signal = objects_common.schematic_objects[object_id]["sigarms"][4][2][1] )
525 # Create the new signal object (according to the signal type)
526 if sig_type == signals_common.sig_type.colour_light:
527 # Turn the signal subtype value back into the required enumeration type
528 sub_type = signals_colour_lights.signal_sub_type(objects_common.schematic_objects[object_id]["itemsubtype"])
529 # Create the signal drawing object on the canvas
530 signals_colour_lights.create_colour_light_signal (
531 canvas = objects_common.canvas,
532 sig_id = objects_common.schematic_objects[object_id]["itemid"],
533 x = objects_common.schematic_objects[object_id]["posx"],
534 y = objects_common.schematic_objects[object_id]["posy"],
535 signal_subtype = sub_type,
536 sig_callback = run_layout.schematic_callback,
537 orientation = objects_common.schematic_objects[object_id]["orientation"],
538 sig_passed_button = objects_common.schematic_objects[object_id]["passedsensor"][0],
539 approach_release_button = objects_common.schematic_objects[object_id]["approachsensor"][0],
540 position_light = objects_common.schematic_objects[object_id]["subsidary"][0],
541 mainfeather = objects_common.schematic_objects[object_id]["feathers"][0],
542 lhfeather45 = objects_common.schematic_objects[object_id]["feathers"][1],
543 lhfeather90 = objects_common.schematic_objects[object_id]["feathers"][2],
544 rhfeather45 = objects_common.schematic_objects[object_id]["feathers"][3],
545 rhfeather90 = objects_common.schematic_objects[object_id]["feathers"][4],
546 theatre_route_indicator = objects_common.schematic_objects[object_id]["theatreroute"],
547 refresh_immediately = False,
548 fully_automatic = objects_common.schematic_objects[object_id]["fullyautomatic"])
549 # set the initial theatre route indication (for MAIN) for the signal if appropriate
550 if objects_common.schematic_objects[object_id]["theatreroute"]:
551 signals.set_route(sig_id = objects_common.schematic_objects[object_id]["itemid"],
552 theatre_text = objects_common.schematic_objects[object_id]["dcctheatre"][1][0])
553 # update the signal to show the initial aspect
554 signals.update_signal(objects_common.schematic_objects[object_id]["itemid"])
555 elif sig_type == signals_common.sig_type.semaphore:
556 # Turn the signal subtype value back into the required enumeration type
557 sub_type = signals_semaphores.semaphore_sub_type(objects_common.schematic_objects[object_id]["itemsubtype"])
558 # Create the signal drawing object on the canvas. Note that the main signal arm is always enabled for home
559 # or distant signals - it is only optional for secondary distant signals (created after the main signal)
560 signals_semaphores.create_semaphore_signal (
561 canvas = objects_common.canvas,
562 sig_id = objects_common.schematic_objects[object_id]["itemid"],
563 x = objects_common.schematic_objects[object_id]["posx"],
564 y = objects_common.schematic_objects[object_id]["posy"],
565 signal_subtype = sub_type,
566 sig_callback = run_layout.schematic_callback,
567 orientation = objects_common.schematic_objects[object_id]["orientation"],
568 sig_passed_button = objects_common.schematic_objects[object_id]["passedsensor"][0],
569 approach_release_button = objects_common.schematic_objects[object_id]["approachsensor"][0],
570 main_signal = True,
571 lh1_signal = objects_common.schematic_objects[object_id]["sigarms"][1][0][0],
572 lh2_signal = objects_common.schematic_objects[object_id]["sigarms"][2][0][0],
573 rh1_signal = objects_common.schematic_objects[object_id]["sigarms"][3][0][0],
574 rh2_signal = objects_common.schematic_objects[object_id]["sigarms"][4][0][0],
575 main_subsidary = objects_common.schematic_objects[object_id]["sigarms"][0][1][0],
576 lh1_subsidary = objects_common.schematic_objects[object_id]["sigarms"][1][1][0],
577 lh2_subsidary = objects_common.schematic_objects[object_id]["sigarms"][2][1][0],
578 rh1_subsidary = objects_common.schematic_objects[object_id]["sigarms"][3][1][0],
579 rh2_subsidary = objects_common.schematic_objects[object_id]["sigarms"][4][1][0],
580 theatre_route_indicator = objects_common.schematic_objects[object_id]["theatreroute"],
581 fully_automatic = objects_common.schematic_objects[object_id]["fullyautomatic"])
582 # Create the associated distant signal (signal_id = home_signal_id + 100)
583 if has_associated_distant(object_id):
584 # Create the signal drawing object on the canvas
585 signals_semaphores.create_semaphore_signal (
586 canvas = objects_common.canvas,
587 sig_id = objects_common.schematic_objects[object_id]["itemid"]+100,
588 x = objects_common.schematic_objects[object_id]["posx"],
589 y = objects_common.schematic_objects[object_id]["posy"],
590 signal_subtype = signals_semaphores.semaphore_sub_type.distant,
591 associated_home = objects_common.schematic_objects[object_id]["itemid"],
592 sig_callback = run_layout.schematic_callback,
593 orientation = objects_common.schematic_objects[object_id]["orientation"],
594 main_signal = objects_common.schematic_objects[object_id]["sigarms"][0][2][0],
595 lh1_signal = objects_common.schematic_objects[object_id]["sigarms"][1][2][0],
596 lh2_signal = objects_common.schematic_objects[object_id]["sigarms"][2][2][0],
597 rh1_signal = objects_common.schematic_objects[object_id]["sigarms"][3][2][0],
598 rh2_signal = objects_common.schematic_objects[object_id]["sigarms"][4][2][0],
599 fully_automatic = objects_common.schematic_objects[object_id]["distautomatic"])
600 elif sig_type == signals_common.sig_type.ground_position:
601 # Turn the signal subtype value back into the required enumeration type
602 sub_type = signals_ground_position.ground_pos_sub_type(objects_common.schematic_objects[object_id]["itemsubtype"])
603 # Create the signal drawing object on the canvas
604 signals_ground_position.create_ground_position_signal (
605 canvas = objects_common.canvas,
606 sig_id = objects_common.schematic_objects[object_id]["itemid"],
607 x = objects_common.schematic_objects[object_id]["posx"],
608 y = objects_common.schematic_objects[object_id]["posy"],
609 signal_subtype = sub_type,
610 sig_callback = run_layout.schematic_callback,
611 orientation = objects_common.schematic_objects[object_id]["orientation"],
612 sig_passed_button = objects_common.schematic_objects[object_id]["passedsensor"][0])
613 elif sig_type == signals_common.sig_type.ground_disc: 613 ↛ 627line 613 didn't jump to line 627, because the condition on line 613 was never false
614 # Turn the signal subtype value back into the required enumeration type
615 sub_type = signals_ground_disc.ground_disc_sub_type(objects_common.schematic_objects[object_id]["itemsubtype"])
616 # Create the signal drawing object on the canvas
617 signals_ground_disc.create_ground_disc_signal (
618 canvas = objects_common.canvas,
619 sig_id = objects_common.schematic_objects[object_id]["itemid"],
620 x = objects_common.schematic_objects[object_id]["posx"],
621 y = objects_common.schematic_objects[object_id]["posy"],
622 signal_subtype = sub_type,
623 sig_callback = run_layout.schematic_callback,
624 orientation = objects_common.schematic_objects[object_id]["orientation"],
625 sig_passed_button = objects_common.schematic_objects[object_id]["passedsensor"][0])
626 # Create/update the canvas "tags" and selection rectangle for the signal
627 objects_common.schematic_objects[object_id]["tags"] = signals_common.get_tags(objects_common.schematic_objects[object_id]["itemid"])
628 objects_common.set_bbox (object_id, objects_common.canvas.bbox(objects_common.schematic_objects[object_id]["tags"]))
629 return()
631#------------------------------------------------------------------------------------
632# Function to Create a new default signal (and draw it on the canvas)
633#------------------------------------------------------------------------------------
635def create_signal(item_type, item_subtype):
636 # Generate a new object from the default configuration with a new UUID
637 object_id = str(uuid.uuid4())
638 objects_common.schematic_objects[object_id] = copy.deepcopy(default_signal_object)
639 # Find the initial canvas position for the new object and assign the item ID
640 x, y = objects_common.find_initial_canvas_position()
641 item_id = objects_common.new_item_id(exists_function=signals_common.sig_exists)
642 # Add the specific elements for this particular instance of the object
643 objects_common.schematic_objects[object_id]["itemid"] = item_id
644 objects_common.schematic_objects[object_id]["itemtype"] = item_type
645 objects_common.schematic_objects[object_id]["itemsubtype"] = item_subtype
646 objects_common.schematic_objects[object_id]["posx"] = x
647 objects_common.schematic_objects[object_id]["posy"] = y
648 # Add the new object to the index of signals
649 objects_common.signal_index[str(item_id)] = object_id
650 # Draw the object on the canvas
651 redraw_signal_object(object_id)
652 return(object_id)
654#------------------------------------------------------------------------------------
655# Function to paste a copy of an existing signal - returns the new Object ID
656# Note that only the basic signal configuration is used. Underlying configuration
657# such as point interlocking, dcc addresses, automation etc is set back to the
658# default values as it will need to be configured specific to the new signal
659#------------------------------------------------------------------------------------
661def paste_signal(object_to_paste, deltax:int, deltay:int):
662 # Create a new UUID for the pasted object
663 new_object_id = str(uuid.uuid4())
664 objects_common.schematic_objects[new_object_id] = copy.deepcopy(object_to_paste)
665 # Assign a new type-specific ID for the object and add to the index
666 new_id = objects_common.new_item_id(exists_function=signals_common.sig_exists)
667 objects_common.schematic_objects[new_object_id]["itemid"] = new_id
668 objects_common.signal_index[str(new_id)] = new_object_id
669 # Set the position for the "pasted" object (offset from the original position)
670 objects_common.schematic_objects[new_object_id]["posx"] += deltax
671 objects_common.schematic_objects[new_object_id]["posy"] += deltay
672 # Now set the default values for all elements we don't want to copy:
673 # Enabled routes for the signal (all route definitions are cleared with interlocking)
674 objects_common.schematic_objects[new_object_id]["sigroutes"] = default_signal_object["sigroutes"]
675 objects_common.schematic_objects[new_object_id]["subroutes"] = default_signal_object["subroutes"]
676 # All interlocking elements (will be completely different for the new signal)
677 objects_common.schematic_objects[new_object_id]["pointinterlock"] = default_signal_object["pointinterlock"]
678 objects_common.schematic_objects[new_object_id]["trackinterlock"] = default_signal_object["trackinterlock"]
679 objects_common.schematic_objects[new_object_id]["siginterlock"] = default_signal_object["siginterlock"]
680 objects_common.schematic_objects[new_object_id]["interlockahead"] = default_signal_object["interlockahead"]
681 # All DCC Addresses (will be completely different for the new signal)
682 objects_common.schematic_objects[new_object_id]["dccaspects"] = default_signal_object["dccaspects"]
683 objects_common.schematic_objects[new_object_id]["dccfeathers"] = default_signal_object["dccfeathers"]
684 objects_common.schematic_objects[new_object_id]["dcctheatre"] = default_signal_object["dcctheatre"]
685 # Associated track sensors and sections (will need different GPIO inputs allocating)
686 objects_common.schematic_objects[new_object_id]["tracksections"] = default_signal_object["tracksections"]
687 objects_common.schematic_objects[new_object_id]["passedsensor"] = default_signal_object["passedsensor"]
688 objects_common.schematic_objects[new_object_id]["approachsensor"] = default_signal_object["approachsensor"]
689 # Any Timed Signal sequences or approach control need to be cleared
690 objects_common.schematic_objects[new_object_id]["timedsequences"] = default_signal_object["timedsequences"]
691 objects_common.schematic_objects[new_object_id]["approachcontrol"] = default_signal_object["approachcontrol"]
692 # Any override selections will need to be cleared (fully automatic selection can be left)
693 objects_common.schematic_objects[new_object_id]["overrideahead"] = default_signal_object["overrideahead"]
694 objects_common.schematic_objects[new_object_id]["overridesignal"] = default_signal_object["overridesignal"]
695 # Any DCC addresses for the semaphore signal arms
696 for index1,signal_route in enumerate(objects_common.schematic_objects[new_object_id]["sigarms"]):
697 for index2,signal_arm in enumerate(signal_route):
698 objects_common.schematic_objects[new_object_id]["sigarms"][index1][index2][1] = 0
699 # The DCC Address for the subsidary signal
700 objects_common.schematic_objects[new_object_id]["subsidary"][1] = 0
701 # Set the Boundary box for the new object to None so it gets created on re-draw
702 objects_common.schematic_objects[new_object_id]["bbox"] = None
703 # Create/draw the new object on the canvas
704 redraw_signal_object(new_object_id)
705 # No need to update the point interlocking tables as the pasted signal is
706 # created without any interlocking configuration - so nothing has changed
707 return(new_object_id)
709#------------------------------------------------------------------------------------
710# Function to "soft delete" the signal object from the canvas together with all
711# associated dcc mappings and track sensor mappings. Primarily used to delete the
712# signal in its current configuration prior to re-creating in its new configuration
713# following a configuration change - also used as part of a hard delete (below)
714#------------------------------------------------------------------------------------
716def delete_signal_object(object_id):
717 # Delete the signal drawing objects and associated DCC mapping
718 signals_common.delete_signal(objects_common.schematic_objects[object_id]["itemid"])
719 dcc_control.delete_signal_mapping(objects_common.schematic_objects[object_id]["itemid"])
720 # Delete the track sensor mappings for the signal (if any)
721 passed_sensor = objects_common.schematic_objects[object_id]["passedsensor"][1]
722 approach_sensor = objects_common.schematic_objects[object_id]["approachsensor"][1]
723 if passed_sensor != "": gpio_sensors.remove_gpio_sensor_callback(passed_sensor)
724 if approach_sensor != "": gpio_sensors.remove_gpio_sensor_callback(approach_sensor)
725 # Delete the associated distant signal (if there is one)
726 if has_associated_distant(object_id):
727 signals_common.delete_signal(objects_common.schematic_objects[object_id]["itemid"]+100)
728 dcc_control.delete_signal_mapping(objects_common.schematic_objects[object_id]["itemid"]+100)
729 return()
731#------------------------------------------------------------------------------------
732# Function to 'hard delete' a signal (drawing objects, DCC mappings, sensor mappings,
733# and the main dict entry). Function called when signal is deleted from the schematic.
734#------------------------------------------------------------------------------------
736def delete_signal(object_id):
737 # Soft delete the associated library objects from the canvas
738 delete_signal_object(object_id)
739 # Remove any references to the signal from other signals
740 remove_references_to_signal(objects_common.schematic_objects[object_id]["itemid"])
741 # "Hard Delete" the selected object - deleting the boundary box rectangle and deleting
742 # the object from the dictionary of schematic objects (and associated dictionary keys)
743 objects_common.canvas.delete(objects_common.schematic_objects[object_id]["bbox"])
744 del objects_common.signal_index[str(objects_common.schematic_objects[object_id]["itemid"])]
745 del objects_common.schematic_objects[object_id]
746 # Recalculate point interlocking tables to remove references to the signal
747 objects_points.reset_point_interlocking_tables()
748 return()
750#------------------------------------------------------------------------------------
751# Function to update the MQTT networking configuration for signals, namely
752# subscribing to remote signals and setting local signals to publish state
753# Note that the editor doesn't use signal passed events
754#------------------------------------------------------------------------------------
756def mqtt_update_signals(signals_to_publish:list, signals_to_subscribe_to:list):
757 signals_common.reset_mqtt_configuration()
758 signals.set_signals_to_publish_state(*signals_to_publish)
759 for signal_identifier in signals_to_subscribe_to:
760 signals.subscribe_to_remote_signal(signal_identifier, run_layout.schematic_callback)
761 return()
763####################################################################################