diff --git a/dronecan_gui_tool/panels/esc_panel.py b/dronecan_gui_tool/panels/esc_panel.py index 4cec913..8907888 100644 --- a/dronecan_gui_tool/panels/esc_panel.py +++ b/dronecan_gui_tool/panels/esc_panel.py @@ -56,6 +56,11 @@ def __init__(self, esc_index, parent): self._temperature_label = QLabel('Temp: NC', self) self._rpm_label = QLabel('RPM: NC', self) self._power_rating_pct_label = QLabel('RAT: NC', self) + self._fps_label = QLabel('FPS: 0', self) + + self._fps_count = 0 + self._fps_window_start = time.time() + self._fps_last_msg_time = 0 layout = QHBoxLayout(self) @@ -67,6 +72,7 @@ def __init__(self, esc_index, parent): status_layout.addWidget(self._temperature_label) status_layout.addWidget(self._rpm_label) status_layout.addWidget(self._power_rating_pct_label) + status_layout.addWidget(self._fps_label) status_layout.addStretch() status_layout.addWidget(self._spinbox) status_layout.addWidget(self._zero_button) @@ -92,9 +98,25 @@ def view_mode_set_value(self, value): self._slider.setValue(value) self._spinbox.setValue(value) + def check_fps_timeout(self): + now = time.time() + if self._fps_last_msg_time > 0 and now - self._fps_last_msg_time >= 1.0: + self._fps_label.setText('FPS: 0.0') + self._fps_count = 0 + self._fps_window_start = now + self._fps_last_msg_time = 0 + def update_status(self, idx, error_count, voltage, current, temperature_celsius, rpm, power_rating_pct): if idx != self._index: return + now = time.time() + self._fps_count += 1 + self._fps_last_msg_time = now + elapsed = now - self._fps_window_start + if elapsed >= 1.0: + self._fps_label.setText(f'FPS: {self._fps_count / elapsed:.1f}') + self._fps_count = 0 + self._fps_window_start = now if error_count is not None: self._error_count_label.setText(f'Err: {error_count}') if voltage is not None: @@ -288,6 +310,8 @@ def _update_view_mode(self): def _do_broadcast(self): try: self._update_view_mode() + for sl in self._sliders: + sl.check_fps_timeout() if not self._view_mode.isChecked(): if not self._pause.isChecked(): if self._safety_enable.checkState(): diff --git a/dronecan_gui_tool/version.py b/dronecan_gui_tool/version.py index c29544a..0cf73d8 100644 --- a/dronecan_gui_tool/version.py +++ b/dronecan_gui_tool/version.py @@ -8,7 +8,7 @@ # Andrew Tridgell # # -__version__ = 1, 2, 28 +__version__ = 1, 2, 29 diff --git a/dronecan_gui_tool/widgets/node_properties.py b/dronecan_gui_tool/widgets/node_properties.py index 6fa8d0e..03169ce 100644 --- a/dronecan_gui_tool/widgets/node_properties.py +++ b/dronecan_gui_tool/widgets/node_properties.py @@ -19,6 +19,7 @@ from .node_monitor import node_health_to_color, node_mode_to_color from .file_server import FileServer_PathKey from ..am32_rtttl import AM32_Rtttl +from typing import Optional logger = getLogger(__name__) @@ -26,6 +27,13 @@ REQUEST_PRIORITY = 30 +class ConfigParamEntry: + def __init__(self, index, response): + self._index = index + self._value = response.value + self._name = response.name + self._response = response; + self._sync=True class FieldValueWidget(QLineEdit): def __init__(self, parent, initial_value=None): @@ -656,7 +664,7 @@ def update_callback(value, is_melody=False): else: self._table.item(index, self.VALUE_COLUMN).setText(str(value)) - win = ConfigParamEditWindow(self, self._node, self._target_node_id, self._params[index], update_callback) + win = ConfigParamEditWindow(self, self._node, self._target_node_id, self._params[index]._response, update_callback) win.show() def _on_fetch_response(self, index, e): @@ -679,14 +687,17 @@ def _on_fetch_response(self, index, e): self.window().show_message('%d params fetched successfully', index) return - self._params.append(e.response) + # create a parameter structure including the index from the response + param = ConfigParamEntry(index, e.response) + + self._params.append(param) self._table.setRowCount(self._table.rowCount() + 1) self._table.set_row(self._table.rowCount() - 1, (index, e.response)) try: index += 1 self.window().show_message('Requesting index %d', index) - self._node.defer(0.1, lambda: self._node.request(dronecan.uavcan.protocol.param.GetSet.Request(index=index), + self._node.defer(0.01, lambda: self._node.request(dronecan.uavcan.protocol.param.GetSet.Request(index=index), self._target_node_id, partial(self._on_fetch_response, index), priority=REQUEST_PRIORITY)) @@ -740,9 +751,9 @@ def _do_save_to_file(self): print("save to file", param_file) f = open(param_file, "w") for p in self._params: - value = p.value - name = p.name - value_string = self.param_as_string(value, AM32_Rtttl.is_am32_melody_param(p)) + value = p._value + name = p._name + value_string = self.param_as_string(value, AM32_Rtttl.is_am32_melody_param(p._response)) if value_string: f.write("%s %s\n" % (name, value_string)) f.close() @@ -753,12 +764,12 @@ def _on_send_response(self, e): else: for i in range(len(self._params)): p = self._params[i] - name = str(p.name) + name = str(p._name) if name == str(e.response.name): logger.info('set %s to %s' % (name, self.param_as_string(e.response.value))) - self._table.item(i, self.VALUE_COLUMN).setText(self.param_as_string(e.response.value, AM32_Rtttl.is_am32_melody_param(p))) + self._table.item(i, self.VALUE_COLUMN).setText(self.param_as_string(e.response.value, AM32_Rtttl.is_am32_melody_param(p._response))) - def save_param(self, name, old_value, str_value): + def save_param(self, name, old_value, str_value, index: Optional[int] = None, delay: Optional[float] = None): value_type = dronecan.get_active_union_field(old_value) v = old_value @@ -774,8 +785,16 @@ def save_param(self, name, old_value, str_value): raise RuntimeError('bad parameter type on save') try: - request = dronecan.uavcan.protocol.param.GetSet.Request(name=name, value=v) - self._node.request(request, self._target_node_id, self._on_send_response, priority=REQUEST_PRIORITY) + if index is None: + request = dronecan.uavcan.protocol.param.GetSet.Request(name=name, value=v) + else: + request = dronecan.uavcan.protocol.param.GetSet.Request(index=index, value=v) + + if delay is None: + self._node.request(request, self._target_node_id, self._on_send_response, priority=REQUEST_PRIORITY) + else: + self._node.defer(delay, lambda: self._node.request(request, self._target_node_id, self._on_send_response, priority=REQUEST_PRIORITY)) + except Exception as ex: show_error('Node error', 'Could not send param set request', ex, self) @@ -787,11 +806,12 @@ def _do_load_from_file(self): return pdict = {} for p in self._params: - pdict[str(p.name)] = p.value + pdict[str(p._name)] = p param_file = os.path.normcase(os.path.abspath(param_file[0])) print("load from file", param_file) f = open(param_file, "r") + delay = 0.0 for line in f.readlines(): a = line.split() name = a[0] @@ -804,9 +824,10 @@ def _do_load_from_file(self): else: value = a[1] if name in pdict: - s = self.param_as_string(pdict[name]) + s = self.param_as_string(pdict[name]._value) if s != value: - self.save_param(name, pdict[name], value) + self.save_param(name, pdict[name]._value, value, pdict[name]._index, delay) + delay += 0.02 f.close() def _do_execute_opcode(self, opcode):