diff --git a/console_backend/src/common_constants.rs b/console_backend/src/common_constants.rs index c9c90ea0b..74a767fa2 100644 --- a/console_backend/src/common_constants.rs +++ b/console_backend/src/common_constants.rs @@ -269,6 +269,10 @@ pub enum Keys { NETWORK_INFO, #[strum(serialize = "IP_ADDRESS")] IP_ADDRESS, + #[strum(serialize = "RECOMMENDED_INS_SETTINGS")] + RECOMMENDED_INS_SETTINGS, + #[strum(serialize = "NEW_INS_CONFIRMATON")] + NEW_INS_CONFIRMATON, #[strum(serialize = "ANTENNA_STATUS")] ANTENNA_STATUS, } diff --git a/console_backend/src/server_recv_thread.rs b/console_backend/src/server_recv_thread.rs index da24dc531..945032d4a 100644 --- a/console_backend/src/server_recv_thread.rs +++ b/console_backend/src/server_recv_thread.rs @@ -347,6 +347,9 @@ pub fn server_recv_thread( port, }) } + m::message::ConfirmInsChange(Ok(_)) => { + shared_state_clone.set_settings_confirm_ins_change(true); + } _ => { error!("unknown message from front-end"); } diff --git a/console_backend/src/settings_tab.rs b/console_backend/src/settings_tab.rs index 8d6461a2b..92fb38f57 100644 --- a/console_backend/src/settings_tab.rs +++ b/console_backend/src/settings_tab.rs @@ -6,6 +6,7 @@ use std::path::Path; use anyhow::anyhow; use capnp::message::Builder; use ini::Ini; +use lazy_static::lazy_static; use log::{debug, error, warn}; use parking_lot::{MappedMutexGuard, Mutex, MutexGuard}; use sbp::link::Link; @@ -20,6 +21,15 @@ use crate::utils::*; const FIRMWARE_VERSION_SETTING_KEY: &str = "firmware_version"; const DGNSS_SOLUTION_MODE_SETTING_KEY: &str = "dgnss_solution_mode"; +lazy_static! { + static ref RECOMMENDED_INS_SETTINGS: [(&'static str, &'static str, SettingValue); 4] = [ + ("imu", "imu_raw_output", SettingValue::Boolean(true)), + ("imu", "gyro_range", SettingValue::String("125".to_string())), + ("imu", "acc_range", SettingValue::String("8g".to_string())), + ("imu", "imu_rate", SettingValue::String("100".to_string())), + ]; +} + pub struct SettingsTab<'link, S> { client_sender: S, shared_state: SharedState, @@ -67,7 +77,7 @@ impl<'link, S: CapnProtoSender> SettingsTab<'link, S> { }; } if settings_state.reset { - if let Err(e) = self.reset() { + if let Err(e) = self.reset(true) { error!("Issue resetting settings {}", e); }; } @@ -76,6 +86,11 @@ impl<'link, S: CapnProtoSender> SettingsTab<'link, S> { error!("Issue saving settings, {}", e); }; } + if settings_state.confirm_ins_change { + if let Err(e) = self.confirm_ins_change() { + error!("Issue confirming INS change, {}", e); + }; + } } pub fn refresh(&mut self) { @@ -138,10 +153,12 @@ impl<'link, S: CapnProtoSender> SettingsTab<'link, S> { .send_data(serialize_capnproto_builder(builder)); } - pub fn reset(&self) -> Result<()> { + pub fn reset(&self, reset_settings: bool) -> Result<()> { + let flags = if reset_settings { 1 } else { 0 }; + self.msg_sender.send( MsgReset { - flags: 1, + flags, sender_id: None, } .into(), @@ -155,6 +172,87 @@ impl<'link, S: CapnProtoSender> SettingsTab<'link, S> { Ok(()) } + pub fn confirm_ins_change(&mut self) -> Result<()> { + let ins_mode = self + .settings + .get("ins", "output_mode")? + .value + .as_ref() + .ok_or_else(|| anyhow!("setting not found"))?; + + let ins_on = ins_mode != &SettingValue::String("Disabled".to_string()); + + if ins_on { + let recommended_settings = self.get_recommended_ins_setting_changes()?; + + for recommendation in recommended_settings { + // todo(DEVINFRA-570): Remove below line when libsettings-rs + // returns "True"/"False" for bools + let value = if &recommendation.3 == "true" { + "True" + } else { + &recommendation.3 + }; + self.write_setting(&recommendation.0, &recommendation.1, value)?; + } + } + + self.save()?; + self.reset(false)?; + + Ok(()) + } + + pub fn get_recommended_ins_setting_changes( + &self, + ) -> Result> { + let client = self.client(); + + let mut recommended_changes = vec![]; + + for setting in RECOMMENDED_INS_SETTINGS.iter() { + let value = client + .read_setting(setting.0, setting.1) + .ok_or_else(|| anyhow!("setting not found"))??; + if value != setting.2 { + recommended_changes.push(( + setting.0.to_string(), + setting.1.to_string(), + value.to_string(), + setting.2.to_string(), + )); + } + } + + Ok(recommended_changes) + } + + pub fn send_ins_change_response(&mut self, output_mode: &str) -> Result<()> { + let mut builder = Builder::new_default(); + let msg = builder.init_root::(); + let mut ins_resp = msg.init_ins_settings_change_response(); + + if output_mode != "Disabled" { + let recommendations = self.get_recommended_ins_setting_changes()?; + let mut recommended_entries = ins_resp + .reborrow() + .init_recommended_settings(recommendations.len() as u32); + + for (i, recommendation) in recommendations.iter().enumerate() { + let mut entry = recommended_entries.reborrow().get(i as u32); + entry.set_setting_group(&recommendation.0); + entry.set_setting_name(&recommendation.1); + entry.set_current_value(&recommendation.2); + entry.set_recommended_value(&recommendation.3); + } + } + + self.client_sender + .send_data(serialize_capnproto_builder(builder)); + + Ok(()) + } + pub fn write_setting(&mut self, group: &str, name: &str, value: &str) -> Result<()> { let setting = self.settings.get(group, name)?; @@ -181,6 +279,10 @@ impl<'link, S: CapnProtoSender> SettingsTab<'link, S> { .set(group, name, SettingValue::String(value.to_string()))?; } + if group == "ins" && name == "output_mode" { + self.send_ins_change_response(value)?; + } + self.send_table_data(); Ok(()) diff --git a/console_backend/src/shared_state.rs b/console_backend/src/shared_state.rs index e967dbb38..b94a5e881 100644 --- a/console_backend/src/shared_state.rs +++ b/console_backend/src/shared_state.rs @@ -248,6 +248,10 @@ impl SharedState { let mut shared_data = self.lock().expect(SHARED_STATE_LOCK_MUTEX_FAILURE); shared_data.settings_tab.reset = set_to; } + pub fn set_settings_confirm_ins_change(&self, set_to: bool) { + let mut shared_data = self.lock().expect(SHARED_STATE_LOCK_MUTEX_FAILURE); + shared_data.settings_tab.confirm_ins_change = set_to; + } pub fn set_export_settings(&self, path: Option) { let mut shared_data = self.lock().expect(SHARED_STATE_LOCK_MUTEX_FAILURE); shared_data.settings_tab.export = path; @@ -546,6 +550,7 @@ pub struct SettingsTabState { pub refresh: bool, pub reset: bool, pub save: bool, + pub confirm_ins_change: bool, pub export: Option, pub import: Option, pub write: Option, @@ -564,6 +569,7 @@ impl SettingsTabState { self.refresh || self.reset || self.save + || self.confirm_ins_change || self.export.is_some() || self.import.is_some() || self.write.is_some() diff --git a/resources/Constants/Constants.qml b/resources/Constants/Constants.qml index 2363c6e17..e307ea1d7 100644 --- a/resources/Constants/Constants.qml +++ b/resources/Constants/Constants.qml @@ -29,6 +29,7 @@ QtObject { property QtObject baselineTable property QtObject settingsTab property QtObject settingsTable + property QtObject insSettingsPopup property QtObject solutionPosition property QtObject solutionTable property QtObject solutionVelocity @@ -514,4 +515,11 @@ QtObject { readonly property string zoomAllButtonUrl: "qrc:///zoom-all.svg" } + insSettingsPopup: QtObject { + readonly property var columnHeaders: ["Name", "Current Value", "Recommended Value"] + readonly property int dialogWidth: 550 + readonly property int columnSpacing: 10 + readonly property int tableHeight: 150 + } + } diff --git a/resources/SettingsTab.qml b/resources/SettingsTab.qml index 489062dd2..6dcc0f91f 100644 --- a/resources/SettingsTab.qml +++ b/resources/SettingsTab.qml @@ -58,6 +58,11 @@ Item { } settings_tab_model.clear_import_status(settingsTabData); } + if (settingsTabData.new_ins_confirmation) { + insSettingsPopup.settings = settingsTabData.recommended_ins_settings; + insSettingsPopup.insPopup.open(); + settings_tab_model.clear_new_ins_confirmation(settingsTabData); + } } } @@ -111,6 +116,10 @@ Item { onYes: data_model.settings_save_request() } + SettingsTabComponents.InsSettingsPopup { + id: insSettingsPopup + } + MessageDialog { id: importFailure diff --git a/resources/SettingsTabComponents/InsSettingsPopup.qml b/resources/SettingsTabComponents/InsSettingsPopup.qml new file mode 100644 index 000000000..49d3d1a1e --- /dev/null +++ b/resources/SettingsTabComponents/InsSettingsPopup.qml @@ -0,0 +1,240 @@ +import "../Constants" +import Qt.labs.qmlmodels 1.0 +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import SwiftConsole 1.0 + +Item { + property variant columnWidths: [layout.width / 3, layout.width / 3, layout.width / 3] + property real mouse_x: 0 + property int selectedRow: -1 + property alias insPopup: dialog + property variant settings: [] + + function settingsChangeConfirmText() { + let text = ""; + text += "In order for the \"Ins Output Mode\" setting to take effect, it is necessary to save the current settings to device "; + text += "flash and then power cycle your device.\n\n"; + if (settings.length > 0) { + text += "Additionally, in order to enable INS output, it is necessary to enable and configure the IMU. "; + text += "The current settings indicate that the IMU raw ouptut is currently disabled and/or improperly "; + text += "configured as shown in the table below."; + } + return text; + } + + function settingBottomText() { + let text = ""; + text += "Choose \"Ok\" to"; + if (settings.length > 0) + text += " allow the console to change the above settings on your device to help enable INS output and then"; + + text += " immediately save settings to device flash and send the software reset command."; + text += " The software reset will temporarily interrupt the console's connection to the device but it "; + text += " will recover on its own. "; + return text; + } + + Dialog { + id: dialog + + parent: Overlay.overlay + title: "Confirm Inertial Navigation Change?" + onAccepted: { + data_model.confirm_ins_change(); + } + standardButtons: Dialog.Ok | Dialog.Cancel + width: Constants.insSettingsPopup.dialogWidth + anchors.centerIn: parent + modal: true + closePolicy: Popup.CloseOnEscape + focus: true + + contentItem: Column { + id: layout + + width: parent.width + spacing: Constants.insSettingsPopup.columnSpacing + + Text { + text: settingsChangeConfirmText() + verticalAlignment: Qt.AlignVCenter + elide: Text.ElideRight + clip: true + font.family: Constants.genericTable.fontFamily + font.pointSize: Constants.largePointSize + wrapMode: Text.Wrap + width: parent.width + } + + ColumnLayout { + spacing: 0 + width: parent.width + height: Constants.insSettingsPopup.tableHeight + visible: settings.length > 0 + + HorizontalHeaderView { + id: horizontalHeader + + interactive: false + syncView: tableView + z: Constants.genericTable.headerZOffset + + delegate: Rectangle { + implicitWidth: columnWidths[index] + implicitHeight: Constants.genericTable.cellHeight + border.color: Constants.genericTable.borderColor + + Text { + width: parent.width + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + text: tableView.model.columns[index].display + elide: Text.ElideRight + clip: true + font.family: Constants.genericTable.fontFamily + } + + MouseArea { + width: Constants.genericTable.mouseAreaResizeWidth + height: parent.height + anchors.right: parent.right + cursorShape: Qt.SizeHorCursor + onPressed: { + mouse_x = mouseX; + } + onPositionChanged: { + if (pressed) { + let oldcols = columnWidths.slice(); + var delta_x = (mouseX - mouse_x); + columnWidths[index] += delta_x; + columnWidths[(index + 1) % 3] -= delta_x; + tableView.forceLayout(); + } + } + } + + gradient: Gradient { + GradientStop { + position: 0 + color: Constants.genericTable.cellColor + } + + GradientStop { + position: 1 + color: Constants.genericTable.gradientColor + } + + } + + } + + } + + TableView { + id: tableView + + columnSpacing: -1 + rowSpacing: -1 + columnWidthProvider: function(column) { + return columnWidths[column]; + } + reuseItems: true + boundsBehavior: Flickable.StopAtBounds + height: parent.height - horizontalHeader.height + width: parent.width + + ScrollBar.horizontal: ScrollBar { + } + + ScrollBar.vertical: ScrollBar { + } + + model: TableModel { + id: tableModel + + rows: [] + + TableModelColumn { + display: Constants.insSettingsPopup.columnHeaders[0] + } + + TableModelColumn { + display: Constants.insSettingsPopup.columnHeaders[1] + } + + TableModelColumn { + display: Constants.insSettingsPopup.columnHeaders[2] + } + + } + + delegate: Rectangle { + implicitHeight: Constants.genericTable.cellHeight + implicitWidth: tableView.columnWidthProvider(column) + border.color: Constants.genericTable.borderColor + color: row == selectedRow ? Constants.genericTable.cellHighlightedColor : Constants.genericTable.cellColor + + Text { + width: parent.width + horizontalAlignment: Text.AlignLeft + clip: true + font.family: Constants.genericTable.fontFamily + font.pointSize: Constants.largePointSize + text: model.display + elide: Text.ElideRight + padding: Constants.genericTable.padding + } + + MouseArea { + width: parent.width + height: parent.height + anchors.centerIn: parent + onPressed: { + if (selectedRow == row) + selectedRow = -1; + else + selectedRow = row; + } + } + + } + + } + + } + + Text { + text: settingBottomText() + verticalAlignment: Qt.AlignVCenter + elide: Text.ElideRight + clip: true + font.family: Constants.genericTable.fontFamily + font.pointSize: Constants.largePointSize + wrapMode: Text.Wrap + width: parent.width + } + + } + + } + + Timer { + interval: Utils.hzToMilliseconds(Constants.staticTableTimerIntervalRate) + running: true + repeat: true + onTriggered: { + for (var idx in settings) { + var new_row = { + }; + new_row[Constants.insSettingsPopup.columnHeaders[0]] = settings[idx][0]; + new_row[Constants.insSettingsPopup.columnHeaders[1]] = settings[idx][1]; + new_row[Constants.insSettingsPopup.columnHeaders[2]] = settings[idx][2]; + tableView.model.setRow(idx, new_row); + } + } + } + +} diff --git a/resources/console_resources.qrc b/resources/console_resources.qrc index 9b89c81f0..46b0b50bf 100644 --- a/resources/console_resources.qrc +++ b/resources/console_resources.qrc @@ -38,6 +38,7 @@ BaselineTabComponents/BaselinePlot.qml SettingsTab.qml SettingsTabComponents/SettingsTable.qml + SettingsTabComponents/InsSettingsPopup.qml SolutionPlotCommon/SolutionPlotLoop.js BaselineTabComponents/BaselineTable.qml SolutionTab.qml diff --git a/src/main/python/constants.py b/src/main/python/constants.py index 32302c51c..1e2c617e4 100644 --- a/src/main/python/constants.py +++ b/src/main/python/constants.py @@ -127,6 +127,8 @@ class Keys(str, Enum): RUNNING = "RUNNING" NETWORK_INFO = "NETWORK_INFO" IP_ADDRESS = "IP_ADDRESS" + RECOMMENDED_INS_SETTINGS = "RECOMMENDED_INS_SETTINGS" + NEW_INS_CONFIRMATON = "NEW_INS_CONFIRMATON" ANTENNA_STATUS = "ANTENNA_STATUS" diff --git a/src/main/python/main.py b/src/main/python/main.py index 780d4d145..2a2544ced 100644 --- a/src/main/python/main.py +++ b/src/main/python/main.py @@ -405,6 +405,12 @@ def receive_messages(app_, backend, messages): SETTINGS_TABLE[Keys.ENTRIES][:] = settings_rows_to_json(m.settingsTableStatus.data) elif m.which == Message.Union.SettingsImportResponse: SETTINGS_TAB[Keys.IMPORT_STATUS] = m.settingsImportResponse.status + elif m.which == Message.Union.InsSettingsChangeResponse: + SETTINGS_TAB[Keys.RECOMMENDED_INS_SETTINGS][:] = [ + [entry.settingName, entry.currentValue, entry.recommendedValue] + for entry in m.insSettingsChangeResponse.recommendedSettings + ] + SETTINGS_TAB[Keys.NEW_INS_CONFIRMATON] = True else: pass @@ -530,6 +536,14 @@ def reset_device(self) -> None: buffer = msg.to_bytes() self.endpoint.send_message(buffer) + @Slot() # type: ignore + def confirm_ins_change(self) -> None: + Message = self.messages.Message + msg = self.messages.Message() + msg.confirmInsChange = msg.init(Message.Union.ConfirmInsChange) + buffer = msg.to_bytes() + self.endpoint.send_message(buffer) + @Slot(bool) # type: ignore def pause(self, pause_: bool) -> None: Message = self.messages.Message diff --git a/src/main/python/settings_tab.py b/src/main/python/settings_tab.py index f8012fccf..ec3be3f36 100644 --- a/src/main/python/settings_tab.py +++ b/src/main/python/settings_tab.py @@ -10,6 +10,8 @@ SETTINGS_TAB: Dict[str, Any] = { Keys.IMPORT_STATUS: None, + Keys.RECOMMENDED_INS_SETTINGS: [], + Keys.NEW_INS_CONFIRMATON: False, } @@ -21,6 +23,8 @@ class SettingsTabData(QObject): _import_status: str = "" + _recommended_ins_settings: List[List[Any]] = [] + _new_ins_confirmation: bool = False def get_import_status(self) -> str: return self._import_status @@ -30,11 +34,31 @@ def set_import_status(self, import_status: str) -> None: import_status = Property(str, get_import_status, set_import_status) + def get_recommended_ins_settings(self) -> List[List[str]]: + return self._recommended_ins_settings + + def set_recommended_ins_settings(self, recommended_ins_settings: List[List[str]]) -> None: + self._recommended_ins_settings = recommended_ins_settings + + recommended_ins_settings = Property( + QTKeys.QVARIANTLIST, get_recommended_ins_settings, set_recommended_ins_settings # type: ignore + ) + + def set_new_ins_confirmation(self, new_ins_confirmation: bool) -> None: + self._new_ins_confirmation = new_ins_confirmation + + def get_new_ins_confirmation(self) -> bool: + return self._new_ins_confirmation + + new_ins_confirmation = Property(bool, get_new_ins_confirmation, set_new_ins_confirmation) + class SettingsTabModel(QObject): # pylint: disable=too-few-public-methods @Slot(SettingsTabData) # type: ignore def fill_data(self, cp: SettingsTabData) -> SettingsTabData: # pylint:disable=no-self-use cp.set_import_status(SETTINGS_TAB[Keys.IMPORT_STATUS]) + cp.set_recommended_ins_settings(SETTINGS_TAB[Keys.RECOMMENDED_INS_SETTINGS]) + cp.set_new_ins_confirmation(SETTINGS_TAB[Keys.NEW_INS_CONFIRMATON]) return cp @Slot(SettingsTabData) # type: ignore @@ -43,6 +67,12 @@ def clear_import_status(self, cp: SettingsTabData) -> SettingsTabData: # pylint self.fill_data(cp) return cp + @Slot(SettingsTabData) # type: ignore + def clear_new_ins_confirmation(self, cp: SettingsTabData) -> SettingsTabData: # pylint:disable=no-self-use + SETTINGS_TAB[Keys.NEW_INS_CONFIRMATON] = False + self.fill_data(cp) + return cp + class SettingsTableEntries(QObject): diff --git a/src/main/resources/base/console_backend.capnp b/src/main/resources/base/console_backend.capnp index c56950c85..c715cfe7d 100644 --- a/src/main/resources/base/console_backend.capnp +++ b/src/main/resources/base/console_backend.capnp @@ -407,6 +407,21 @@ struct Status { text @0 :Text; } +struct RecommendedInsSettingsRow { + settingGroup @0 :Text; + settingName @1 :Text; + currentValue @2 :Text; + recommendedValue @3 :Text; +} + +struct InsSettingsChangeResponse { + recommendedSettings @0 :List(RecommendedInsSettingsRow); +} + +struct ConfirmInsChange { + confirm @0 :Void = void; +} + struct Message { union { solutionVelocityStatus @0 :SolutionVelocityStatus; @@ -458,5 +473,7 @@ struct Message { advancedNetworkingStatus @46 :AdvancedNetworkingStatus; networkState @47 :NetworkState; advancedNetworkingStatusFront @48 :AdvancedNetworkingStatusFront; + insSettingsChangeResponse @49 : InsSettingsChangeResponse; + confirmInsChange @50 : ConfirmInsChange; } }