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

240 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 point library objects on the canvas. 

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

4# 

5# External API - classes and functions (used by the Schematic Editor): 

6#  

7# point_type (use when creating points) 

8# point_type.RH 

9# point_type.LH 

10#  

11# point_callback_type (tells the calling program what has triggered the callback): 

12# point_callback_type.point_switched (point has been switched) 

13# point_callback_type.fpl_switched (facing point lock has been switched) 

14#  

15# create_point - Creates a point object and returns the "tag" for all tkinter canvas drawing objects  

16# This allows the editor to move the point object on the schematic as required  

17# Mandatory Parameters: 

18# Canvas - The Tkinter Drawing canvas on which the point is to be displayed 

19# point_id:int - The ID for the point - also displayed on the point button 

20# pointtype:point_type - either point_type.RH or point_type.LH 

21# x:int, y:int - Position of the point on the canvas (in pixels) 

22# callback - the function to call on track point or FPL switched events 

23# Note that the callback function returns (item_id, callback type) 

24# Optional Parameters: 

25# colour:str - Any tkinter colour can be specified as a string - default = "Black" 

26# orientation:int- Orientation in degrees (0 or 180) - default = 0 

27# reverse:bool - If the switching logic is to be reversed - Default = False 

28# fpl:bool - If the point is to have a Facing point lock - Default = False (no FPL) 

29# also_switch:int - the Id of another point to switch with this point - Default = None 

30# auto:bool - Point is fully automatic (i.e. no point control buttons) - Default = False. 

31# 

32# delete_point(point_id:int) - To delete the specified point from the schematic 

33# 

34# update_autoswitch(point_id:int, autoswitch_id:int) - To update the 'autoswitch' reference 

35# 

36# lock_point(point_id:int) - use for point/signal interlocking 

37#  

38# unlock_point(point_id:int) - use for point/signal interlocking 

39#  

40# toggle_point(point_id:int) - use for route setting (use 'point_switched' to find state first) 

41#  

42# toggle_fpl(point_id:int) - use for route setting (use 'fpl_active' to find state first) 

43#  

44# point_switched(point_id:int) - returns the point state (True/False) - to support interlocking 

45#  

46# fpl_active(point_id:int) - returns the FPL state (True/False) - to support interlocking 

47# - Will return True if the point does not have a Facing point Lock 

48# 

49#--------------------------------------------------------------------------------------------------- 

50 

51from . import dcc_control 

52from . import common 

53from . import file_interface 

54 

55import tkinter as Tk 

56import enum 

57import logging 

58 

59# ------------------------------------------------------------------------- 

60# Public API classes (to be used by external functions) 

61# ------------------------------------------------------------------------- 

62 

63class point_type(enum.Enum): 

64 RH = 1 # Right Hand point 

65 LH = 2 # Left Hand point 

66 

67# Define the different callbacks types for the point 

68class point_callback_type(enum.Enum): 

69 point_switched = 11 # The point has been switched by the user 

70 fpl_switched = 12 # The facing point lock has been switched by the user 

71 

72# ------------------------------------------------------------------------- 

73# Points are to be added to a global dictionary when created 

74# ------------------------------------------------------------------------- 

75 

76points: dict = {} 

77 

78# ------------------------------------------------------------------------- 

79# API Function to check if a Point exists in the dictionary of Points 

80# ------------------------------------------------------------------------- 

81 

82def point_exists(point_id:int): 

83 if not isinstance(point_id, int): 

84 logging.error("Point "+str(point_id)+": point_exists - Point ID must be an integer") 

85 point_exists = False 

86 else: 

87 point_exists = str(point_id) in points.keys() 

88 return(point_exists) 

89 

90# ------------------------------------------------------------------------- 

91# Callbacks for processing button pushes 

92# ------------------------------------------------------------------------- 

93 

94def fpl_button_event(point_id:int): 

95 logging.info("Point "+str(point_id)+": FPL Button Event ************************************************************") 

96 toggle_fpl(point_id) 

97 points[str(point_id)]["extcallback"] (point_id,point_callback_type.fpl_switched) 

98 return () 

99 

100def change_button_event(point_id:int): 

101 logging.info("Point "+str(point_id)+": Change Button Event *********************************************************") 

102 toggle_point(point_id) 

103 points[str(point_id)]["extcallback"] (point_id,point_callback_type.point_switched) 

104 return () 

105 

