Coverage for /home/pi/Software/model-railway-signalling/model_railway_signals/library/signals_semaphores.py: 84%

461 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2024-04-10 15:08 +0100

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

2# This module is used for creating and managing semaphore signal types 

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

4 

5from . import common 

6from . import signals_common 

7from . import dcc_control 

8from . import file_interface 

9 

10from typing import Union 

11import logging 

12import enum 

13 

14# ------------------------------------------------------------------------- 

15# Classes used externally when creating/updating semaphore signals  

16# ------------------------------------------------------------------------- 

17 

18# Define the superset of signal sub types that can be created 

19class semaphore_sub_type(enum.Enum): 

20 home = 1 

21 distant = 2 

22 

23# --------------------------------------------------------------------------------- 

24# Externally called Function to create a Semaphore Signal 'object'. The Signal is 

25# normally set to "NOT CLEAR" = RED unless its fully automatic - when its set to "CLEAR" 

26# All attributes (that need to be tracked) are stored as a dictionary which is then 

27# stored in the common dictionary of signals. Note that some elements in the dictionary 

28# are MANDATORY across all signal types (to allow mixing and matching of signal types) 

29# --------------------------------------------------------------------------------- 

30 

31def create_semaphore_signal (canvas, sig_id: int, x:int, y:int, 

32 signal_subtype=semaphore_sub_type.home, 

33 associated_home:int = 0, 

34 sig_callback = None, 

35 orientation:int = 0, 

36 sig_passed_button:bool=False, 

37 approach_release_button:bool=False, 

38 main_signal:bool=True, 

39 lh1_signal:bool=False, 

40 lh2_signal:bool=False, 

41 rh1_signal:bool=False, 

42 rh2_signal:bool=False, 

43 main_subsidary:bool=False, 

44 lh1_subsidary:bool=False, 

45 lh2_subsidary:bool=False, 

46 rh1_subsidary:bool=False, 

47 rh2_subsidary:bool=False, 

48 theatre_route_indicator:bool=False, 

49 refresh_immediately:bool = True, 

50 fully_automatic:bool=False): 

51 

52 # Do some basic validation on the parameters we have been given 

53 logging.info ("Signal "+str(sig_id)+": Creating Semaphore Signal") 

54 

55 has_subsidary = main_subsidary or lh1_subsidary or lh2_subsidary or rh1_subsidary or rh2_subsidary 

56 has_junction_arms = (lh1_subsidary or lh2_subsidary or rh1_subsidary or rh2_subsidary or 

57 lh1_signal or lh2_signal or rh1_signal or rh2_signal ) 

58 

59 if signals_common.sig_exists(sig_id): 59 ↛ 60line 59 didn't jump to line 60, because the condition on line 59 was never true

60 logging.error ("Signal "+str(sig_id)+": Signal already exists") 

61 elif sig_id < 1: 61 ↛ 62line 61 didn't jump to line 62, because the condition on line 61 was never true

62 logging.error ("Signal "+str(sig_id)+": Signal ID must be greater than zero") 

63 elif orientation != 0 and orientation != 180: 63 ↛ 64line 63 didn't jump to line 64, because the condition on line 63 was never true

64 logging.error ("Signal "+str(sig_id)+": Invalid orientation angle - only 0 and 180 currently supported") 

65 elif has_junction_arms and theatre_route_indicator: 65 ↛ 66line 65 didn't jump to line 66, because the condition on line 65 was never true

66 logging.error ("Signal "+str(sig_id)+": Signal can only have junction arms OR a Theatre Route Indicator") 

67 elif signal_subtype == semaphore_sub_type.distant and theatre_route_indicator: 67 ↛ 68line 67 didn't jump to line 68, because the condition on line 67 was never true

68 logging.error ("Signal "+str(sig_id)+": Distant signals should not have a Theatre Route Indicator") 

69 elif signal_subtype == semaphore_sub_type.distant and has_subsidary: 69 ↛ 70line 69 didn't jump to line 70, because the condition on line 69 was never true

70 logging.error ("Signal "+str(sig_id)+": Distant signals should not have subsidary signals") 

71 elif signal_subtype == semaphore_sub_type.distant and approach_release_button: 71 ↛ 72line 71 didn't jump to line 72, because the condition on line 71 was never true

72 logging.error ("Signal "+str(sig_id)+": Distant signals should not have Approach Release Control") 

73 elif associated_home > 0 and signal_subtype == semaphore_sub_type.home: 73 ↛ 74line 73 didn't jump to line 74, because the condition on line 73 was never true

74 logging.error ("Signal "+str(sig_id)+": Can only specify an associated signal for a distant signal") 

75 elif associated_home > 0 and not signals_common.sig_exists(associated_home): 75 ↛ 76line 75 didn't jump to line 76, because the condition on line 75 was never true

76 logging.error ("Signal "+str(sig_id)+": Associated signal "+str(associated_home)+" does not exist") 

77 elif associated_home > 0 and signals_common.signals[str(associated_home)]["sigtype"] != signals_common.sig_type.semaphore: 77 ↛ 78line 77 didn't jump to line 78, because the condition on line 77 was never true

78 logging.error ("Signal "+str(sig_id)+": Associated signal "+str(associated_home)+" is not a semaphore type") 

79 elif associated_home > 0 and signals_common.signals[str(associated_home)]["subtype"] == semaphore_sub_type.distant: 79 ↛ 80line 79 didn't jump to line 80, because the condition on line 79 was never true

80 logging.error ("Signal "+str(sig_id)+": Associated signal "+str(associated_home)+" is not a home signal") 

81 elif associated_home > 0 and sig_passed_button: 81 ↛ 82line 81 didn't jump to line 82, because the condition on line 81 was never true

82 logging.error ("Signal "+str(sig_id)+": Cannot create a signal passed button if associated with another signal") 

83 elif associated_home == 0 and not main_signal: 83 ↛ 84line 83 didn't jump to line 84, because the condition on line 83 was never true

84 logging.error ("Signal "+str(sig_id)+": Normal home and distant signals must have a signal arm for the main route") 

85 else: 

86 # Define the "Tag" for all drawing objects for this signal instance 

87 # If it is an associated distant then set the tag the same as the home signal 

88 if associated_home > 0: sig_id_tag = "signal"+str(associated_home) 

89 else: sig_id_tag = "signal"+str(sig_id) 

90 # Work out the offset for the post depending on the combination of signal arms. Note that if 

91 # this is a distant signal associated with another home signal then we'll use the post offset 

92 # for the existing signal (as there may be a different combination of home arms specified) 

93 # This to cater for the situation where not all home arms have an associated distant arm 

94 if associated_home > 0: 

95 postoffset = signals_common.signals[str(associated_home)]["postoffset"] 

96 elif (not rh2_signal and not rh2_subsidary and not rh1_signal and not rh1_subsidary ): 

97 if lh2_signal or lh2_subsidary: 

98 postoffset = -8 

99 elif lh1_signal or lh1_subsidary: 

100 postoffset = -15 

101 else: 

102 postoffset = -20 

103 elif rh2_signal or rh2_subsidary: 

104 postoffset = -37 

105 elif (not rh2_signal and not rh2_subsidary) and (not lh2_signal and not lh2_subsidary): 105 ↛ 108line 105 didn't jump to line 108, because the condition on line 105 was never false

106 postoffset = -22 

107 else: 

108 postoffset = -22 

109 lh2offset = postoffset-28 

110 lh1offset = postoffset-14 

111 rh1offset = postoffset+14 

112 rh2offset = postoffset+28 

113 

114 # Draw the signal base & signal post (unless this is a distant associated with an existing home signal 

115 # in which case the signal base & post will already have been drawn when the home signal was created 

116 # and we therefore only need to add the additional distant arms to the existing posts 

117 if associated_home == 0: 

