feat: Add DTR/RTS control for serial ports with GUI toggles
- Assert DTR/RTS after serial port open to prevent wireless modules (HC-12/HC-15/Bluetooth bridges) from entering AT command mode after the unavoidable Linux kernel DTR pulse on every open() call. - Add dtr/rts fields to TransportConfig::Serial (default: false). - Add set_dtr()/set_rts() on SerialTransport, Connection, SessionHandle. - Add SessionCmd::SetDtr/SetRts for live toggling via command channel. - Add DTR/RTS checkboxes to GUI session controls (visible when connected) and to config form for persistence. - Add test_plot_serial.c and test_text_serial.c — standalone C test tools for sending MixedTextPlot frames and plain text over serial. - Update AGENTS.md with build prerequisites, architecture map, testing conventions, env vars, and known quirks.
This commit is contained in:
@@ -64,6 +64,8 @@ pub struct SessionTab {
|
||||
pub plot_view: plot_view::PlotViewState,
|
||||
pub view: View,
|
||||
pub auto_reconnect: bool,
|
||||
pub dtr: bool,
|
||||
pub rts: bool,
|
||||
pub send_input: String,
|
||||
pub send_mode: SendMode,
|
||||
pub append_newline: bool,
|
||||
@@ -183,6 +185,10 @@ impl XserialApp {
|
||||
fn add_session_tab(&mut self, session_config: SessionConfig) {
|
||||
let history_limit = session_config.history_limit;
|
||||
let auto_reconnect = session_config.auto_reconnect;
|
||||
let (dtr, rts) = match &session_config.transport {
|
||||
TransportConfig::Serial { dtr, rts, .. } => (*dtr, *rts),
|
||||
_ => (false, false),
|
||||
};
|
||||
let handle = self.manager.create(session_config.clone());
|
||||
self.tabs.push(SessionTab {
|
||||
id: handle.id(),
|
||||
@@ -194,6 +200,8 @@ impl XserialApp {
|
||||
plot_view: plot_view::PlotViewState::default(),
|
||||
view: View::Text,
|
||||
auto_reconnect,
|
||||
dtr,
|
||||
rts,
|
||||
send_input: String::new(),
|
||||
send_mode: SendMode::Text,
|
||||
append_newline: true,
|
||||
@@ -920,6 +928,22 @@ fn render_session_controls(
|
||||
tab.status = ConnectionStatus::Error(String::from("session not found"));
|
||||
}
|
||||
}
|
||||
if matches!(tab.status, ConnectionStatus::Connected) {
|
||||
let dtr_changed = ui.checkbox(&mut tab.dtr, "DTR").changed();
|
||||
let rts_changed = ui.checkbox(&mut tab.rts, "RTS").changed();
|
||||
if dtr_changed || rts_changed {
|
||||
if let Some(handle) = manager.get(tab.id) {
|
||||
let dtr = tab.dtr;
|
||||
let rts = tab.rts;
|
||||
tokio::spawn(async move {
|
||||
if dtr_changed { let _ = handle.set_dtr(dtr).await; }
|
||||
if rts_changed { let _ = handle.set_rts(rts).await; }
|
||||
});
|
||||
} else {
|
||||
tab.status = ConnectionStatus::Error(String::from("session not found"));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
auto_reconnect_changed
|
||||
}
|
||||
@@ -1062,6 +1086,8 @@ mod tests {
|
||||
parity: SerialParity::None,
|
||||
stop_bits: SerialStopBits::One,
|
||||
flow_control: SerialFlowControl::None,
|
||||
dtr: false,
|
||||
rts: false,
|
||||
}),
|
||||
"Serial COM7"
|
||||
);
|
||||
|
||||
@@ -17,6 +17,8 @@ pub struct ConfigForm {
|
||||
pub serial_parity: SerialParity,
|
||||
pub serial_stop_bits: SerialStopBits,
|
||||
pub serial_flow_control: SerialFlowControl,
|
||||
pub serial_dtr: bool,
|
||||
pub serial_rts: bool,
|
||||
pub addr: String,
|
||||
pub udp_bind: String,
|
||||
pub udp_remote: String,
|
||||
@@ -116,6 +118,8 @@ impl Default for ConfigForm {
|
||||
serial_parity: SerialParity::None,
|
||||
serial_stop_bits: SerialStopBits::One,
|
||||
serial_flow_control: SerialFlowControl::None,
|
||||
serial_dtr: false,
|
||||
serial_rts: false,
|
||||
addr: String::from("127.0.0.1:8080"),
|
||||
udp_bind: String::from("0.0.0.0:9000"),
|
||||
udp_remote: String::new(),
|
||||
@@ -158,6 +162,14 @@ impl ConfigForm {
|
||||
TransportConfig::Serial { flow_control, .. } => *flow_control,
|
||||
_ => SerialFlowControl::None,
|
||||
},
|
||||
serial_dtr: match &config.transport {
|
||||
TransportConfig::Serial { dtr, .. } => *dtr,
|
||||
_ => false,
|
||||
},
|
||||
serial_rts: match &config.transport {
|
||||
TransportConfig::Serial { rts, .. } => *rts,
|
||||
_ => false,
|
||||
},
|
||||
addr: match &config.transport {
|
||||
TransportConfig::Tcp { addr } => addr.clone(),
|
||||
_ => String::from("127.0.0.1:8080"),
|
||||
@@ -190,6 +202,8 @@ impl ConfigForm {
|
||||
parity: self.serial_parity,
|
||||
stop_bits: self.serial_stop_bits,
|
||||
flow_control: self.serial_flow_control,
|
||||
dtr: self.serial_dtr,
|
||||
rts: self.serial_rts,
|
||||
},
|
||||
TransportChoice::Tcp => TransportConfig::Tcp {
|
||||
addr: self.addr.clone(),
|
||||
@@ -484,6 +498,8 @@ fn render_transport_section(ui: &mut Ui, form: &mut ConfigForm) {
|
||||
);
|
||||
});
|
||||
});
|
||||
ui.checkbox(&mut form.serial_dtr, "DTR");
|
||||
ui.checkbox(&mut form.serial_rts, "RTS");
|
||||
}
|
||||
TransportChoice::Tcp => {
|
||||
ui.horizontal(|ui| {
|
||||
|
||||
Reference in New Issue
Block a user