106# ------------------------------------------------------------------------- 

107# API Function to flip the state of the Point's Facing Point Lock (to 

108# enable route setting functions. Also called when the FPL button is pressed  

109# ------------------------------------------------------------------------- 

110 

111def toggle_fpl(point_id:int): 

112 global points 

113 if not isinstance(point_id, int): 

114 logging.error("Point "+str(point_id)+": toggle_fpl - Point ID must be an integer") 

115 elif not point_exists(point_id): 

116 logging.error("Point "+str(point_id)+": toggle_fpl - Point ID does not exist") 

117 elif not points[str(point_id)]["hasfpl"]: 

118 logging.error("Point "+str(point_id)+": toggle_fpl - Point does not have a Facing Point Lock") 

119 else: 

120 if points[str(point_id)]["locked"]: 

121 logging.warning("Point "+str(point_id)+": toggle_fpl - Point is externally locked - Toggling anyway") 

122 if not points[str(point_id)]["fpllock"]: 

123 logging.info("Point "+str(point_id)+": Activating FPL") 

124 points[str(point_id)]["changebutton"].config(state="disabled") 

125 points[str(point_id)]["lockbutton"].config(relief="sunken",bg="white") 

126 points[str(point_id)]["fpllock"] = True 

127 else: 

128 logging.info("Point "+str(point_id)+": Clearing FPL") 

129 points[str(point_id)]["changebutton"].config(state="normal") 

130 points[str(point_id)]["lockbutton"].config(relief="raised",bg="grey85") 

131 points[str(point_id)]["fpllock"] = False 

132 return() 

133 

134# ------------------------------------------------------------------------- 

135# Internal Function to toggle the point blade drawing objects and update 

136# the internal state of the point - called by the toggle_point function 

137# Can also be called on point creation to set the initial (loaded) state 

138# ------------------------------------------------------------------------- 

139 

140def toggle_point_state (point_id:int, switched_by_another_point:bool=False): 

141 global points 

142 if not points[str(point_id)]["switched"]: 

143 if switched_by_another_point: 

144 logging.info("Point "+str(point_id)+": Changing point to SWITCHED (switched with another point)") 

145 else: 

146 logging.info("Point "+str(point_id)+": Changing point to SWITCHED") 

147 points[str(point_id)]["changebutton"].config(relief="sunken",bg="white") 

148 points[str(point_id)]["switched"] = True 

149 points[str(point_id)]["canvas"].itemconfig(points[str(point_id)]["blade2"],state="normal") #switched 

150 points[str(point_id)]["canvas"].itemconfig(points[str(point_id)]["blade1"],state="hidden") #normal 

151 dcc_control.update_dcc_point(point_id, True) 

152 else: 

153 if switched_by_another_point: 

154 logging.info("Point "+str(point_id)+": Changing point to NORMAL (switched with another point)") 

155 else: 

156 logging.info("Point "+str(point_id)+": Changing point to NORMAL") 

157 points[str(point_id)]["changebutton"].config(relief="raised",bg="grey85") 

158 points[str(point_id)]["switched"] = False 

159 points[str(point_id)]["canvas"].itemconfig(points[str(point_id)]["blade2"],state="hidden") #switched  

160 points[str(point_id)]["canvas"].itemconfig(points[str(point_id)]["blade1"],state="normal") #normal 

161 dcc_control.update_dcc_point(point_id, False) 

162 return 

163 

164# ------------------------------------------------------------------------- 

165# Internal Function to update any downstream points (i.e. points 

166# 'autoswitched' by the current point) - called on point creation 

167# (if a point exists) and when a point is toggled via the API 

168# ------------------------------------------------------------------------- 

169 

170def update_downstream_points(point_id:int): 

171 if points[str(point_id)]["alsoswitch"] != 0: 

172 if not point_exists(points[str(point_id)]["alsoswitch"]): 

173 logging.error("Point "+str(point_id)+": update_downstream_points - Can't 'also switch' point " 

174 +str(points[str(point_id)]["alsoswitch"]) +" as that point does not exist") 

175 elif not points[str(points[str(point_id)]["alsoswitch"])]["automatic"]: 

176 logging.error("Point "+str(point_id)+": update_downstream_points - Can't 'also switch' point " 

177 +str(points[str(point_id)]["alsoswitch"]) +" as that point is not automatic") 

178 elif point_switched(point_id) != point_switched(points[str(point_id)]["alsoswitch"]): 

