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

59 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2024-04-05 16:40 +0100

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

2# This module contains all of the parameters, funcions and classes that  

3# are used across multiple modules in the model_railway_signalling library 

4#-------------------------------------------------------------------------- 

5# 

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

7# 

8# set_root_window(root) - initialise the library with the root window reference 

9# 

10# shutdown() - perform an orderly shutdown of the library functions (clean up) 

11# 

12# External API - classes and functions (used by the other library modules): 

13# 

14# execute_function_in_tkinter_thread(callback_function) - Will 'pass' the function 

15# into the main tkinter thread via a queue (and tkinter event) and then execute 

16# the function - used for MQTT and GPIO events to keep everything threadsafe. 

17# 

18# ------------------------------------------------------------------------- 

19 

20import math 

21import queue 

22import logging 

23import time 

24from . import mqtt_interface 

25from . import pi_sprog_interface 

26from . import gpio_sensors 

27 

28# ------------------------------------------------------------------------- 

29# Global variables used within the Library Modules 

30# ------------------------------------------------------------------------- 

31 

32# Global variables for how the signals/points/sections buttons appear 

33# on the screen. This is to allow the appearance to be optimised for 

34# particular window sizes/screen resolutions. 

35fontsize = 8 # Used by the Signals, Points and sections modules 

36xpadding = 2 # Used by the Signals, Points and sections modules 

37ypadding = -1 # Used by the Signals, Points and sections modules 

38bgraised = "grey85" # Used by the Signals and Points modules 

39bgsunken = "white" # Used by the Signals and Points modules 

40 

41# Global Variable to hold a reference to the TkInter Root Window 

42root_window = None 

43# Event queue for passing "commands" back into the main tkinter thread 

44event_queue = queue.Queue() 

45# Global variable to signal (to other modules) that application is closing 

46shutdown_initiated = False 

47 

48#------------------------------------------------------------------------- 

49# Function to perfor an orderly shutdown of the library modules: 

50# MQTT Networking - clean up the published topics and disconnect 

51# SPROG interface - switch off the DCC power and close the serial port 

52# GPIO Sensors - revert all GPIO pins to their default states 

53# Finally - wait for all scheduled TKinter events to complete 

54#------------------------------------------------------------------------- 

55 

56def shutdown(): 

57 global shutdown_initiated 

58 if not shutdown_initiated: 58 ↛ 81line 58 didn't jump to line 81, because the condition on line 58 was never false

59 logging.info ("Initiating Application Shutdown") 

60 shutdown_initiated = True 

61 # Clear out any retained messages and disconnect from broker 

62 mqtt_interface.mqtt_shutdown() 

63 # Turn off the DCC bus power and close the comms port 

64 pi_sprog_interface.sprog_shutdown() 

65 # Return the GPIO ports to their original configuration 

66 gpio_sensors.gpio_shutdown() 

67 # Wait until all the tasks we have scheduled via the tkinter 'after' method have completed 

68 # We need to put a timeout around this to deal with any scheduled Tkinter "after" events 

69 # (although its unlikely the user would initiate a shut down until these have finished) 

70 timeout_start = time.time() 

71 while time.time() < timeout_start + 30: 71 ↛ 78line 71 didn't jump to line 78, because the condition on line 71 was never false

72 if root_window.tk.call('after','info') != "": 72 ↛ 73line 72 didn't jump to line 73, because the condition on line 72 was never true

73 root_window.update() 

74 time.sleep(0.01) 

75 else: 

76 logging.info ("Exiting Application") 

77 break 

78 if time.time() >= timeout_start + 30: 78 ↛ 79line 78 didn't jump to line 79, because the condition on line 78 was never true

79 logging.warning ("Timeout waiting for scheduled tkinter events to complete - Exiting anyway") 

80 root_window.destroy() 

81 return() 

82 

83#------------------------------------------------------------------------- 

84# Function to set the tkinter "root" window reference as this is used to 

85# schedule callback events in the main tkinter event loop using the 'after'  

86# method and also for feeding custom callback functions into the main tkinter 

87# thread. We do this as all the information out there on the internet concludes 

88# tkinter isn't fully thread safe and so all manipulation of tkinter drawing 

89# objects should be done from within the main tkinter thread. 

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

91 

92def set_root_window (root): 

93 global root_window 

94 root_window = root 

95 # bind the tkinter event for handling events raised in external threads 

96 root_window.bind("<<ExtCallback>>", handle_callback_in_tkinter_thread) 

97 return(root_window) 

98 

99#------------------------------------------------------------------------- 

100# Functions to allow custom callback functions to be passed in (from an external 

101# thread) and then handled in the main Tkinter thread (to keep everything threadsafe). 

102# We use the tkinter event_generate method to generate a custom event in the main 

103# tkinter event loop in conjunction with a (threadsafe) queue to pass the callback function 

104# Use as follows: execute_function_in_tkinter_thread (lambda: my_function(arg1,arg2...)) 

105#------------------------------------------------------------------------- 

106 

107def handle_callback_in_tkinter_thread(*args): 

108 while not event_queue.empty() and not shutdown_initiated: 

109 callback = event_queue.get(False) 

110 callback() 

111 return() 

112 

113def execute_function_in_tkinter_thread(callback_function): 

114 if not shutdown_initiated: 114 ↛ 121line 114 didn't jump to line 121, because the condition on line 114 was never false

115 if root_window is not None: 115 ↛ 119line 115 didn't jump to line 119, because the condition on line 115 was never false

116 event_queue.put(callback_function) 

117 root_window.event_generate("<<ExtCallback>>", when="tail") 

118 else: 

119 logging.error ("Execute_function_in_tkinter_thread - root undefined - executing in current thread") 

120 callback_function() 

121 return() 

122 

123# ------------------------------------------------------------------------- 

124# Common functions to rotate offset coordinates around an origin 

125# The angle should be passed into these functions in degrees. 

126# ------------------------------------------------------------------------- 

127 

128def rotate_point(ox,oy,px,py,angle): 

129 angle = math.radians(angle) 

130 qx = ox + math.cos(angle) * (px) - math.sin(angle) * (py) 

131 qy = oy + math.sin(angle) * (px) + math.cos(angle) * (py) 

132 return (qx,qy) 

133 

134def rotate_line(ox,oy,px1,py1,px2,py2,angle): 

135 start_point = rotate_point(ox,oy,px1,py1,angle) 

136 end_point = rotate_point(ox,oy,px2,py2,angle) 

137 return (start_point, end_point) 

138 

139################################################################################################## 

140