Coverage for /home/pi/Software/model-railway-signalling/model_railway_signals/library/signals_ground_position.py: 88%
89 statements
« prev ^ index » next coverage.py v7.2.7, created at 2024-04-05 17:29 +0100
« prev ^ index » next coverage.py v7.2.7, created at 2024-04-05 17:29 +0100
1# --------------------------------------------------------------------------------
2# This module is used for creating and managing Ground Position Light signal objects
3# --------------------------------------------------------------------------------
5from . import signals_common
6from . import dcc_control
7from . import file_interface
8from . import common
10import logging
11import enum
13# -------------------------------------------------------------------------
14# Classes used externally when creating/updating Ground Disk signals
15# -------------------------------------------------------------------------
17# Define the superset of signal sub types that can be created
18class ground_pos_sub_type(enum.Enum):
19 standard = 1
20 shunt_ahead = 2
21 early_standard = 3
22 early_shunt_ahead = 4
24# -------------------------------------------------------------------------
25# Public API function to create a Ground Position Signal (drawing objects and
26# internal state). By default the Signal is "NOT CLEAR" (i.e. set to DANGER)
27# -------------------------------------------------------------------------
29def create_ground_position_signal (canvas, sig_id:int, x:int, y:int,
30 signal_subtype=ground_pos_sub_type.early_standard,
31 sig_callback = None,
32 orientation:int = 0,
33 sig_passed_button: bool = False):
34 logging.info ("Signal "+str(sig_id)+": Creating Ground Position Signal")
35 # Do some basic validation on the parameters we have been given
36 if signals_common.sig_exists(sig_id): 36 ↛ 37line 36 didn't jump to line 37, because the condition on line 36 was never true
37 logging.error ("Signal "+str(sig_id)+": Signal already exists")
38 elif sig_id < 1: 38 ↛ 39line 38 didn't jump to line 39, because the condition on line 38 was never true
39 logging.error ("Signal "+str(sig_id)+": Signal ID must be greater than zero")
40 elif orientation != 0 and orientation != 180: 40 ↛ 41line 40 didn't jump to line 41, because the condition on line 40 was never true
41 logging.error ("Signal "+str(sig_id)+": Invalid orientation angle - only 0 and 180 currently supported")
42 else:
43 # Define the "Tag" for all drawing objects for this signal instance
44 sig_id_tag = "signal"+str(sig_id)
45 # Draw the signal base
46 line_coords = common.rotate_line (x,y,0,0,0,-25,orientation)
47 canvas.create_line (line_coords,width=2,tags=sig_id_tag)
48 # Draw the main body of signal
49 point_coords1 = common.rotate_point (x,y,0,-5,orientation)
50 point_coords2 = common.rotate_point (x,y,0,-25,orientation)
51 point_coords3 = common.rotate_point (x,y,+20,-25,orientation)
52 point_coords4 = common.rotate_point (x,y,+20,-20,orientation)
53 point_coords5 = common.rotate_point (x,y,+5,-5,orientation)
54 points = point_coords1, point_coords2, point_coords3, point_coords4, point_coords5
55 canvas.create_polygon (points, outline="black",tags=sig_id_tag)
56 # Create the position light "dark" aspects (i.e. when particular aspect is "not-lit")
57 # We don't need to create a "dark" aspect for the "root" position light as this is always lit
58 oval_coords = common.rotate_line (x,y,+9,-24,+16,-17,orientation)
59 canvas.create_oval (oval_coords,fill="grey",outline="black",tags=sig_id_tag)
60 oval_coords = common.rotate_line (x,y,+1,-24,+8,-17,orientation)
61 canvas.create_oval (oval_coords,fill="grey",outline="black",tags=sig_id_tag)
62 # Draw the "DANGER" and "PROCEED" aspects (initially hidden)
63 if signal_subtype in (ground_pos_sub_type.early_shunt_ahead,ground_pos_sub_type.shunt_ahead):
64 danger_colour = "gold"
65 else:
66 danger_colour = "red"
67 if signal_subtype in (ground_pos_sub_type.standard,ground_pos_sub_type.shunt_ahead):
68 root_colour = danger_colour
69 else:
70 root_colour = "white"
71 line_coords = common.rotate_line (x,y,+1,-14,+8,-7,orientation)
72 sigoff1 = canvas.create_oval (line_coords,fill="white",outline="black",state="hidden",tags=sig_id_tag)
73 line_coords = common.rotate_line (x,y,+9,-24,+16,-17,orientation)
74 sigoff2 = canvas.create_oval (line_coords,fill="white",outline="black",state="hidden",tags=sig_id_tag)
75 line_coords = common.rotate_line (x,y,+1,-14,+8,-7,orientation)
76 sigon1 = canvas.create_oval (line_coords,fill=root_colour,outline="black",state="hidden",tags=sig_id_tag)
77 line_coords = common.rotate_line (x,y,+1,-24,+8,-17,orientation)
78 sigon2 = canvas.create_oval (line_coords,fill=danger_colour,outline="black",state="hidden",tags=sig_id_tag)
80 # Create all of the signal elements common to all signal types
81 signals_common.create_common_signal_elements (canvas, sig_id, x, y,
82 signal_type = signals_common.sig_type.ground_position,
83 ext_callback = sig_callback,
84 orientation = orientation,
85 sig_passed_button = sig_passed_button,
86 tag = sig_id_tag)
88 # Add all of the signal-specific elements we need to manage Ground Position light signal types
89 signals_common.signals[str(sig_id)]["sig_subtype"] = signal_subtype # Type-specific - Signal Subtype
90 signals_common.signals[str(sig_id)]["sigoff1"] = sigoff1 # Type-specific - drawing object
91 signals_common.signals[str(sig_id)]["sigoff2"] = sigoff2 # Type-specific - drawing object
92 signals_common.signals[str(sig_id)]["sigon1"] = sigon1 # Type-specific - drawing object
93 signals_common.signals[str(sig_id)]["sigon2"] = sigon2 # Type-specific - drawing object
95 # Get the initial state for the signal (if layout state has been successfully loaded)
96 # Note that each element of 'loaded_state' will be 'None' if no data was loaded
97 loaded_state = file_interface.get_initial_item_state("signals",sig_id)
98 # Set the initial state from the "loaded" state - We only need to set the 'override' and
99 # 'sigclear' for ground signals - everything else gets set when the signal is updated
100 if loaded_state["override"]: signals_common.set_signal_override(sig_id)
101 if loaded_state["sigclear"]: signals_common.toggle_signal(sig_id)
102 # Update the signal to show the initial aspect (and send out DCC commands)
103 update_ground_position_signal(sig_id)
104 # finally Lock the signal if required
105 if loaded_state["siglocked"]: signals_common.lock_signal(sig_id)
106 # Publish the initial state to the broker (for other nodes to consume). Note that changes will
107 # only be published if the MQTT interface has been configured for publishing updates for this
108 # signal. This allows publish/subscribe to be configured prior to signal creation
109 signals_common.publish_signal_state(sig_id)
110 return ()
112# -------------------------------------------------------------------------
113# Internal function to Refresh the aspects of a ground position signal
114# Note that we expect this function to only ever get called on a state
115# change therefore we don't track the displayed aspect of the signal
116# -------------------------------------------------------------------------
118def update_ground_position_signal (sig_id:int):
120 # Establish what the signal should be displaying based on the state
121 if not signals_common.signals[str(sig_id)]["sigclear"]:
122 if ( signals_common.signals[str(sig_id)]["sig_subtype"] == ground_pos_sub_type.shunt_ahead or
123 signals_common.signals[str(sig_id)]["sig_subtype"] == ground_pos_sub_type.early_shunt_ahead ):
124 aspect_to_set = signals_common.signal_state_type.CAUTION
125 else:
126 aspect_to_set = signals_common.signal_state_type.DANGER
127 log_message = " (signal is ON)"
128 elif signals_common.signals[str(sig_id)]["override"]: 128 ↛ 129line 128 didn't jump to line 129, because the condition on line 128 was never true
129 if ( signals_common.signals[str(sig_id)]["sig_subtype"] == ground_pos_sub_type.shunt_ahead or
130 signals_common.signals[str(sig_id)]["sig_subtype"] == ground_pos_sub_type.early_shunt_ahead ):
131 aspect_to_set = signals_common.signal_state_type.CAUTION
132 else:
133 aspect_to_set = signals_common.signal_state_type.DANGER
134 log_message = " (signal is OVERRIDDEN)"
135 else:
136 aspect_to_set = signals_common.signal_state_type.PROCEED
137 log_message = " (signal is OFF)"
139 # Only refresh the signal if the aspect has been changed
140 if aspect_to_set != signals_common.signals[str(sig_id)]["sigstate"]:
141 logging.info ("Signal "+str(sig_id)+": Changing aspect to " + str(aspect_to_set).rpartition('.')[-1] + log_message)
142 signals_common.signals[str(sig_id)]["sigstate"] = aspect_to_set
144 if signals_common.signals[str(sig_id)]["sigstate"] == signals_common.signal_state_type.PROCEED:
145 signals_common.signals[str(sig_id)]["canvas"].itemconfig(signals_common.signals[str(sig_id)]["sigoff1"],state="normal")
146 signals_common.signals[str(sig_id)]["canvas"].itemconfig(signals_common.signals[str(sig_id)]["sigoff2"],state="normal")
147 signals_common.signals[str(sig_id)]["canvas"].itemconfig(signals_common.signals[str(sig_id)]["sigon1"],state="hidden")
148 signals_common.signals[str(sig_id)]["canvas"].itemconfig(signals_common.signals[str(sig_id)]["sigon2"],state="hidden")
150 elif ( signals_common.signals[str(sig_id)]["sigstate"] == signals_common.signal_state_type.DANGER or 150 ↛ 159line 150 didn't jump to line 159, because the condition on line 150 was never false
151 signals_common.signals[str(sig_id)]["sigstate"] == signals_common.signal_state_type.CAUTION):
152 signals_common.signals[str(sig_id)]["canvas"].itemconfig(signals_common.signals[str(sig_id)]["sigoff1"],state="hidden")
153 signals_common.signals[str(sig_id)]["canvas"].itemconfig(signals_common.signals[str(sig_id)]["sigoff2"],state="hidden")
154 signals_common.signals[str(sig_id)]["canvas"].itemconfig(signals_common.signals[str(sig_id)]["sigon1"],state="normal")
155 signals_common.signals[str(sig_id)]["canvas"].itemconfig(signals_common.signals[str(sig_id)]["sigon2"],state="normal")
157 # Send the required DCC bus commands to change the signal to the desired aspect. Note that commands will only
158 # be sent if the Pi-SPROG interface has been successfully configured and a DCC mapping exists for the signal
159 dcc_control.update_dcc_signal_aspects(sig_id, aspect_to_set)
161 # Publish the signal changes to the broker (for other nodes to consume). Note that state changes will only
162 # be published if the MQTT interface has been successfully configured for publishing updates for this signal
163 signals_common.publish_signal_state(sig_id)
165 return ()
167###############################################################################