179 logging.info("Point "+str(point_id)+": Also changing point "+str(points[str(point_id)]["alsoswitch"])) 

180 # Recursively call back into the toggle_point function to change the point 

181 toggle_point(points[str(point_id)]["alsoswitch"],switched_by_another_point=True) 

182 return() 

183 

184# ------------------------------------------------------------------------- 

185# API Function to flip the route setting for the Point (to enable 

186# route setting functions. Also called whenthe POINT button is pressed  

187# Will also recursivelly call itself to change any "also_switch" points 

188# ------------------------------------------------------------------------- 

189 

190def toggle_point(point_id:int, switched_by_another_point:bool=False): 

191 global points 

192 if not isinstance(point_id, int): 

193 logging.error("Point "+str(point_id)+": toggle_point - Point ID must be an integer") 

194 elif not point_exists(point_id): 

195 logging.error("Point "+str(point_id)+": toggle_point - Point ID does not exist") 

196 elif points[str(point_id)]["automatic"] and not switched_by_another_point: 

197 logging.error("Point "+str(point_id)+": toggle_point - Point is automatic (should be 'also switched' by another point)") 

198 else: 

199 if points[str(point_id)]["locked"]: 

200 logging.warning("Point "+str(point_id)+": toggle_point - Point is externally locked - Toggling anyway") 

201 elif points[str(point_id)]["hasfpl"] and points[str(point_id)]["fpllock"]: 

202 logging.warning("Point "+str(point_id)+": toggle_point - Facing Point Lock is active - Toggling anyway") 

203 # Call the internal function to toggle the point state and update the drawing objects 

204 toggle_point_state(point_id,switched_by_another_point) 

205 # Now change any other points we need (i.e. points switched with this one) 

206 update_downstream_points(point_id) 

207 return() 

208 

209# ------------------------------------------------------------------------- 

210# Public API function to create a Point (drawing objects + state) 

211# By default the point is "NOT SWITCHED" (i.e. showing the default route) 

212# If the point has a Facing Point Lock then this is set to locked 

213# Function returns a list of the lines that have been drawn (so an 

214# external programme can change the colours if required) 

215# ------------------------------------------------------------------------- 

216 

217def create_point (canvas, point_id:int, pointtype:point_type, 

218 x:int, y:int, callback, colour:str="black", 

219 orientation:int = 0, also_switch:int = 0, 

220 reverse:bool=False, auto:bool=False, fpl:bool=False): 

221 global points 

222 # Set a unique 'tag' to reference the tkinter drawing objects 

223 canvas_tag = "point"+str(point_id) 

224 if not isinstance(point_id, int) or point_id < 1: 

225 logging.error("Point "+str(point_id)+": create_point - Point ID must be a positive integer") 

226 elif point_exists(point_id): 

227 logging.error("Point "+str(point_id)+": create_point - Point ID already exists") 

228 elif not isinstance(also_switch, int): 

229 logging.error("Point "+str(point_id)+": create_point - Alsoswitch ID must be an integer") 

230 elif also_switch == point_id: 

231 logging.error("Point "+str(point_id)+": create_point - Alsoswitch ID is the same as the Point ID") 

232 elif pointtype not in point_type: 

233 logging.error("Point "+str(point_id)+": create_point - Invalid Point Type specified") 

234 elif fpl and auto: 

235 logging.error("Point "+str(point_id)+": create_point - Automatic point should be created without a FPL") 

236 else: 

237 logging.debug("Point "+str(point_id)+": Creating library object on the schematic") 

238 # Create the button objects and their callbacks 

239 if point_id < 10: main_button_text = "0" + str(point_id) 

240 else: main_button_text = str(point_id) 

241 point_button = Tk.Button (canvas, text=main_button_text, state="normal", relief="raised", 241 ↛ exitline 241 didn't jump to the function exit

242 font=('Courier',common.fontsize,"normal"),bg= "grey85", 

243 padx=common.xpadding, pady=common.ypadding, 

244 command = lambda:change_button_event(point_id)) 

245 fpl_button = Tk.Button (canvas,text="L",state="normal", relief="sunken", 245 ↛ exitline 245 didn't jump to the function exit

246 font=('Courier',common.fontsize,"normal"), bg = "white", 

247 padx=common.xpadding, pady=common.ypadding, 

248 command = lambda:fpl_button_event(point_id)) 

