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

1# -------------------------------------------------------------------------------- 

2# This module is used for creating and managing Ground Position Light signal objects 

3# -------------------------------------------------------------------------------- 

4 

5from . import signals_common 

6from . import dcc_control 

7from . import file_interface 

8from . import common 

9 

10import logging 

11import enum 

12 

13# ------------------------------------------------------------------------- 

14# Classes used externally when creating/updating Ground Disk signals  

15# ------------------------------------------------------------------------- 

16 

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 

23 

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# ------------------------------------------------------------------------- 

28 

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) 

79 

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) 

87 

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 

94 

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 () 

111 

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# ------------------------------------------------------------------------- 

117 

118def update_ground_position_signal (sig_id:int): 

119 

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)" 

138 

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 

143 

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") 

149 

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") 

156 

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) 

160 

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) 

164 

165 return () 

166 

167###############################################################################