118 line_coords = common.rotate_line(x,y,0,0,0,postoffset,orientation) 

119 canvas.create_line(line_coords,width=2,fill="white",tags=sig_id_tag) 

120 line_coords = common.rotate_line(x,y,0,postoffset,+70,postoffset,orientation) 

121 canvas.create_line(line_coords,width=3,fill="white",tags=sig_id_tag) 

122 # Draw the rest of the gantry to support other arms as required 

123 if lh2_signal or lh2_subsidary: 

124 line_coords = common.rotate_line(x,y,30,postoffset,30,lh2offset,orientation) 

125 canvas.create_line(line_coords,width=2,fill="white",tags=sig_id_tag) 

126 line_coords = common.rotate_line(x,y,30,lh2offset,40,lh2offset,orientation) 

127 canvas.create_line(line_coords,width=2,fill="white",tags=sig_id_tag) 

128 if lh2_signal: 128 ↛ 131line 128 didn't jump to line 131, because the condition on line 128 was never false

129 line_coords = common.rotate_line(x,y,40,lh2offset,65,lh2offset,orientation) 

130 canvas.create_line(line_coords,width=2,fill="white",tags=sig_id_tag) 

131 if lh1_signal or lh1_subsidary: 

132 line_coords = common.rotate_line(x,y,30,postoffset,30,lh1offset,orientation) 

133 canvas.create_line(line_coords,width=2,fill="white",tags=sig_id_tag) 

134 line_coords = common.rotate_line(x,y,30,lh1offset,40,lh1offset,orientation) 

135 canvas.create_line(line_coords,width=2,fill="white",tags=sig_id_tag) 

136 if lh1_signal: 

137 line_coords = common.rotate_line(x,y,40,lh1offset,65,lh1offset,orientation) 

138 canvas.create_line(line_coords,width=2,fill="white",tags=sig_id_tag) 

139 if rh2_signal or rh2_subsidary: 

140 line_coords = common.rotate_line(x,y,30,postoffset,30,rh2offset,orientation) 

141 canvas.create_line(line_coords,width=2,fill="white",tags=sig_id_tag) 

142 line_coords = common.rotate_line(x,y,30,rh2offset,40,rh2offset,orientation) 

143 canvas.create_line(line_coords,width=2,fill="white",tags=sig_id_tag) 

144 if rh2_signal: 144 ↛ 147line 144 didn't jump to line 147, because the condition on line 144 was never false

145 line_coords = common.rotate_line(x,y,40,rh2offset,65,rh2offset,orientation) 

146 canvas.create_line(line_coords,width=2,fill="white",tags=sig_id_tag) 

147 if rh1_signal or rh1_subsidary: 

148 line_coords = common.rotate_line(x,y,30,postoffset,30,rh1offset,orientation) 

149 canvas.create_line(line_coords,width=2,fill="white",tags=sig_id_tag) 

150 line_coords = common.rotate_line(x,y,30,rh1offset,40,rh1offset,orientation) 

151 canvas.create_line(line_coords,width=2,fill="white",tags=sig_id_tag) 

152 if rh1_signal: 152 ↛ 157line 152 didn't jump to line 157, because the condition on line 152 was never false

153 line_coords = common.rotate_line(x,y,40,rh1offset,65,rh1offset,orientation) 

154 canvas.create_line(line_coords,width=2,fill="white",tags=sig_id_tag) 

155 

156 # set the colour of the signal arm according to the signal type 

157 if signal_subtype == semaphore_sub_type.distant: arm_colour="yellow" 

158 else: arm_colour = "red" 

159 

160 # If this is a distant signal associated with an existing home signal then the distant arms need 

161 # to be created underneath the main home signal arms - we therefore need to apply a vertical offset 

162 if associated_home > 0: armoffset = -10 

163 else: armoffset = 0 

164 # Draw the signal arm for the main route 

165 line_coords = common.rotate_line(x,y,65+armoffset,postoffset+3,65+armoffset,postoffset-8,orientation) 

166 mainsigon = canvas.create_line(line_coords,fill=arm_colour,width=4,tags=sig_id_tag) 

167 line_coords = common.rotate_line(x,y,65+armoffset,postoffset+3,72+armoffset,postoffset-8,orientation) 

168 mainsigoff = canvas.create_line(line_coords,fill=arm_colour,width=4,state='hidden',tags=sig_id_tag) 

169 # Draw the subsidary arm for the main route 

170 line_coords = common.rotate_line(x,y,+43,postoffset+3,+43,postoffset-6,orientation) 

171 mainsubon = canvas.create_line(line_coords,fill=arm_colour,width=3,tags=sig_id_tag) 

172 line_coords = common.rotate_line(x,y,+43,postoffset+3,+48,postoffset-6,orientation) 

173 mainsuboff = canvas.create_line(line_coords,fill=arm_colour,width=3,state='hidden',tags=sig_id_tag) 

174 # Draw the signal arms for the RH routes 

175 line_coords = common.rotate_line(x,y,60+armoffset,rh1offset+2,60+armoffset,rh1offset-8,orientation) 

176 rh1sigon = canvas.create_line(line_coords,fill=arm_colour,width=4,tags=sig_id_tag) 

177 line_coords = common.rotate_line(x,y,60+armoffset,rh1offset+2,67+armoffset,rh1offset-8,orientation) 

178 rh1sigoff = canvas.create_line(line_coords,fill=arm_colour,width=4,state='hidden',tags=sig_id_tag) 

179 line_coords = common.rotate_line(x,y,60+armoffset,rh2offset+2,60+armoffset,rh2offset-8,orientation) 

180 rh2sigon = canvas.create_line(line_coords,fill=arm_colour,width=4,tags=sig_id_tag) 

181 line_coords = common.rotate_line(x,y,60+armoffset,rh2offset+2,67+armoffset,rh2offset-8,orientation) 

182 rh2sigoff = canvas.create_line(line_coords,fill=arm_colour,width=4,state='hidden',tags=sig_id_tag) 

183 # Draw the subsidary arms for the RH routes 

184 line_coords = common.rotate_line(x,y,+38,rh1offset+2,+38,rh1offset-6,orientation) 

185 rh1subon = canvas.create_line(line_coords,fill=arm_colour,width=3,tags=sig_id_tag) 

186 line_coords = common.rotate_line(x,y,+38,rh1offset+2,+43,rh1offset-6,orientation) 

187 rh1suboff = canvas.create_line(line_coords,fill=arm_colour,width=3,state='hidden',tags=sig_id_tag) 

188 line_coords = common.rotate_line(x,y,+38,rh2offset+2,+38,rh2offset-6,orientation) 

189 rh2subon = canvas.create_line(line_coords,fill=arm_colour,width=3,tags=sig_id_tag) 

190 line_coords = common.rotate_line(x,y,+38,rh2offset+2,+43,rh2offset-6,orientation) 

191 rh2suboff = canvas.create_line(line_coords,fill=arm_colour,width=3,state='hidden',tags=sig_id_tag) 

192 # Draw the signal arms for the LH routes 

193 line_coords = common.rotate_line(x,y,60+armoffset,lh1offset+2,60+armoffset,lh1offset-8,orientation) 

194 lh1sigon = canvas.create_line(line_coords,fill=arm_colour,width=4,tags=sig_id_tag) 

195 line_coords = common.rotate_line(x,y,60+armoffset,lh1offset+2,67+armoffset,lh1offset-8,orientation) 

196 lh1sigoff = canvas.create_line(line_coords,fill=arm_colour,width=4,state='hidden',tags=sig_id_tag) 

197 line_coords = common.rotate_line(x,y,60+armoffset,lh2offset+2,60+armoffset,lh2offset-8,orientation) 

198 lh2sigon = canvas.create_line(line_coords,fill=arm_colour,width=4,tags=sig_id_tag) 

