Skip to content

Commit

Permalink
Merge pull request #295 from tinymovr/studio/timing_improvements
Browse files Browse the repository at this point in the history
Studio GUI timer improvements and bug fixes
  • Loading branch information
yconst authored Sep 25, 2023
2 parents 11ab9bd + 647e2be commit 19d4f26
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 82 deletions.
3 changes: 1 addition & 2 deletions studio/Python/tinymovr/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,14 @@
"""

import can
from canine import CANineBus
import pkg_resources
import IPython
from traitlets.config import Config
from docopt import docopt

from tinymovr import init_tee, destroy_tee
from tinymovr.discovery import Discovery
from tinymovr.constants import app_name, base_node_name
from tinymovr.constants import app_name
from tinymovr.config import get_bus_config, configure_logging

"""
Expand Down
1 change: 0 additions & 1 deletion studio/Python/tinymovr/gui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
magnitude_of,
hold_sema,
TimedGetter,
RateLimitedFunction,
check_selected_items,
get_dynamic_attrs,
is_dark_mode
Expand Down
30 changes: 0 additions & 30 deletions studio/Python/tinymovr/gui/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -432,36 +432,6 @@ def get_value(self, getter):
return val


class RateLimitedFunction:
"""
A class that limits the rate of calls to a passed
function f
"""

def __init__(self, func, rate):
self.func = func
self.rate = rate
self.busy_dt = 0
self.meas_dt = 0
self.load = 0
self.stop = False

def call(self, *args, **kwargs):
if self.busy_dt > 0 and self.busy_dt < self.rate:
self.load = self.load * 0.99 + self.busy_dt / self.rate * 0.01
time.sleep(self.rate - self.busy_dt)
self.meas_dt = self.rate
else:
self.load = 1
self.meas_dt = self.busy_dt
start_time = time.time()
self.func()
self.busy_dt = time.time() - start_time

def __call__(self, *args, **kwargs):
self.call(self, *args, **kwargs)