249 # Disable the change button if the point has FPL (default state = FPL active) 

250 if fpl: point_button.config(state="disabled") 

251 # Create the Tkinter drawing objects for the point 

252 if pointtype==point_type.RH: 

253 # Draw the lines representing a Right Hand point 

254 line_coords = common.rotate_line (x,y,-25,0,-10,0,orientation) 

255 blade1 = canvas.create_line (line_coords,fill=colour,width=3,tags=canvas_tag) #straignt blade 

256 line_coords = common.rotate_line (x,y,-25,0,-15,+10,orientation) 

257 blade2 = canvas.create_line (line_coords,fill=colour,width=3,tags=canvas_tag) #switched blade 

258 line_coords = common.rotate_line (x,y,-10,0,+25,0,orientation) 

259 canvas.create_line (line_coords,fill=colour,width=3,tags=canvas_tag) #straight route 

260 line_coords = common.rotate_line (x,y,-15,+10,0,+25,orientation) 

261 canvas.create_line(line_coords,fill=colour,width=3,tags=canvas_tag) #switched route 

262 # Create the button windows in the correct relative positions for a Right Hand Point 

263 point_coords = common.rotate_point (x,y,-3,-13,orientation) 

264 if not auto: canvas.create_window (point_coords,anchor=Tk.W,window=point_button,tags=canvas_tag) 

265 if fpl: canvas.create_window (point_coords,anchor=Tk.E,window=fpl_button,tags=canvas_tag) 

266 else: 

267 # Draw the lines representing a Left Hand point 

268 line_coords = common.rotate_line (x,y,-25,0,-10,0,orientation) 

269 blade1 = canvas.create_line (line_coords,fill=colour,width=3,tags=canvas_tag) #straignt blade 

270 line_coords = common.rotate_line (x,y,-25,0,-15,-10,orientation) 

271 blade2 = canvas.create_line (line_coords,fill=colour,width=3,tags=canvas_tag) #switched blade 

272 line_coords = common.rotate_line (x,y,-10,0,+25,0,orientation) 

273 canvas.create_line (line_coords,fill=colour,width=3,tags=canvas_tag) #straight route 

274 line_coords = common.rotate_line (x,y,-15,-10,0,-25,orientation) 

275 canvas.create_line(line_coords,fill=colour,width=3,tags=canvas_tag) #switched route 

276 # Create the button windows in the correct relative positions for a Left Hand Point 

277 point_coords = common.rotate_point (x,y,-3,+13,orientation) 

278 if not auto: canvas.create_window (point_coords,anchor=Tk.W,window=point_button,tags=canvas_tag) 

279 if fpl: canvas.create_window (point_coords,anchor=Tk.E,window=fpl_button,tags=canvas_tag) 

280 # The "normal" state of the point is the straight through route by default 

281 # With reverse set to True, the divergent route becomes the "normal" state 

282 if reverse is True: blade1, blade2 = blade2, blade1 

283 # Hide the line for the switched route (display it later when we need it) 

284 canvas.itemconfig(blade2,state="hidden") 

285 # Compile a dictionary of everything we need to track 

286 points[str(point_id)] = {} 

287 points[str(point_id)]["canvas"] = canvas # Tkinter canvas object 

288 points[str(point_id)]["blade1"] = blade1 # Tkinter drawing object 

289 points[str(point_id)]["blade2"] = blade2 # Tkinter drawing object 

290 points[str(point_id)]["changebutton"] = point_button # Tkinter drawing object 

291 points[str(point_id)]["lockbutton"] = fpl_button # Tkinter drawing object 

292 points[str(point_id)]["extcallback"] = callback # The callback to make on an event 

293 points[str(point_id)]["alsoswitch"] = also_switch # Point to automatically switch (0=none) 

294 points[str(point_id)]["automatic"] = auto # Whether the point is automatic or not 

295 points[str(point_id)]["hasfpl"] = fpl # Whether the point has a FPL or not 

296 points[str(point_id)]["fpllock"] = fpl # Initial state of the FPL (locked if it has FPL) 

297 points[str(point_id)]["locked"] = False # Initial "interlocking" state of the point 

298 points[str(point_id)]["switched"] = False # Initial "switched" state of the point 

299 points[str(point_id)]["tags"] = canvas_tag # Canvas Tags for all drawing objects 

300 # Get the initial state for the point (if layout state has been successfully loaded) 

