Scale the Sud forecast progress by the server's actual warp factor
SudTask now computes warp_factor = dt/interval (the same simulated-vs- wall-clock split Pot/TempController/Stirrer already use internally) and sends it once at startup as 'WarpFactor'. The GUI uses it to scale real elapsed time into the forecast's simulated-schedule time axis, so the progress line lines up with the actual sim speed instead of assuming real time. Also fixes SudTask.tick() to decrement hold_remaining by dt (simulated seconds) instead of interval (wall-clock seconds) - it was the only place in the sim using the wall-clock interval for something meant to track simulated time, so hold steps were taking warp_factor times longer in real time than their declared duration intends. Without this fix the GUI's new warp-scaled progress line would race ahead of the real server during holds.
This commit is contained in:
+1
-1
@@ -74,7 +74,7 @@ if __name__ == '__main__':
|
|||||||
sud_path = config.get('sud')
|
sud_path = config.get('sud')
|
||||||
if sud_path:
|
if sud_path:
|
||||||
sud = Sud(sud_path)
|
sud = Sud(sud_path)
|
||||||
taskmgr.add(SudTask(sud, tc, stirrer, pot, DT_TASK, dispatcher.msgio_get("Sud")))
|
taskmgr.add(SudTask(sud, tc, stirrer, pot, DT, DT_TASK, dispatcher.msgio_get("Sud")))
|
||||||
|
|
||||||
# Tracer
|
# Tracer
|
||||||
taskmgr.add(TracerTask(sensor, heater, tc, trace_tc, DT_TASK_TRACER, dispatcher.msgio_get("Tracer")))
|
taskmgr.add(TracerTask(sensor, heater, tc, trace_tc, DT_TASK_TRACER, dispatcher.msgio_get("Tracer")))
|
||||||
|
|||||||
+11
-1
@@ -190,6 +190,10 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
|
|||||||
self.sud_loaded = False
|
self.sud_loaded = False
|
||||||
self.sud_state = None
|
self.sud_state = None
|
||||||
self.sud_user_message = None
|
self.sud_user_message = None
|
||||||
|
# Simulated-seconds-per-real-second, from SudTask's 'WarpFactor'
|
||||||
|
# message; defaults to 1.0 (no scaling) until that arrives, or for
|
||||||
|
# a server that doesn't send it at all.
|
||||||
|
self.sud_warp_factor = 1.0
|
||||||
# time.monotonic() of the last IDLE/DONE -> running transition, used
|
# time.monotonic() of the last IDLE/DONE -> running transition, used
|
||||||
# to position the forecast plot's progress line; None while not
|
# to position the forecast plot's progress line; None while not
|
||||||
# running. sud_paused_total accumulates time spent PAUSED so the
|
# running. sud_paused_total accumulates time spent PAUSED so the
|
||||||
@@ -287,7 +291,10 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
|
|||||||
# While paused, use the moment the pause began as "now" so the
|
# While paused, use the moment the pause began as "now" so the
|
||||||
# line freezes instead of drifting ahead of actual progress.
|
# line freezes instead of drifting ahead of actual progress.
|
||||||
now = self.sud_pause_started_at if self.sud_pause_started_at is not None else time.monotonic()
|
now = self.sud_pause_started_at if self.sud_pause_started_at is not None else time.monotonic()
|
||||||
elapsed_min = (now - self.sud_start_time - self.sud_paused_total) / 60.0
|
elapsed_real_s = now - self.sud_start_time - self.sud_paused_total
|
||||||
|
# The forecast's x-axis is simulated-schedule time; scale real
|
||||||
|
# elapsed time by the server's warp factor to match it.
|
||||||
|
elapsed_min = elapsed_real_s * self.sud_warp_factor / 60.0
|
||||||
self.forecast_plot.set_progress(elapsed_min)
|
self.forecast_plot.set_progress(elapsed_min)
|
||||||
else:
|
else:
|
||||||
self.forecast_plot.clear_progress()
|
self.forecast_plot.clear_progress()
|
||||||
@@ -314,6 +321,7 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
|
|||||||
self.sud_loaded = False
|
self.sud_loaded = False
|
||||||
self.sud_state = None
|
self.sud_state = None
|
||||||
self.sud_user_message = None
|
self.sud_user_message = None
|
||||||
|
self.sud_warp_factor = 1.0
|
||||||
self.sud_start_time = None
|
self.sud_start_time = None
|
||||||
self.sud_paused_total = 0.0
|
self.sud_paused_total = 0.0
|
||||||
self.sud_pause_started_at = None
|
self.sud_pause_started_at = None
|
||||||
@@ -483,6 +491,8 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
|
|||||||
self.update_sud_forecast(msg['Json'])
|
self.update_sud_forecast(msg['Json'])
|
||||||
elif "Name" in key:
|
elif "Name" in key:
|
||||||
self.statusBar().showMessage("Sud schedule updated: {}".format(msg['Name']), 5000)
|
self.statusBar().showMessage("Sud schedule updated: {}".format(msg['Name']), 5000)
|
||||||
|
elif "WarpFactor" in key:
|
||||||
|
self.sud_warp_factor = msg['WarpFactor']
|
||||||
elif "State" in key:
|
elif "State" in key:
|
||||||
prev_state = self.sud_state
|
prev_state = self.sud_state
|
||||||
self.sud_state = msg['State']
|
self.sud_state = msg['State']
|
||||||
|
|||||||
+15
-3
@@ -11,12 +11,20 @@ TEMP_REACHED_TOLERANCE = 0.2
|
|||||||
|
|
||||||
|
|
||||||
class SudTask(ATask):
|
class SudTask(ATask):
|
||||||
def __init__(self, sud: Sud, tc: APid, stirrer: AStirrer, pot: APlant, interval, msg_handler: MsgIo):
|
def __init__(self, sud: Sud, tc: APid, stirrer: AStirrer, pot: APlant, dt, interval, msg_handler: MsgIo):
|
||||||
ATask.__init__(self, interval)
|
ATask.__init__(self, interval)
|
||||||
self.sud = sud
|
self.sud = sud
|
||||||
self.tc = tc
|
self.tc = tc
|
||||||
self.stirrer = stirrer
|
self.stirrer = stirrer
|
||||||
self.pot = pot
|
self.pot = pot
|
||||||
|
# Simulated seconds per tick, vs. interval's wall-clock seconds per
|
||||||
|
# tick - same dt/interval split Pot/TempController/Stirrer already
|
||||||
|
# use internally. Their warp-induced speedup falls out naturally
|
||||||
|
# from ticking real physics at simulated dt; Sud has no physics of
|
||||||
|
# its own, so hold_remaining must be ticked by dt explicitly to get
|
||||||
|
# the same speedup instead of running in real time.
|
||||||
|
self.dt = dt
|
||||||
|
self.warp_factor = dt / interval
|
||||||
self.msg_handler = msg_handler
|
self.msg_handler = msg_handler
|
||||||
msg_handler.set_recv_handler(self.recv)
|
msg_handler.set_recv_handler(self.recv)
|
||||||
|
|
||||||
@@ -104,7 +112,11 @@ class SudTask(ATask):
|
|||||||
self.sud.set_on_changed('user_message', self.on_user_message_changed)
|
self.sud.set_on_changed('user_message', self.on_user_message_changed)
|
||||||
self.sud.set_on_changed('hold_remaining', ChangedFloat(self.on_hold_remaining_changed, prec=0).set)
|
self.sud.set_on_changed('hold_remaining', ChangedFloat(self.on_hold_remaining_changed, prec=0).set)
|
||||||
|
|
||||||
asyncio.create_task(self.send({'Name': self.sud.name, 'Description': self.sud.description}))
|
asyncio.create_task(self.send({
|
||||||
|
'Name': self.sud.name,
|
||||||
|
'Description': self.sud.description,
|
||||||
|
'WarpFactor': self.warp_factor,
|
||||||
|
}))
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
if self.sud.state == SudState.RAMPING:
|
if self.sud.state == SudState.RAMPING:
|
||||||
@@ -113,5 +125,5 @@ class SudTask(ATask):
|
|||||||
# the previous step for one tick after a new target is pushed.
|
# the previous step for one tick after a new target is pushed.
|
||||||
if abs(self.tc.get_theta_ist() - self.tc.get_theta_soll_set()) < TEMP_REACHED_TOLERANCE:
|
if abs(self.tc.get_theta_ist() - self.tc.get_theta_soll_set()) < TEMP_REACHED_TOLERANCE:
|
||||||
self.sud.temp_reached()
|
self.sud.temp_reached()
|
||||||
self.sud.tick(self.interval)
|
self.sud.tick(self.dt)
|
||||||
await asyncio.sleep(self.interval)
|
await asyncio.sleep(self.interval)
|
||||||
|
|||||||
Reference in New Issue
Block a user