199 line_coords = common.rotate_line(x,y,60+armoffset,lh2offset+2,67+armoffset,lh2offset-8,orientation) 

200 lh2sigoff = canvas.create_line(line_coords,fill=arm_colour,width=4,state='hidden',tags=sig_id_tag) 

201 # Draw the subsidary arms for the LH routes 

202 line_coords = common.rotate_line(x,y,+38,lh1offset+2,+38,lh1offset-6,orientation) 

203 lh1subon = canvas.create_line(line_coords,fill=arm_colour,width=3,tags=sig_id_tag) 

204 line_coords = common.rotate_line(x,y,+38,lh1offset+2,+43,lh1offset-6,orientation) 

205 lh1suboff = canvas.create_line(line_coords,fill=arm_colour,width=3,state='hidden',tags=sig_id_tag) 

206 line_coords = common.rotate_line(x,y,+38,lh2offset+2,+38,lh2offset-6,orientation) 

207 lh2subon = canvas.create_line(line_coords,fill=arm_colour,width=3,tags=sig_id_tag) 

208 line_coords = common.rotate_line(x,y,+38,lh2offset+2,+43,lh2offset-6,orientation) 

209 lh2suboff = canvas.create_line(line_coords,fill=arm_colour,width=3,state='hidden',tags=sig_id_tag) 

210 # Hide any otherdrawing objects we don't need for this particular signal 

211 if not main_signal: canvas.itemconfigure(mainsigon,state='hidden') 

212 if not main_subsidary: canvas.itemconfigure(mainsubon,state='hidden') 

213 if not lh1_subsidary: canvas.itemconfigure(lh1subon,state='hidden') 

214 if not rh1_subsidary: canvas.itemconfigure(rh1subon,state='hidden') 

215 if not lh2_subsidary: canvas.itemconfigure(lh2subon,state='hidden') 

216 if not rh2_subsidary: canvas.itemconfigure(rh2subon,state='hidden') 

217 if not lh1_signal: canvas.itemconfigure(lh1sigon,state='hidden') 

218 if not rh1_signal: canvas.itemconfigure(rh1sigon,state='hidden') 

219 if not lh2_signal: canvas.itemconfigure(lh2sigon,state='hidden') 

220 if not rh2_signal: canvas.itemconfigure(rh2sigon,state='hidden') 

221 

222 # Set the initial state of the signal Arms. We use True/False to represent the current 

223 # state of the signal arm, or a value of 'None' if the arm doesn't exist. We set the 

224 # arms that do exist in the "wrong" state initially, so that when they are first updated 

225 # they get "changed" to the correct initial state (causing the appropriate DCC commands 

226 # to be sent out to the external signal). So, bearing in mind that the parameters passed 

227 # into this function were either True or False (with False representing no signal arm) 

228 # Signal arm specified - corresponding arm will bee set to True (OFF) initially 

229 # No signal arm specified - corresponding arm will be set to None 

230 if not main_signal: main_signal = None 

231 if not main_subsidary: main_subsidary = None 

232 if not lh1_subsidary: lh1_subsidary = None 

233 if not rh1_subsidary: rh1_subsidary = None 

234 if not lh2_subsidary: lh2_subsidary = None 

235 if not rh2_subsidary: rh2_subsidary = None 

236 if not lh1_signal: lh1_signal = None 

237 if not rh1_signal: rh1_signal = None 

238 if not lh2_signal: lh2_signal = None 

239 if not rh2_signal: rh2_signal = None 

240 

241 # If this is a distant signal associated with another home signal then we need to adjust the 

242 # position of the signal button to "deconflict" with the buttons of the home signal 

243 if associated_home > 0: 

244 if signals_common.signals[str(associated_home)]["hassubsidary"]: 

245 button_offset = -55 

246 else: 

247 button_offset = -37 

248 else: 

249 button_offset = 0 

250 

251 # Create all of the signal elements common to all signal types 

252 signals_common.create_common_signal_elements (canvas, sig_id, x, y, 

253 ext_callback = sig_callback, 

254 signal_type = signals_common.sig_type.semaphore, 

255 orientation = orientation, 

256 subsidary = has_subsidary, 

257 sig_passed_button = sig_passed_button, 

258 automatic = fully_automatic, 

259 distant_button_offset = button_offset, 

260 tag = sig_id_tag) 

261 

262 # Create the signal elements for a Theatre Route indicator 

263 signals_common.create_theatre_route_elements (canvas, sig_id, x, y, xoff=25, yoff = postoffset, 

264 orientation = orientation,has_theatre = theatre_route_indicator) 

265 

266 # Create the signal elements to support Approach Control 

267 signals_common.create_approach_control_elements (canvas, sig_id, x, y, orientation = orientation, 

268 approach_button = approach_release_button) 

269 

270 # Compile a dictionary of everything we need to track for the signal 

271 # Note that all MANDATORY attributes are signals_common to ALL signal types 

272 # All SHARED attributes are signals_common to more than one signal Types 

273 signals_common.signals[str(sig_id)]["refresh"] = refresh_immediately # Type-specific - if signal should be refreshed on a change 

274 signals_common.signals[str(sig_id)]["subtype"] = signal_subtype # Type-specific - subtype of the signal (home/distant) 

275 signals_common.signals[str(sig_id)]["associatedsignal"] = associated_home # Type-specific - subtype of the signal (home/distant) 

276 signals_common.signals[str(sig_id)]["main_subsidary"] = main_subsidary # Type-specific - details of the signal configuration 

277 signals_common.signals[str(sig_id)]["lh1_subsidary"] = lh1_subsidary # Type-specific - details of the signal configuration 

278 signals_common.signals[str(sig_id)]["rh1_subsidary"] = rh1_subsidary # Type-specific - details of the signal configuration 

279 signals_common.signals[str(sig_id)]["lh2_subsidary"] = lh2_subsidary # Type-specific - details of the signal configuration 

280 signals_common.signals[str(sig_id)]["rh2_subsidary"] = rh2_subsidary # Type-specific - details of the signal configuration 

281 signals_common.signals[str(sig_id)]["main_signal"] = main_signal # Type-specific - details of the signal configuration 

282 signals_common.signals[str(sig_id)]["lh1_signal"] = lh1_signal # Type-specific - details of the signal configuration 

283 signals_common.signals[str(sig_id)]["rh1_signal"] = rh1_signal # Type-specific - details of the signal configuration 

284 signals_common.signals[str(sig_id)]["lh2_signal"] = lh2_signal # Type-specific - details of the signal configuration 

285 signals_common.signals[str(sig_id)]["rh2_signal"] = rh2_signal # Type-specific - details of the signal configuration 

286 signals_common.signals[str(sig_id)]["postoffset"] = postoffset # Type-specific - used for drawing associated distants 

287 signals_common.signals[str(sig_id)]["mainsigon"] = mainsigon # Type-specific - drawing object 

288 signals_common.signals[str(sig_id)]["mainsigoff"] = mainsigoff # Type-specific - drawing object 

289 signals_common.signals[str(sig_id)]["lh1sigon"] = lh1sigon # Type-specific - drawing object 

290 signals_common.signals[str(sig_id)]["lh1sigoff"] = lh1sigoff # Type-specific - drawing object 

291 signals_common.signals[str(sig_id)]["lh2sigon"] = lh2sigon # Type-specific - drawing object 

292 signals_common.signals[str(sig_id)]["lh2sigoff"] = lh2sigoff # Type-specific - drawing object 

293 signals_common.signals[str(sig_id)]["rh1sigon"] = rh1sigon # Type-specific - drawing object 

294 signals_common.signals[str(sig_id)]["rh1sigoff"] = rh1sigoff # Type-specific - drawing object 

295 signals_common.signals[str(sig_id)]["rh2sigon"] = rh2sigon # Type-specific - drawing object 