301 # if nothing has been loaded then the default state (as created) will be applied 

302 loaded_state = file_interface.get_initial_item_state("points",point_id) 

303 # Toggle the FPL if FPL is ACTIVE ("switched" will be 'None' if no data was loaded) 

304 # We toggle on False as points with FPLs are created with the FPL active by default 

305 if fpl and loaded_state["fpllock"] == False: toggle_fpl(point_id) 

306 # Toggle the point state if SWITCHED ("switched" will be 'None' if no data was loaded) 

307 # Note that Toggling the point will also send the DCC commands to set the initial state 

308 # If we don't toggle the point we need to send out the DCC commands for the default state 

309 if loaded_state["switched"]: toggle_point_state(point_id) 

310 else: dcc_control.update_dcc_point(point_id,False) 

311 # Externally lock the point if required 

312 if loaded_state["locked"]: lock_point(point_id) 

313 # We need to ensure that all points in an 'auto switch' chain are set to the same 

314 # switched/not-switched state so they switch together correctly. First, we test to 

315 # see if any existing points have already been configured to "autoswitch' the newly 

316 # created point and, if so, toggle the newly created point to the same state 

317 for other_point_id in points: 

318 if points[other_point_id]["alsoswitch"] == point_id: 

319 update_downstream_points(int(other_point_id)) 

320 # Update any downstream points (configured to be 'autoswitched' by this point 

321 # but only if they have been created (allows them to be created after this point) 

322 if point_exists(points[str(point_id)]["alsoswitch"]): 

323 validate_alsoswitch_point(point_id, also_switch) 

324 update_downstream_points(point_id) 

325 # Return the canvas_tag for the tkinter drawing objects  

326 return(canvas_tag) 

327 

328# ------------------------------------------------------------------------- 

329# Public API function to Lock a points (Warning generated if APL and not FPL active) 

330# ------------------------------------------------------------------------- 

331 

332def lock_point(point_id:int): 

333 global points 

334 if not isinstance(point_id, int): 

335 logging.error("Point "+str(point_id)+": lock_point - Point ID must be an integer") 

336 elif not point_exists(point_id): 

337 logging.error("Point "+str(point_id)+": lock_point - Point ID does not exist") 

338 elif not points[str(point_id)]["locked"]: 

339 logging.info ("Point "+str(point_id)+": Locking point") 

340 if not points[str(point_id)]["hasfpl"]: 

341 # If the point doesn't have a FPL we just inhibit the change button 

342 points[str(point_id)]["changebutton"].config(state="disabled") 

343 elif not points[str(point_id)]["fpllock"]: 

344 # If the FPL is not already active then we need to activate it (with a warning) 

345 logging.warning ("Point "+str(point_id)+": lock_point - Activating FPL before locking") 

346 toggle_fpl (point_id) 

347 # Now inhibit the FPL button to stop it being manually unlocked 

348 points[str(point_id)]["lockbutton"].config(state="disabled") 

349 points[str(point_id)]["locked"] = True 

350 return() 

351 

352# ------------------------------------------------------------------------- 

353# API function to Unlock a point 

354# ------------------------------------------------------------------------- 

355 

356def unlock_point(point_id:int): 

357 global points 

358 if not isinstance(point_id, int): 

359 logging.error("Point "+str(point_id)+": unlock_point - Point ID must be an integer") 

360 elif not point_exists(point_id): 

361 logging.error("Point "+str(point_id)+": unlock_point - Point ID does not exist") 

362 elif points[str(point_id)]["locked"]: 

363 logging.info("Point "+str(point_id)+": Unlocking point") 

364 if not points[str(point_id)]["hasfpl"]: 

365 # If the point doesn't have FPL we need to re-enable the change button 

366 points[str(point_id)]["changebutton"].config(state="normal") 

367 else: 

368 # If the point has FPL we just need to re-enable the FPL button 

369 points[str(point_id)]["lockbutton"].config(state="normal") 

370 points[str(point_id)]["locked"] = False 

371 return() 

372 

373# ------------------------------------------------------------------------- 

374# API function to Return the current state of the point 

375# ------------------------------------------------------------------------- 

376 

377def point_switched(point_id:int): 

378 if not isinstance(point_id, int): 

379 logging.error("Point "+str(point_id)+": point_switched - Point ID must be an integer") 

380 switched = False 

381 elif not point_exists(point_id): 

