From e5060b33c5ab505a7ca493468ddcfa999912171f Mon Sep 17 00:00:00 2001 From: Chris Davoren Date: Sat, 21 Oct 2023 16:38:12 +1000 Subject: [PATCH] Variables in SimulationThread now consistently distinguish real vs simulation time in names. --- README.md | 8 --- simulation/simulation_thread.py | 57 ++++++++++--------- .../templates/trafficlightfrontend/index.html | 2 +- 3 files changed, 30 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 9073451..f86520d 100644 --- a/README.md +++ b/README.md @@ -82,11 +82,3 @@ Project settings are in the standard Django location, `trafflights/settings.py`. 1. **SIMULATION_START_TIME**: The internal time at which the simulation starts, in ISO text format. By default this is 07:59 for demonstration purposes. 1. **SIMULATION_TIME_FACTOR**: The time "acceleration" factor (floating point) for the simulation. A factor of 1.0 will run in real-time, and higher factors will run faster. Default is 4.0. -# TODO List - -1. Add instructions for installation/execution to README (including pre-requisites) -1. Add configuration options for periods data file, start time, and time factor -1. Change print statements to formal logging -1. Remaining refinements for frontend (font, remove debug visual aids, time remaining) -1. Move algorithm testing file to simulation directory, and change to use SimulationThread -1. Consider variable naming to better delineate similation vs real time variables diff --git a/simulation/simulation_thread.py b/simulation/simulation_thread.py index 699f462..c530e1d 100644 --- a/simulation/simulation_thread.py +++ b/simulation/simulation_thread.py @@ -45,8 +45,8 @@ class SimulationThread(threading.Thread): self.mutex = threading.Lock() self.periods = periods - self.start_time = start_time - self.run_time = run_time + self.start_simulation_time = start_time + self.run_real_time = run_time self.time_factor = time_factor self.verbose = verbose @@ -55,9 +55,11 @@ class SimulationThread(threading.Thread): self.current_period_index = 0 self.current_state_index = 0 - self.next_changeover_time = None - self.simulation_start_datetime = None + self.next_changeover_real_time = None + + self.start_simulation_datetime = None + self.current_simulation_datetime = None # For convenience, convert all ISO format times to datetime.time objects for period in self.periods: @@ -78,13 +80,12 @@ class SimulationThread(threading.Thread): """ Resets the simulation time to the provided time. This is for testing purposes; the current state is IGNORED and the sequence immediately resets to the beginning. """ - new_datetime = datetime.datetime.fromisoformat("1900-01-01 " + new_time.isoformat()) + new_simulation_datetime = datetime.datetime.fromisoformat("1900-01-01 " + new_time.isoformat()) self.mutex.acquire() print() - # print("Forcing internal time change from {} to {}".format(self.simulation_start_datetime.time().isoformat(), new_time.isoformat())) - self.current_period_index = self._get_period_index(self.periods, new_datetime.time()) + self.current_period_index = self._get_period_index(self.periods, new_simulation_datetime.time()) self.current_state_index = 0 - self.next_changeover_time = time.time() + (self.periods[self.current_period_index]["states"][self.current_state_index]["duration"] / self.time_factor) + self.next_changeover_real_time = time.time() + (self.periods[self.current_period_index]["states"][self.current_state_index]["duration"] / self.time_factor) self.mutex.release() def get_snapshot(self): @@ -93,14 +94,14 @@ class SimulationThread(threading.Thread): """ self.mutex.acquire() return_data = { - "current_period_name": self.periods[self.current_period_index]["name"], + "current_period_name": self.periods[self.current_period_index]["name"], "current_period_verbose_name": self.periods[self.current_period_index]["verbose-name"], "current_period_index": self.current_period_index, - "current_state_index" : self.current_state_index, - "current_state_data": self.periods[self.current_period_index]["states"][self.current_state_index], - "next_changeover_time": self.next_changeover_time, + "current_state_index": self.current_state_index, + "current_state_data": self.periods[self.current_period_index]["states"][self.current_state_index], + "next_changeover_real_time": self.next_changeover_real_time, "simulation_time": self.current_simulation_datetime.time(), - "time_remaining": (self.next_changeover_time - time.time()) * self.time_factor, + "time_remaining": (self.next_changeover_real_time - time.time()) * self.time_factor, } self.mutex.release() return return_data @@ -128,30 +129,29 @@ class SimulationThread(threading.Thread): - signal_stop() has been called - The executing program finishes (daemon=True) """ - real_start_time = time.time() + start_real_time = time.time() - self.simulation_start_datetime = None - if self.start_time is None: - self.simulation_start_datetime = datetime.datetime.now() + if self.start_simulation_time is None: + self.start_simulation_datetime = datetime.datetime.now() else: - self.simulation_start_datetime = datetime.datetime.fromisoformat( - "1900-01-01 " + self.start_time.isoformat()) + self.start_simulation_datetime = datetime.datetime.fromisoformat( + "1900-01-01 " + self.start_simulation_time.isoformat()) - self.current_period_index = self._get_period_index(self.simulation_start_datetime.time()) + self.current_period_index = self._get_period_index(self.start_simulation_datetime.time()) self.current_state_index = 0 - self.next_changeover_time = real_start_time + (self.periods[self.current_period_index]["states"][self.current_state_index]["duration"] / self.time_factor) + self.next_changeover_real_time = start_real_time + (self.periods[self.current_period_index]["states"][self.current_state_index]["duration"] / self.time_factor) if self.verbose: - print("Starting with simulation time {}".format(self.simulation_start_datetime.time())) + print("Starting with simulation time {}".format(self.start_simulation_datetime.time())) print("Initial state data: {}".format(self.periods[self.current_period_index]["states"][self.current_state_index])) - if self.run_time is None: + if self.run_real_time is None: print("No simulation time limit.") else: - print("Simulation duration (in real second(s)): {}".format(self.run_time)) + print("Simulation duration (in real second(s)): {}".format(self.run_real_time)) print("Time compression factor: {}".format(self.time_factor)) - while (self.run_time is None) or (self.run_time is not None and time.time() < real_start_time + self.run_time): + while (self.run_real_time is None) or (self.run_real_time is not None and time.time() < start_real_time + self.run_real_time): self.mutex.acquire() if not self.running: @@ -159,10 +159,10 @@ class SimulationThread(threading.Thread): break now = time.time() - self.current_simulation_datetime = self.simulation_start_datetime + datetime.timedelta(seconds=int((now - real_start_time) * self.time_factor)) + self.current_simulation_datetime = self.start_simulation_datetime + datetime.timedelta(seconds=int((now - start_real_time) * self.time_factor)) timed_period_index = self._get_period_index(self.current_simulation_datetime.time()) - if now >= self.next_changeover_time: + if now >= self.next_changeover_real_time: timed_period_index = self._get_period_index(self.current_simulation_datetime.time()) if timed_period_index != self.current_period_index and self.current_state_index == len(self.periods[self.current_period_index]["states"])-1: if self.verbose: @@ -192,7 +192,8 @@ class SimulationThread(threading.Thread): self.current_state_index = next_cycle_index - self.next_changeover_time = self.next_changeover_time + (self.periods[self.current_period_index]["states"][self.current_state_index]["duration"] / self.time_factor) + # Ensure this calculation is done based on the PREVIOUS scheduled time to avoid "timing drift" + self.next_changeover_real_time = self.next_changeover_real_time + (self.periods[self.current_period_index]["states"][self.current_state_index]["duration"] / self.time_factor) else: if self.verbose: print(".", end="") diff --git a/trafficlightfrontend/templates/trafficlightfrontend/index.html b/trafficlightfrontend/templates/trafficlightfrontend/index.html index abdd7cf..7ef268f 100644 --- a/trafficlightfrontend/templates/trafficlightfrontend/index.html +++ b/trafficlightfrontend/templates/trafficlightfrontend/index.html @@ -105,7 +105,7 @@ while (true) { await sleep(250); - console.log("Tick"); + // console.log("Tick"); req.open("GET", "/frontend/status", false); // Note try/catch for exceptions is the only way to account for net::ERR_CONNECTION_REFUSED - this is NOT dealt with by XMLHttpRequest event hooks.