296 signals_common.signals[str(sig_id)]["rh2sigoff"] = rh2sigoff # Type-specific - drawing object 

297 signals_common.signals[str(sig_id)]["mainsubon"] = mainsubon # Type-specific - drawing object 

298 signals_common.signals[str(sig_id)]["mainsuboff"] = mainsuboff # Type-specific - drawing object 

299 signals_common.signals[str(sig_id)]["lh1subon"] = lh1subon # Type-specific - drawing object 

300 signals_common.signals[str(sig_id)]["lh1suboff"] = lh1suboff # Type-specific - drawing object 

301 signals_common.signals[str(sig_id)]["lh2subon"] = lh2subon # Type-specific - drawing object 

302 signals_common.signals[str(sig_id)]["lh2suboff"] = lh2suboff # Type-specific - drawing object 

303 signals_common.signals[str(sig_id)]["rh1subon"] = rh1subon # Type-specific - drawing object 

304 signals_common.signals[str(sig_id)]["rh1suboff"] = rh1suboff # Type-specific - drawing object 

305 signals_common.signals[str(sig_id)]["rh2subon"] = rh2subon # Type-specific - drawing object 

306 signals_common.signals[str(sig_id)]["rh2suboff"] = rh2suboff # Type-specific - drawing object 

307 

308 # Create the timed sequence class instances for the signal (one per route) 

309 signals_common.signals[str(sig_id)]["timedsequence"] = [] 

310 for route in signals_common.route_type: 

311 signals_common.signals[str(sig_id)]["timedsequence"].append(timed_sequence(sig_id,route)) 

312 

313 # if there is an associated signal then we also need to update that signal to refer back to this one 

314 if associated_home > 0: signals_common.signals[str(associated_home)]["associatedsignal"] = sig_id 

315 

316 # Get the initial state for the signal (if layout state has been successfully loaded) 

317 # Note that each element of 'loaded_state' will be 'None' if no data was loaded 

318 loaded_state = file_interface.get_initial_item_state("signals",sig_id) 

319 # Note that for Enum types we load the value - need to turn this back into the Enum 

320 if loaded_state["routeset"] is not None: 

321 loaded_state["routeset"] = signals_common.route_type(loaded_state["routeset"]) 

322 # Set the initial state from the "loaded" state 

323 if loaded_state["releaseonred"]: signals_common.set_approach_control(sig_id,release_on_yellow=False) 

324 if loaded_state["releaseonyel"]: signals_common.set_approach_control(sig_id,release_on_yellow=True) 

325 if loaded_state["theatretext"]: signals_common.update_theatre_route_indication(sig_id,loaded_state["theatretext"]) 

326 if loaded_state["routeset"]: signals_common.signals[str(sig_id)]["routeset"]=loaded_state["routeset"] 

327 if loaded_state["override"]: signals_common.set_signal_override(sig_id) 

328 # If no state was loaded we still need to toggle fully automatic signals to OFF 

329 # Note that we also need to Set the signal Arms to the "wrong" initial state so that when they 

330 # are first updated they get "changed" to the correct aspects and the correct DCC commands sent out 

331 # We test to see if there is a arm for each route (as the signal may not have one for all the routes) 

332 if loaded_state["sigclear"] or fully_automatic: 

333 if (signals_common.signals[str(sig_id)]["routeset"]==signals_common.route_type.MAIN 

334 and signals_common.signals[str(sig_id)]["main_signal"]==True ): 

335 signals_common.signals[str(sig_id)]["main_signal"] = False 

336 elif (signals_common.signals[str(sig_id)]["routeset"]==signals_common.route_type.LH1 

337 and signals_common.signals[str(sig_id)]["lh1_signal"]==True ): 

338 signals_common.signals[str(sig_id)]["lh1_signal"] = False 

339 elif (signals_common.signals[str(sig_id)]["routeset"]==signals_common.route_type.LH2 339 ↛ 341line 339 didn't jump to line 341, because the condition on line 339 was never true

340 and signals_common.signals[str(sig_id)]["lh2_signal"]==True ): 

341 signals_common.signals[str(sig_id)]["lh2_signal"] = False 

342 elif (signals_common.signals[str(sig_id)]["routeset"]==signals_common.route_type.RH1 342 ↛ 344line 342 didn't jump to line 344, because the condition on line 342 was never true

343 and signals_common.signals[str(sig_id)]["rh1_signal"]==True ): 

344 signals_common.signals[str(sig_id)]["rh1_signal"] = False 

345 elif (signals_common.signals[str(sig_id)]["routeset"]==signals_common.route_type.RH2 345 ↛ 347line 345 didn't jump to line 347, because the condition on line 345 was never true

346 and signals_common.signals[str(sig_id)]["rh2_signal"]==True ): 

347 signals_common.signals[str(sig_id)]["rh2_signal"] = False 

348 signals_common.toggle_signal(sig_id) 

349 # Update the signal to show the initial aspect (and send out DCC commands) 

350 # We only refresh the signal if it is set to refresh immediately 

351 if signals_common.signals[str(sig_id)]["refresh"]: update_semaphore_signal(sig_id) 

352 # finally Lock the signal if required 

353 if loaded_state["siglocked"]: signals_common.lock_signal(sig_id) 

354 

355 # Set the initial state of the subsidary from the "loaded" state 

356 # Note that we also need to Set the signal Arms to the "wrong" initial state so that when they 

357 # are first updated they get "changed" to the correct aspects and the correct DCC commands sent out 

358 # We test to see if there is a subsidary arm (as the signal may not have one for all the routes) 

359 if has_subsidary: 

360 if loaded_state["subclear"]: 360 ↛ 361line 360 didn't jump to line 361, because the condition on line 360 was never true

361 signals_common.toggle_subsidary(sig_id) 

362 if (signals_common.signals[str(sig_id)]["routeset"]==signals_common.route_type.MAIN 

363 and signals_common.signals[str(sig_id)]["main_subsidary"]==True ): 

364 signals_common.signals[str(sig_id)]["main_subsidary"] = False 

365 elif (signals_common.signals[str(sig_id)]["routeset"]==signals_common.route_type.LH1 

366 and signals_common.signals[str(sig_id)]["lh1_subsidary"]==True ): 

367 signals_common.signals[str(sig_id)]["lh1_subsidary"] = False 

368 elif (signals_common.signals[str(sig_id)]["routeset"]==signals_common.route_type.LH2 

369 and signals_common.signals[str(sig_id)]["lh2_subsidary"]==True ): 

370 signals_common.signals[str(sig_id)]["lh2_subsidary"] = False 

371 elif (signals_common.signals[str(sig_id)]["routeset"]==signals_common.route_type.RH1 

372 and signals_common.signals[str(sig_id)]["rh1_subsidary"]==True ): 

373 signals_common.signals[str(sig_id)]["rh1_subsidary"] = False 

374 elif (signals_common.signals[str(sig_id)]["routeset"]==signals_common.route_type.RH2 

375 and signals_common.signals[str(sig_id)]["rh2_subsidary"]==True ): 

376 signals_common.signals[str(sig_id)]["rh2_subsidary"] = False 

377 # Update the signal to show the initial aspect (and send out DCC commands) 

378 update_semaphore_subsidary_arms(sig_id) 

379 # finally Lock the subsidary if required  

380 if loaded_state["sublocked"]: signals_common.lock_subsidary(sig_id) 

381 

382 # Publish the initial state to the broker (for other nodes to consume). Note that changes will 

383 # only be published if the MQTT interface has been configured for publishing updates for this  

384 # signal. This allows publish/subscribe to be configured prior to signal creation 

385 signals_common.publish_signal_state(sig_id) 

386 

387 return () 

388 

389#------------------------------------------------------------------- 