382 logging.error("Point "+str(point_id)+": point_switched - Point ID does not exist") 

383 switched = False 

384 else: 

385 switched = points[str(point_id)]["switched"] 

386 return(switched) 

387 

388# ------------------------------------------------------------------------- 

389# API function to query the current state of the FPL (no FPL will return True) 

390# ------------------------------------------------------------------------- 

391 

392def fpl_active(point_id:int): 

393 if not isinstance(point_id, int): 

394 logging.error("Point "+str(point_id)+": fpl_active - Point ID must be an integer") 

395 locked = False 

396 elif not point_exists(point_id): 

397 logging.error("Point "+str(point_id)+": fpl_active - Point ID does not exist") 

398 locked = False 

399 elif not points[str(point_id)]["hasfpl"]: 

400 # Point does not have a FPL - always return True in this case 

401 locked = True 

402 else: 

403 locked = points[str(point_id)]["fpllock"] 

404 return(locked) 

405 

406# ------------------------------------------------------------------------------------------ 

407# API function for deleting a point library object (including all the drawing objects). 

408# This is used by the schematic editor for changing point types where we delete the existing 

409# point with all its data and then recreate it (with the same ID) in its new configuration. 

410# ------------------------------------------------------------------------------------------ 

411 

412def delete_point(point_id:int): 

413 global points 

414 if not isinstance(point_id, int): 

415 logging.error("Point "+str(point_id)+": delete_point - Point ID must be an integer") 

416 elif not point_exists(point_id): 

417 logging.error("Point "+str(point_id)+": delete_point - Point ID does not exist") 

418 else: 

419 logging.debug("Point "+str(point_id)+": Deleting library object from the schematic") 

420 # Delete all the tkinter drawing objects associated with the point 

421 points[str(point_id)]["canvas"].delete(points[str(point_id)]["tags"]) 

422 points[str(point_id)]["changebutton"].destroy() 

423 points[str(point_id)]["lockbutton"].destroy() 

424 # Delete the point entry from the dictionary of points 

425 del points[str(point_id)] 

426 return() 

427 

428# ------------------------------------------------------------------------------------------ 

429# API function for updating the ID of the point to be 'autoswitched' by a point without 

430# needing to delete the point and then create it in its new state. The main use case is  

431# when bulk deleting objects via the schematic editor, where we want to avoid interleaving 

432# tkinter 'create' commands in amongst the 'delete' commands outside of the main tkinter 

433# loop as this can lead to problems with artefacts persisting on the canvas. 

434# ------------------------------------------------------------------------------------------ 

435 

436def update_autoswitch(point_id:int, autoswitch_id:int): 

437 if not isinstance(point_id, int): 

438 logging.error("Point "+str(point_id)+": update_autoswitch - Point ID must be an integer") 

439 elif not point_exists(point_id): 

440 logging.error("Point "+str(point_id)+": update_autoswitch - Point ID does not exist") 

441 elif not isinstance(autoswitch_id, int): 

442 logging.error("Point "+str(point_id)+": update_autoswitch - Autoswitch ID must be an integer") 

443 elif autoswitch_id > 0 and not point_exists(autoswitch_id): 

444 logging.error("Point "+str(point_id)+": update_autoswitch - Autoswitch ID does not exist") 

445 else: 

446 logging.debug("Point "+str(point_id)+": Updating Autoswitch point to "+str(autoswitch_id)) 

447 points[str(point_id)]["alsoswitch"] = autoswitch_id 

448 if point_exists(points[str(point_id)]["alsoswitch"]): 

449 validate_alsoswitch_point(point_id, autoswitch_id) 

450 update_downstream_points(point_id) 

451 return() 

452 

453# ------------------------------------------------------------------------------------------ 

454# Internal common function to validate point linking (raising a warning as required) 

455# ------------------------------------------------------------------------------------------ 

456 

457def validate_alsoswitch_point(point_id:int, autoswitch_id:int): 

458 for other_point in points: 

459 if points[other_point]['alsoswitch'] == autoswitch_id and other_point != str(point_id): 

460 # We've found another point 'also switching' the point we are trying to link to 

461 logging.warning("Point "+str(point_id)+": configuring to 'autoswitch' "+str(autoswitch_id)+ 

462 " - but point "+ other_point+" is also configured to 'autoswitch' "+str(autoswitch_id)) 

463 return() 

464 

465############################################################################### 

466