def display_warning(title, text):
"""
Display a pop up message with a warning
Expand Down
56 changes: 32 additions & 24 deletions studio/Python/tinymovr/gui/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from contextlib import suppress
import json
from PySide6 import QtCore
from PySide6.QtCore import Signal
from PySide6.QtCore import Signal, QTimer
from PySide6.QtWidgets import (
QMainWindow,
QDialog,
Expand Down Expand Up @@ -146,22 +146,29 @@ def __init__(self, app, arguments):
else:
params = {"bustype": buses[0], "channel": channel, "bitrate": bitrate}

self.thread = QtCore.QThread()
self.worker = Worker(params, self.logger)

self.TreeItemCheckedSignal.connect(self.worker.update_active_attrs)
self.thread.started.connect(self.worker.run)
self.worker.moveToThread(self.thread)
self.worker.handle_error.connect(self.handle_worker_error)
self.worker.regen.connect(self.regen_tree)
self.worker.update_attrs.connect(self.attrs_updated)
self.worker.handleErrorSignal.connect(
self.handle_worker_error, QtCore.Qt.QueuedConnection
)
self.worker.regenSignal.connect(self.regen_tree, QtCore.Qt.QueuedConnection)
self.worker.updateAttrsSignal.connect(
self.attrs_updated, QtCore.Qt.QueuedConnection
)
self.worker.updateTimingsSignal.connect(
self.timings_updated, QtCore.Qt.QueuedConnection
)
app.aboutToQuit.connect(self.about_to_quit)
self.thread.start()

self.timer = QTimer(self)
self.timer.timeout.connect(self.worker._update)
self.timer.start(40)

@QtCore.Slot()
def about_to_quit(self):
self.timer.stop()
self.worker.stop()
self.thread.quit()
self.thread.wait()

@QtCore.Slot()
def handle_worker_error(self, e):
Expand All @@ -173,7 +180,7 @@ def handle_worker_error(self, e):
else:
self.logger.warn("Timeout while getting value.")
self.timeout_count += 1

else:
raise e

Expand Down Expand Up @@ -289,7 +296,12 @@ def item_changed(self, item):
@QtCore.Slot()
def attrs_updated(self, data):
for attr_name, val in data.items():
self.attr_widgets_by_id[attr_name]["widget"].setText(1, format_value(val))
try:
self.attr_widgets_by_id[attr_name]["widget"].setText(
1, format_value(val)
)
except RuntimeError:
self.logger.warn("Attribute widget disappeared while updating")
if attr_name in self.graphs_by_id:
graph_info = self.graphs_by_id[attr_name]
x = graph_info["data"]["x"]
Expand All @@ -301,36 +313,32 @@ def attrs_updated(self, data):
y.append(magnitude_of(val))
graph_info["data_line"].setData(x, y)
graph_info["widget"].update()
meas_dt = self.worker._rate_limited_update.meas_dt
if meas_dt == 0:
meas_dt_str = "-"
else:
meas_dt_str = "{:.1f}Hz".format(meas_dt)

@QtCore.Slot()
def timings_updated(self, timings_dict):
meas_freq = timings_dict["meas_freq"]
meas_freq_str = "-" if meas_freq == 0 else "{:.1f}Hz".format(meas_freq)
self.status_label.setText(
"{}\t CH:{:.0f}%\t RT:{:.1f}ms".format(
meas_dt_str,
self.worker._rate_limited_update.load * 100,
self.worker.timed_getter.dt * 1000,
meas_freq_str,
timings_dict["load"],
timings_dict["getter_dt"] * 1000,
)
)

@QtCore.Slot()
def f_call_clicked(self, f):
args = []

# Check if the function has any arguments
if f.arguments:
dialog = ArgumentInputDialog(f.arguments, self)
if dialog.exec_() == QDialog.Accepted:
input_values = dialog.get_values()
args = [input_values[arg.name] for arg in f.arguments]
else:
return # User cancelled, stop the entire process

# Convert arguments as required using pint
args = [get_registry()(arg) for arg in args]

# Call the function with the collected arguments
f(*args)
if "reload_data" in f.meta and f.meta["reload_data"]:
self.worker.reset()
Expand Down
44 changes: 19 additions & 25 deletions studio/Python/tinymovr/gui/worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,51 +20,43 @@
import can
from PySide6 import QtCore
from PySide6.QtCore import QObject
from PySide6.QtWidgets import (
QApplication,
)
from tinymovr.gui import TimedGetter, RateLimitedFunction, get_dynamic_attrs
from tinymovr.gui import TimedGetter, get_dynamic_attrs
from tinymovr.tee import init_tee, destroy_tee
from tinymovr.discovery import Discovery
from tinymovr.constants import base_node_name


class Worker(QObject):

update_attrs = QtCore.Signal(dict)
regen = QtCore.Signal(dict)
handle_error = QtCore.Signal(object)
updateAttrsSignal = QtCore.Signal(dict)
updateTimingsSignal = QtCore.Signal(dict)
regenSignal = QtCore.Signal(dict)
handleErrorSignal = QtCore.Signal(object)

def __init__(self, busparams, logger):
super().__init__()
self.logger = logger
self.mutx = QtCore.QMutex()
init_tee(can.Bus(**busparams))
self._init_containers()
self.dsc = Discovery(self._device_appeared, self._device_disappeared, self.logger)
self.dsc = Discovery(
self._device_appeared, self._device_disappeared, self.logger
)
self.timed_getter = TimedGetter()
self._rate_limited_update = RateLimitedFunction(lambda: self._update(), 0.040)
self.running = True

def run(self):
while self.running:
self._rate_limited_update() # calls update()
destroy_tee()

@QtCore.Slot()
def stop(self):
self.running = False
destroy_tee()

def force_regen(self):
self.mutx.lock()
self.regen.emit(dict(self.devices_by_name))
self.regenSignal.emit(dict(self.devices_by_name))
self.mutx.unlock()

def reset(self):
self.mutx.lock()
self.dsc.reset()
self._init_containers()
self.regen.emit(dict(self.devices_by_name))
self.regenSignal.emit(dict(self.devices_by_name))
self.mutx.unlock()

@QtCore.Slot(dict)
Expand All @@ -80,7 +72,7 @@ def _get_attr_values(self):
try:
vals[attr.full_name] = self.timed_getter.get_value(attr.get_value)
except Exception as e:
self.handle_error.emit(e)
self.handleErrorSignal.emit(e)
start_time = time.time()
self.dynamic_attrs.sort(
key=lambda attr: self.dynamic_attrs_last_update[attr.full_name]
Expand All @@ -98,7 +90,7 @@ def _get_attr_values(self):
vals[attr.full_name] = self.timed_getter.get_value(attr.get_value)
self.dynamic_attrs_last_update[attr.full_name] = start_time
except Exception as e:
self.handle_error.emit(e)
self.handleErrorSignal.emit(e)
break
return vals

Expand All @@ -113,9 +105,11 @@ def _update(self):
self.mutx.lock()
last_updated = self._get_attr_values()
if len(last_updated) > 0:
self.update_attrs.emit(last_updated)
self.updateAttrsSignal.emit(last_updated)
self.updateTimingsSignal.emit(
{"meas_freq": 0, "load": 0, "getter_dt": self.timed_getter.dt}
)
self.mutx.unlock()
QApplication.processEvents()

def _device_appeared(self, device, node_id):
self.mutx.lock()
Expand All @@ -125,7 +119,7 @@ def _device_appeared(self, device, node_id):
device.name = display_name
device.include_base_name = True
self.dynamic_attrs = get_dynamic_attrs(self.devices_by_name)
self.regen.emit(dict(self.devices_by_name))
self.regenSignal.emit(dict(self.devices_by_name))
self.mutx.unlock()

def _device_disappeared(self, node_id):
Expand All @@ -134,5 +128,5 @@ def _device_disappeared(self, node_id):
del self.devices_by_name[display_name]
del self.names_by_id[node_id]
self.dynamic_attrs = get_dynamic_attrs(self.devices_by_name)
self.regen.emit(dict(self.devices_by_name))
self.regenSignal.emit(dict(self.devices_by_name))
self.mutx.unlock()

0 comments on commit 19d4f26

Please sign in to comment.