390# Internal Function to update the drawing objects for a specified signal Arm 

391# to represent the state of the signal arm (either ON or OFF). If the signal 

392# was not created with the specificed signal arm then the state of the signal 

393# arm will be "None" and the function will have no effect. All changes in the 

394# signal arm state are logged (together with the additional log message passed 

395# into the function as to WHY the signal Arm is being changed to its new state 

396#------------------------------------------------------------------ 

397 

398def update_signal_arm (sig_id, signal_arm, off_element, on_element, set_to_clear, log_message = ""): 

399 # We explicitly test for True or False as "None" signifies the signal arm does not exist 

400 if set_to_clear and signals_common.signals[str(sig_id)][signal_arm]==False: 

401 logging.info ("Signal "+str(sig_id)+": Changing \'"+signal_arm+"\' arm to OFF"+log_message) 

402 signals_common.signals[str(sig_id)]["canvas"].itemconfigure(signals_common.signals[str(sig_id)][off_element],state='normal') 

403 signals_common.signals[str(sig_id)]["canvas"].itemconfigure(signals_common.signals[str(sig_id)][on_element],state='hidden') 

404 dcc_control.update_dcc_signal_element(sig_id,True,element=signal_arm) 

405 signals_common.signals[str(sig_id)][signal_arm]=True 

406 elif not set_to_clear and signals_common.signals[str(sig_id)][signal_arm]==True: 

407 logging.info ("Signal "+str(sig_id)+": Changing \'"+ signal_arm +"\' arm to ON"+log_message) 

408 signals_common.signals[str(sig_id)]["canvas"].itemconfigure(signals_common.signals[str(sig_id)][off_element],state='hidden') 

409 signals_common.signals[str(sig_id)]["canvas"].itemconfigure(signals_common.signals[str(sig_id)][on_element],state='normal') 

410 dcc_control.update_dcc_signal_element(sig_id,False,element=signal_arm) 

411 signals_common.signals[str(sig_id)][signal_arm]=False 

412 return() 

413 

414#------------------------------------------------------------------- 

415# Helper function to determine if the signal has any diverging route arms 

416#------------------------------------------------------------------- 

417 

418def has_diverging_route_arms(sig_id:int): 

419 has_route_arms = (signals_common.signals[str(sig_id)]["lh1_subsidary"] is not None or 

420 signals_common.signals[str(sig_id)]["lh2_subsidary"] is not None or 

421 signals_common.signals[str(sig_id)]["rh1_subsidary"] is not None or 

422 signals_common.signals[str(sig_id)]["rh1_subsidary"] is not None or 

423 signals_common.signals[str(sig_id)]["lh1_signal"] is not None or 

424 signals_common.signals[str(sig_id)]["lh2_signal"] is not None or 

425 signals_common.signals[str(sig_id)]["rh1_signal"] is not None or 

426 signals_common.signals[str(sig_id)]["rh2_signal"] is not None ) 

427 return (has_route_arms) 

428 

429#------------------------------------------------------------------- 

430# Internal Function to update each of the subsidary signal arms supported by 

431# a signal to reflect the current state of the subsidary (either ON or OFF) 

432# and the route set for the signal (i.e the actual subsidary arm that is changed 

433# will depend on the route that the particular subsidary arm is controlling 

434# Calls the Update_Signal_Arm function to update the state of each arm 

435#------------------------------------------------------------------ 

436 

437def update_semaphore_subsidary_arms (sig_id:int, log_message:str=""): 

438 # We explicitly test for True and False as a state of 'None' signifies the signal was created without a subsidary 

439 if signals_common.signals[str(sig_id)]["subclear"] == True: 

440 # If the route has been set to signals_common.route_type.NONE then we assume MAIN and change the MAIN arm 

441 # We also change the MAIN subsidary arm for Home signals without any diverging route arms (main signal or  

442 # subsidary signal) to cover the case of a single subsidary signal arm controlling multiple routes 

443 if ( signals_common.signals[str(sig_id)]["routeset"] == signals_common.route_type.MAIN or 

444 signals_common.signals[str(sig_id)]["routeset"] == signals_common.route_type.NONE or 

445 not has_diverging_route_arms(sig_id)): 

446 update_signal_arm (sig_id, "main_subsidary", "mainsuboff", "mainsubon", True, log_message) 

447 update_signal_arm (sig_id, "lh1_subsidary", "lh1suboff", "lh1subon", False, log_message) 

448 update_signal_arm (sig_id, "lh2_subsidary", "lh2suboff", "lh2subon", False, log_message) 

449 update_signal_arm (sig_id, "rh1_subsidary", "rh1suboff", "rh1subon", False, log_message) 

450 update_signal_arm (sig_id, "rh2_subsidary", "rh2suboff", "rh2subon", False, log_message) 

451 elif signals_common.signals[str(sig_id)]["routeset"] == signals_common.route_type.LH1: 

452 if signals_common.signals[str(sig_id)]["lh1_subsidary"] is None: 452 ↛ 453line 452 didn't jump to line 453, because the condition on line 452 was never true

453 logging.info ("Signal "+str(sig_id)+": No subsidary arm exists for route LH1") 

454 update_signal_arm (sig_id, "main_subsidary", "mainsuboff", "mainsubon", False, log_message) 

455 update_signal_arm (sig_id, "lh1_subsidary", "lh1suboff", "lh1subon", True, log_message) 

456 update_signal_arm (sig_id, "lh2_subsidary", "lh2suboff", "lh2subon", False, log_message) 

457 update_signal_arm (sig_id, "rh1_subsidary", "rh1suboff", "rh1subon", False, log_message) 

458 update_signal_arm (sig_id, "rh2_subsidary", "rh2suboff", "rh2subon", False, log_message) 

459 elif signals_common.signals[str(sig_id)]["routeset"] == signals_common.route_type.LH2: 

460 if signals_common.signals[str(sig_id)]["lh2_subsidary"] is None: 460 ↛ 461line 460 didn't jump to line 461, because the condition on line 460 was never true

461 logging.info ("Signal "+str(sig_id)+": No subsidary arm exists for route LH2") 

462 update_signal_arm (sig_id, "main_subsidary", "mainsuboff", "mainsubon", False, log_message) 

463 update_signal_arm (sig_id, "lh1_subsidary", "lh1suboff", "lh1subon", False, log_message) 

464 update_signal_arm (sig_id, "lh2_subsidary", "lh2suboff", "lh2subon", True, log_message) 

465 update_signal_arm (sig_id, "rh1_subsidary", "rh1suboff", "rh1subon", False, log_message) 

466 update_signal_arm (sig_id, "rh2_subsidary", "rh2suboff", "rh2subon", False, log_message) 

467 elif signals_common.signals[str(sig_id)]["routeset"] == signals_common.route_type.RH1: 

468 if signals_common.signals[str(sig_id)]["rh1_subsidary"] is None: 468 ↛ 469line 468 didn't jump to line 469, because the condition on line 468 was never true

469 logging.info ("Signal "+str(sig_id)+": No subsidary arm exists for route RH1") 

470 update_signal_arm (sig_id, "main_subsidary", "mainsuboff", "mainsubon", False, log_message) 

471 update_signal_arm (sig_id, "lh1_subsidary", "lh1suboff", "lh1subon", False, log_message) 

472 update_signal_arm (sig_id, "lh2_subsidary", "lh2suboff", "lh2subon", False, log_message) 

473 update_signal_arm (sig_id, "rh1_subsidary", "rh1suboff", "rh1subon", True, log_message) 

474 update_signal_arm (sig_id, "rh2_subsidary", "rh2suboff", "rh2subon", False, log_message) 

475 elif signals_common.signals[str(sig_id)]["routeset"] == signals_common.route_type.RH2: 475 ↛ 490line 475 didn't jump to line 490, because the condition on line 475 was never false

476 if signals_common.signals[str(sig_id)]["rh2_subsidary"] is None: 476 ↛ 477line 476 didn't jump to line 477, because the condition on line 476 was never true

477 logging.info ("Signal "+str(sig_id)+": No subsidary arm exists for route RH2") 

478 update_signal_arm (sig_id, "main_subsidary", "mainsuboff", "mainsubon", False, log_message) 

479 update_signal_arm (sig_id, "lh1_subsidary", "lh1suboff", "lh1subon", False, log_message) 

480 update_signal_arm (sig_id, "lh2_subsidary", "lh2suboff", "lh2subon", False, log_message) 

481 update_signal_arm (sig_id, "rh1_subsidary", "rh1suboff", "rh1subon", False, log_message) 

482 update_signal_arm (sig_id, "rh2_subsidary", "rh2suboff", "rh2subon", True, log_message) 

483 elif signals_common.signals[str(sig_id)]["subclear"] == False: 483 ↛ 490line 483 didn't jump to line 490, because the condition on line 483 was never false

484 # The subsidary signal is at danger 

485 update_signal_arm (sig_id, "main_subsidary", "mainsuboff", "mainsubon", False, log_message) 

486 update_signal_arm (sig_id, "lh1_subsidary", "lh1suboff", "lh1subon", False, log_message) 

487 update_signal_arm (sig_id, "lh2_subsidary", "lh2suboff", "lh2subon", False, log_message) 

488 update_signal_arm (sig_id, "rh1_subsidary", "rh1suboff", "rh1subon", False, log_message) 

489 update_signal_arm (sig_id, "rh2_subsidary", "rh2suboff", "rh2subon", False, log_message) 

490 return () 

491 

492# ------------------------------------------------------------------------- 

493# Internal Function to update each of the Main signal arms supported by 

494# a signal to reflect the current state of the main signal (either ON or OFF) 

495# and the route set for the signal (i.e the actual signal arm that is changed 

496# will depend on the route that the particular signal arm is controlling 

497# Calls the Update_Signal_Arm function to update the state of each arm 

498# ------------------------------------------------------------------------- 

499 

500def update_main_signal_arms(sig_id:int, log_message:str=""): 

501 # When Home/Distant signal is set to PROCEED - the main signal arms will reflect the route 

502 # Also the case of a home signal associated with a distant signal (i.e on the same post). In 

503 # this case if the home signal is at DANGER and the distant signal is at CAUTION then the state 

504 # of the Home signal will be set to caution - in this case we need to set the home arms to OFF 

505 if (signals_common.signals[str(sig_id)]["sigstate"] == signals_common.signal_state_type.PROCEED or 

506 (signals_common.signals[str(sig_id)]["sigstate"] == signals_common.signal_state_type.CAUTION and 

507 signals_common.signals[str(sig_id)]["subtype"] == semaphore_sub_type.home) ): 

508 # If the route has been set to signals_common.route_type.NONE then we assume MAIN and change the MAIN arm 

509 # We also change the MAIN signal arm for (1) Home signals without any diverging route arms (main signal or  

510 # subsidary signal) to cover the case of a single subsidary signal arm controlling multiple routes, and 

511 # (2) Associated Distant signals where the associated home signal has no diverging route arms 

512 if ( signals_common.signals[str(sig_id)]["routeset"] == signals_common.route_type.MAIN or 

513 signals_common.signals[str(sig_id)]["routeset"] == signals_common.route_type.NONE or 

514 (not has_diverging_route_arms(sig_id) and not (signals_common.signals[str(sig_id)]["associatedsignal"] > 0 

515 and has_diverging_route_arms(signals_common.signals[str(sig_id)]["associatedsignal"])))): 

516 update_signal_arm (sig_id, "main_signal", "mainsigoff", "mainsigon", True, log_message) 

517 update_signal_arm (sig_id, "lh1_signal", "lh1sigoff", "lh1sigon", False, log_message) 

518 update_signal_arm (sig_id, "lh2_signal", "lh2sigoff", "lh2sigon", False, log_message) 

519 update_signal_arm (sig_id, "rh1_signal", "rh1sigoff", "rh1sigon", False, log_message) 

520 update_signal_arm (sig_id, "rh2_signal", "rh2sigoff", "rh2sigon", False, log_message) 

521 elif signals_common.signals[str(sig_id)]["routeset"] == signals_common.route_type.LH1: 

522 if signals_common.signals[str(sig_id)]["lh1_signal"] is None: 522 ↛ 523line 522 didn't jump to line 523, because the condition on line 522 was never true

523 logging.info ("Signal "+str(sig_id)+": No main signal arm exists for route LH1") 

524 update_signal_arm (sig_id, "main_signal", "mainsigoff", "mainsigon", False, log_message) 

525 update_signal_arm (sig_id, "lh1_signal", "lh1sigoff", "lh1sigon", True, log_message) 

526 update_signal_arm (sig_id, "lh2_signal", "lh2sigoff", "lh2sigon", False, log_message) 

527 update_signal_arm (sig_id, "rh1_signal", "rh1sigoff", "rh1sigon", False, log_message) 

528 update_signal_arm (sig_id, "rh2_signal", "rh2sigoff", "rh2sigon", False, log_message) 

529 elif signals_common.signals[str(sig_id)]["routeset"] == signals_common.route_type.LH2: 

530 if signals_common.signals[str(sig_id)]["lh2_signal"] is None: 530 ↛ 531line 530 didn't jump to line 531, because the condition on line 530 was never true

531 logging.info ("Signal "+str(sig_id)+": No main signal arm exists for route LH2") 

532 update_signal_arm (sig_id, "main_signal", "mainsigoff", "mainsigon", False, log_message) 

533 update_signal_arm (sig_id, "lh1_signal", "lh1sigoff", "lh1sigon", False, log_message) 

534 update_signal_arm (sig_id, "lh2_signal", "lh2sigoff", "lh2sigon", True, log_message) 

535 update_signal_arm (sig_id, "rh1_signal", "rh1sigoff", "rh1sigon", False, log_message) 

536 update_signal_arm (sig_id, "rh2_signal", "rh2sigoff", "rh2sigon", False, log_message) 

537 elif signals_common.signals[str(sig_id)]["routeset"] == signals_common.route_type.RH1: 

538 if signals_common.signals[str(sig_id)]["rh1_signal"] is None: 538 ↛ 539line 538 didn't jump to line 539, because the condition on line 538 was never true

539 logging.info ("Signal "+str(sig_id)+": No main signal arm exists for route RH1") 

540 update_signal_arm (sig_id, "main_signal", "mainsigoff", "mainsigon", False, log_message) 

541 update_signal_arm (sig_id, "lh1_signal", "lh1sigoff", "lh1sigon", False, log_message) 

542 update_signal_arm (sig_id, "lh2_signal", "lh2sigoff", "lh2sigon", False, log_message) 

543 update_signal_arm (sig_id, "rh1_signal", "rh1sigoff", "rh1sigon", True, log_message) 

544 update_signal_arm (sig_id, "rh2_signal", "rh2sigoff", "rh2sigon", False, log_message) 

545 elif signals_common.signals[str(sig_id)]["routeset"] == signals_common.route_type.RH2: 545 ↛ 561line 545 didn't jump to line 561, because the condition on line 545 was never false

546 if signals_common.signals[str(sig_id)]["rh2_signal"] is None: 546 ↛ 547line 546 didn't jump to line 547, because the condition on line 546 was never true

547 logging.info ("Signal "+str(sig_id)+": No main signal arm exists for route RH2") 

548 update_signal_arm (sig_id, "main_signal", "mainsigoff", "mainsigon", False, log_message) 

549 update_signal_arm (sig_id, "lh1_signal", "lh1sigoff", "lh1sigon", False, log_message) 

550 update_signal_arm (sig_id, "lh2_signal", "lh2sigoff", "lh2sigon", False, log_message) 

551 update_signal_arm (sig_id, "rh1_signal", "rh1sigoff", "rh1sigon", False, log_message) 

552 update_signal_arm (sig_id, "rh2_signal", "rh2sigoff", "rh2sigon", True, log_message) 

553 else: 

554 # Its either a Home signal at DANGER or a Distant Signal at CAUTION 

555 # In either case - all the main signal arms should be set to ON 

556 update_signal_arm (sig_id, "main_signal", "mainsigoff", "mainsigon", False, log_message) 

557 update_signal_arm (sig_id, "lh1_signal", "lh1sigoff", "lh1sigon", False, log_message) 

558 update_signal_arm (sig_id, "lh2_signal", "lh2sigoff", "lh2sigon", False, log_message) 

559 update_signal_arm (sig_id, "rh1_signal", "rh1sigoff", "rh1sigon", False, log_message) 

560 update_signal_arm (sig_id, "rh2_signal", "rh2sigoff", "rh2sigon", False, log_message) 

561 return() 

562 

563# ------------------------------------------------------------------------- 

564# Function to Refresh the displayed signal aspect according the signal state 

565# Also takes into account the state of the signal ahead if one is specified 

566# to ensure the correct aspect is displayed for 3/4 aspect types and 2 aspect  

567# distant signals - e.g. for a 3/4 aspect signal - if the signal ahead is ON 

568# and this signal is OFF then we want to change it to YELLOW rather than GREEN 

569# This function assumes the Sig_ID has been validated by the calling programme 

570# ------------------------------------------------------------------------- 

571 

572def update_semaphore_signal (sig_id:int, sig_ahead_id:Union[int,str]=None, updating_associated_signal:bool=False): 

573 

574 route = signals_common.signals[str(sig_id)]["routeset"] 

575 

576 # Get the ID of the associated signal (to make the following code more readable) 

577 associated_signal = signals_common.signals[str(sig_id)]["associatedsignal"] 

578 # Establish what the signal should be displaying based on the state 

579 if signals_common.signals[str(sig_id)]["subtype"] == semaphore_sub_type.distant: 

580 if not signals_common.signals[str(sig_id)]["sigclear"]: 

581 new_aspect = signals_common.signal_state_type.CAUTION 

582 log_message = " (CAUTION) - signal is ON" 

583 elif signals_common.signals[str(sig_id)]["override"]: 

584 new_aspect = signals_common.signal_state_type.CAUTION 

585 log_message = " (CAUTION) - signal is OVERRIDDEN" 

586 elif signals_common.signals[str(sig_id)]["overcaution"]: 

587 new_aspect = signals_common.signal_state_type.CAUTION 

588 log_message = " (CAUTION) - signal is OVERRIDDEN to CAUTION" 

589 elif signals_common.signals[str(sig_id)]["timedsequence"][route.value].sequence_in_progress: 589 ↛ 590line 589 didn't jump to line 590, because the condition on line 589 was never true

590 new_aspect = signals_common.signal_state_type.CAUTION 

591 log_message = " (CAUTION) - signal is on a timed sequence" 

592 elif associated_signal > 0 and signals_common.signals[str(associated_signal)]["sigstate"] == signals_common.signal_state_type.DANGER: 

593 new_aspect = signals_common.signal_state_type.CAUTION 

594 log_message = (" (CAUTION) - signal is OFF but slotted with home signal "+str(associated_signal)+" at DANGER") 

595 elif sig_ahead_id is not None and signals_common.signals[str(sig_ahead_id)]["sigstate"] == signals_common.signal_state_type.DANGER: 595 ↛ 596line 595 didn't jump to line 596, because the condition on line 595 was never true

596 new_aspect = signals_common.signal_state_type.CAUTION 

597 log_message = (" (CAUTION) - distant signal is OFF but signal ahead "+str(sig_ahead_id)+" is at DANGER") 

598 else: 

599 new_aspect = signals_common.signal_state_type.PROCEED 

600 log_message = (" (PROCEED) - signal is OFF - route is set to " + 

601 str(signals_common.signals[str(sig_id)]["routeset"]).rpartition('.')[-1] +")") 

602 else: 

603 if not signals_common.signals[str(sig_id)]["sigclear"]: 

604 new_aspect = signals_common.signal_state_type.DANGER 

605 log_message = " (DANGER) - signal is ON" 

606 elif signals_common.signals[str(sig_id)]["override"]: 

607 new_aspect = signals_common.signal_state_type.DANGER 

608 log_message = " (DANGER) - signal is OVERRIDDEN" 

609 elif signals_common.signals[str(sig_id)]["timedsequence"][route.value].sequence_in_progress: 

610 new_aspect = signals_common.signal_state_type.DANGER 

611 log_message = " (DANGER) - signal is on a timed sequence" 

612 elif signals_common.signals[str(sig_id)]["releaseonred"]: 

613 new_aspect = signals_common.signal_state_type.DANGER 

614 log_message = " (DANGER) - signal is subject to \'release on red\' approach control" 

615 elif associated_signal > 0 and signals_common.signals[str(associated_signal)]["sigstate"] == signals_common.signal_state_type.CAUTION: 

616 new_aspect = signals_common.signal_state_type.CAUTION 

617 log_message = (" (CAUTION) - signal is OFF but associated distant "+str(associated_signal)+" is at CAUTION") 

618 else: 

619 new_aspect = signals_common.signal_state_type.PROCEED 

620 log_message = (" (PROCEED) - signal is OFF - route is set to " + 

621 str(signals_common.signals[str(sig_id)]["routeset"]).rpartition('.')[-1] +")") 

622 

623 current_aspect = signals_common.signals[str(sig_id)]["sigstate"] 

624 

625 # Now refresh the displayed aspect (passing in the log message to be displayed) if the aspect has changed 

626 if new_aspect != current_aspect: 

627 signals_common.signals[str(sig_id)]["sigstate"] = new_aspect 

628 update_main_signal_arms (sig_id,log_message) 

629 # If this signal is an associated with another signal then we also need to refresh the other signal 

630 # Associated distant signals need to be updated as they are "slotted" with the home signal - i.e. if the 

631 # home signal is set to DANGER then the distant signal (on the same arm) should show CAUTION 

632 # Associated Home signals need to be updated as the internal state of home signals relies on the state of 

633 # the distant signal and the home signal (i.e. Home is OFF but distant is ON - State is therefore CAUTION 

634 # We set a flag for the recursive call so we don't end up in a circular recursion 

635 if associated_signal > 0 and not updating_associated_signal: 

636 if signals_common.signals[str(associated_signal)]["refresh"]: 636 ↛ 640line 636 didn't jump to line 640, because the condition on line 636 was never false

637 update_semaphore_signal(associated_signal,updating_associated_signal=True) 

638 # Call the common function to update the theatre route indicator elements 

639 # (if the signal has a theatre route indicator - otherwise no effect) 

640 signals_common.enable_disable_theatre_route_indication(sig_id) 

641 # Publish the signal changes to the broker (for other nodes to consume). Note that state changes will only 

642 # be published if the MQTT interface has been successfully configured for publishing updates for this signal 

643 signals_common.publish_signal_state(sig_id) 

644 # For associated distant signals we need to take into account the state of both the home and distant signals 

645 # to set the correct "state" for the associated home/distant signal - and we will only know this state once 

646 # both home and distant signals have been updated (to take into account any "slotting") 

647 if ( signals_common.signals[str(sig_id)]["subtype"] == semaphore_sub_type.home and associated_signal > 0 and 

648 signals_common.signals[str(sig_id)]["sigstate"] == signals_common.signal_state_type.CAUTION and 

649 signals_common.signals[str(associated_signal)]["sigstate"] == signals_common.signal_state_type.PROCEED ): 

650 logging.info ("Signal "+str(sig_id)+": Updating signal state to PROCEED - associated (slotted) distant is now OFF") 

651 signals_common.signals[str(sig_id)]["sigstate"] = signals_common.signal_state_type.PROCEED 

652 return() 

653 

654# ------------------------------------------------------------------------- 

655# Function to set (and update) the route indication for the signal 

656# Calls the internal functions to update the route feathers and the 

657# theatre route indication. This Function assumes the Sig_ID has 

658# already been validated by the calling programme 

659# ------------------------------------------------------------------------- 

660 

661def update_semaphore_route_indication (sig_id,route_to_set = None): 

662 # Only update the respective route indication if the route has been changed and has actively 

663 # been set (a route of 'NONE' signifies that the particular route indication isn't used)  

664 if route_to_set is not None and signals_common.signals[str(sig_id)]["routeset"] != route_to_set: 

665 logging.info ("Signal "+str(sig_id)+": Setting semaphore route to "+str(route_to_set).rpartition('.')[-1]) 

666 signals_common.signals[str(sig_id)]["routeset"] = route_to_set 

667 # Refresh the signal drawing objects (which will also send the DCC commands to change the arms accordingly) 

668 # Log messages will also be generated for each change - so we don't need lo log anything extra here 

669 update_main_signal_arms(sig_id," (route has been changed to "+str(route_to_set).rpartition('.')[-1]+")") 

670 # Also update the subsidary aspects for route changes (as these may be represented by different subsidary arms) 

671 update_semaphore_subsidary_arms(sig_id," (route has been changed to "+str(route_to_set).rpartition('.')[-1]+")") 

672 # If this is a home signal with an associated distant signal then we also need to set the route for 

673 # the distant signal as it is effectively on the same post and "slotted" with the home signal 

674 # Get the ID of the associated signal (to make the following code more readable) 

675 associated_signal = signals_common.signals[str(sig_id)]["associatedsignal"] 

676 if signals_common.signals[str(sig_id)]["subtype"] == semaphore_sub_type.home and associated_signal > 0: 

677 update_semaphore_route_indication (associated_signal,route_to_set) 

678 # Refresh the signal aspect (a catch-all to ensure the signal displays the correct aspect 

679 # in case the signal is in the middle of a timed sequence for the old route or the new route 

680 if signals_common.signals[str(sig_id)]["refresh"]: update_semaphore_signal(sig_id) 

681 return() 

682 

683 

684# ------------------------------------------------------------------------- 

685# Class for a timed signal sequence. A class instance is created for each 

686# route for each signal. When a timed signal is triggered the existing 

687# instance is first aborted. A new instance is then created/started 

688# ------------------------------------------------------------------------- 

689 

690class timed_sequence(): 

691 def __init__(self, sig_id:int, route, start_delay:int=0, time_delay:int=0): 

692 self.sig_id = sig_id 

693 self.sig_route = route 

694 self.start_delay = start_delay 

695 self.time_delay = time_delay 

696 self.sequence_abort_flag = False 

697 self.sequence_in_progress = False 

698 

699 def abort(self): 

700 self.sequence_abort_flag = True 

701 

702 def start(self): 

703 if self.sequence_abort_flag or not signals_common.sig_exists(self.sig_id): 703 ↛ 704line 703 didn't jump to line 704, because the condition on line 703 was never true

704 self.sequence_in_progress = False 

705 else: 

706 self.sequence_in_progress = True 

707 # For a start delay of zero we assume the intention is not to make a callback (on the basis 

708 # that the user has triggered the timed signal in the first place). For start delays > 0 the  

709 # sequence is initiated after the specified delay and this will trigger a callback 

710 # Note that we only change the aspect and generate the callback if the same route is set 

711 if signals_common.signals[str(self.sig_id)]["routeset"] == self.sig_route: 711 ↛ 722line 711 didn't jump to line 722, because the condition on line 711 was never false

712 if self.start_delay > 0: 712 ↛ 713line 712 didn't jump to line 713, because the condition on line 712 was never true

713 logging.info("Signal "+str(self.sig_id)+": Timed Signal - Signal Passed Event **************************") 

714 update_semaphore_signal(self.sig_id) 

715 # Publish the signal passed event via the mqtt interface. Note that the event will only be published if the 

716 # mqtt interface has been successfully configured and the signal has been set to publish passed events 

717 signals_common.publish_signal_passed_event(self.sig_id) 

718 signals_common.signals[str(self.sig_id)]["extcallback"] (self.sig_id,signals_common.sig_callback_type.sig_passed) 

719 else: 

720 update_semaphore_signal(self.sig_id) 

721 # We need to schedule the sequence completion (i.e. back to clear 

722 common.root_window.after(self.time_delay*1000,lambda:self.timed_signal_sequence_end()) 

723 

724 def timed_signal_sequence_end(self): 

725 # We've finished - Set the signal back to its "normal" condition 

726 self.sequence_in_progress = False 

727 if signals_common.sig_exists(self.sig_id): 727 ↛ exitline 727 didn't return from function 'timed_signal_sequence_end', because the condition on line 727 was never false

728 logging.info("Signal "+str(self.sig_id)+": Timed Signal - Signal Updated Event *************************") 

729 update_semaphore_signal(self.sig_id) 

730 signals_common.signals[str(self.sig_id)]["extcallback"] (self.sig_id, signals_common.sig_callback_type.sig_updated) 

731 

732# ------------------------------------------------------------------------- 

733# Function to initiate a timed signal sequence - setting the signal initially to ON 

734# and then returning to OFF (assuming the signal is clear and nor overridden) after 

735# the specified time delay. Intended for automation of 'exit' signals on a layout. 

736# The start_delay is the initial delay (in seconds) before the signal is set to ON 

737# and the time_delay is the delay before the signal returns to OFF. A 'sig_passed' 

738# callback event will be generated when the signal is set to ON if if a start delay 

739# is specified. When returning to OFF, a 'sig_updated' callback event will be generated. 

740# ------------------------------------------------------------------------- 

741 

742def trigger_timed_semaphore_signal (sig_id:int,start_delay:int=0,time_delay:int=5): 

743 

744 def delayed_sequence_start(sig_id:int, sig_route): 

745 if signals_common.sig_exists(sig_id): 

746 signals_common.signals[str(sig_id)]["timedsequence"][route.value].start() 

747 

748 # Don't initiate a timed signal sequence if a shutdown has already been initiated 

749 if common.shutdown_initiated: 749 ↛ 750line 749 didn't jump to line 750, because the condition on line 749 was never true

750 logging.warning("Signal "+str(sig_id)+": Timed Signal - Shutdown initiated - not triggering timed signal") 

751 else: 

752 # Abort any timed signal sequences already in progess 

753 route = signals_common.signals[str(sig_id)]["routeset"] 

754 signals_common.signals[str(sig_id)]["timedsequence"][route.value].abort() 

755 # Create a new instnce of the time signal class - this should have the effect of "destroying" 

756 # the old instance when it goes out of scope, leaving us with the newly created instance 

757 signals_common.signals[str(sig_id)]["timedsequence"][route.value] = timed_sequence(sig_id, route, start_delay, time_delay) 

758 # Schedule the start of the sequence (i.e. signal to danger) if the start delay is greater than zero 

759 # Otherwise initiate the sequence straight away (so the signal state is updated immediately) 

760 if start_delay > 0: 760 ↛ 761line 760 didn't jump to line 761, because the condition on line 760 was never true

761 common.root_window.after(start_delay*1000,lambda:delayed_sequence_start(sig_id,route)) 

762 else: 

763 signals_common.signals[str(sig_id)]["timedsequence"][route.value].start() 

764 return() 

765 

766###############################################################################