mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-04-29 17:36:35 +08:00
fix: 修复 ATX 串口继电器共享设备失效 #233
当 power/reset 配置为同一串口设备时,改为复用同一个串口句柄,避免重复 open 导致 reset 不生效。 Made-with: Cursor
This commit is contained in:
@@ -43,32 +43,93 @@ pub struct AtxController {
|
||||
}
|
||||
|
||||
impl AtxController {
|
||||
async fn init_components(inner: &mut AtxInner) {
|
||||
// Initialize power executor
|
||||
if inner.config.power.is_configured() {
|
||||
let mut executor = AtxKeyExecutor::new(inner.config.power.clone());
|
||||
if let Err(e) = executor.init().await {
|
||||
warn!("Failed to initialize power executor: {}", e);
|
||||
} else {
|
||||
info!(
|
||||
"Power executor initialized: {:?} on {} pin {}",
|
||||
inner.config.power.driver, inner.config.power.device, inner.config.power.pin
|
||||
);
|
||||
inner.power_executor = Some(executor);
|
||||
}
|
||||
}
|
||||
fn should_share_serial_device(power: &AtxKeyConfig, reset: &AtxKeyConfig) -> bool {
|
||||
power.is_configured()
|
||||
&& reset.is_configured()
|
||||
&& power.driver == super::types::AtxDriverType::Serial
|
||||
&& reset.driver == super::types::AtxDriverType::Serial
|
||||
&& !power.device.is_empty()
|
||||
&& power.device == reset.device
|
||||
&& power.baud_rate == reset.baud_rate
|
||||
}
|
||||
|
||||
// Initialize reset executor
|
||||
if inner.config.reset.is_configured() {
|
||||
let mut executor = AtxKeyExecutor::new(inner.config.reset.clone());
|
||||
if let Err(e) = executor.init().await {
|
||||
warn!("Failed to initialize reset executor: {}", e);
|
||||
} else {
|
||||
info!(
|
||||
"Reset executor initialized: {:?} on {} pin {}",
|
||||
inner.config.reset.driver, inner.config.reset.device, inner.config.reset.pin
|
||||
);
|
||||
inner.reset_executor = Some(executor);
|
||||
async fn init_components(inner: &mut AtxInner) {
|
||||
if Self::should_share_serial_device(&inner.config.power, &inner.config.reset) {
|
||||
match AtxKeyExecutor::open_shared_serial(
|
||||
&inner.config.power.device,
|
||||
inner.config.power.baud_rate,
|
||||
) {
|
||||
Ok(shared_serial) => {
|
||||
let mut power_executor = AtxKeyExecutor::new_with_shared_serial(
|
||||
inner.config.power.clone(),
|
||||
shared_serial.clone(),
|
||||
);
|
||||
if let Err(e) = power_executor.init().await {
|
||||
warn!("Failed to initialize power executor: {}", e);
|
||||
} else {
|
||||
info!(
|
||||
"Power executor initialized: {:?} on {} pin {}",
|
||||
inner.config.power.driver,
|
||||
inner.config.power.device,
|
||||
inner.config.power.pin
|
||||
);
|
||||
inner.power_executor = Some(power_executor);
|
||||
}
|
||||
|
||||
let mut reset_executor = AtxKeyExecutor::new_with_shared_serial(
|
||||
inner.config.reset.clone(),
|
||||
shared_serial,
|
||||
);
|
||||
if let Err(e) = reset_executor.init().await {
|
||||
warn!("Failed to initialize reset executor: {}", e);
|
||||
} else {
|
||||
info!(
|
||||
"Reset executor initialized: {:?} on {} pin {}",
|
||||
inner.config.reset.driver,
|
||||
inner.config.reset.device,
|
||||
inner.config.reset.pin
|
||||
);
|
||||
inner.reset_executor = Some(reset_executor);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"Failed to open shared serial device {} for ATX power/reset: {}",
|
||||
inner.config.power.device, e
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Initialize power executor
|
||||
if inner.config.power.is_configured() {
|
||||
let mut executor = AtxKeyExecutor::new(inner.config.power.clone());
|
||||
if let Err(e) = executor.init().await {
|
||||
warn!("Failed to initialize power executor: {}", e);
|
||||
} else {
|
||||
info!(
|
||||
"Power executor initialized: {:?} on {} pin {}",
|
||||
inner.config.power.driver,
|
||||
inner.config.power.device,
|
||||
inner.config.power.pin
|
||||
);
|
||||
inner.power_executor = Some(executor);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize reset executor
|
||||
if inner.config.reset.is_configured() {
|
||||
let mut executor = AtxKeyExecutor::new(inner.config.reset.clone());
|
||||
if let Err(e) = executor.init().await {
|
||||
warn!("Failed to initialize reset executor: {}", e);
|
||||
} else {
|
||||
info!(
|
||||
"Reset executor initialized: {:?} on {} pin {}",
|
||||
inner.config.reset.driver,
|
||||
inner.config.reset.device,
|
||||
inner.config.reset.pin
|
||||
);
|
||||
inner.reset_executor = Some(executor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,3 +323,49 @@ impl AtxController {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::atx::AtxDriverType;
|
||||
|
||||
#[test]
|
||||
fn test_should_share_serial_device_true() {
|
||||
let power = AtxKeyConfig {
|
||||
driver: AtxDriverType::Serial,
|
||||
device: "/dev/ttyUSB0".to_string(),
|
||||
pin: 1,
|
||||
active_level: super::super::types::ActiveLevel::High,
|
||||
baud_rate: 9600,
|
||||
};
|
||||
let reset = AtxKeyConfig {
|
||||
driver: AtxDriverType::Serial,
|
||||
device: "/dev/ttyUSB0".to_string(),
|
||||
pin: 2,
|
||||
active_level: super::super::types::ActiveLevel::High,
|
||||
baud_rate: 9600,
|
||||
};
|
||||
|
||||
assert!(AtxController::should_share_serial_device(&power, &reset));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_should_share_serial_device_false_on_different_baud() {
|
||||
let power = AtxKeyConfig {
|
||||
driver: AtxDriverType::Serial,
|
||||
device: "/dev/ttyUSB0".to_string(),
|
||||
pin: 1,
|
||||
active_level: super::super::types::ActiveLevel::High,
|
||||
baud_rate: 9600,
|
||||
};
|
||||
let reset = AtxKeyConfig {
|
||||
driver: AtxDriverType::Serial,
|
||||
device: "/dev/ttyUSB0".to_string(),
|
||||
pin: 2,
|
||||
active_level: super::super::types::ActiveLevel::High,
|
||||
baud_rate: 115200,
|
||||
};
|
||||
|
||||
assert!(!AtxController::should_share_serial_device(&power, &reset));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use serialport::SerialPort;
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::Write;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Mutex;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
use tokio::time::sleep;
|
||||
use tracing::{debug, info};
|
||||
@@ -16,6 +16,8 @@ use tracing::{debug, info};
|
||||
use super::types::{ActiveLevel, AtxDriverType, AtxKeyConfig};
|
||||
use crate::error::{AppError, Result};
|
||||
|
||||
pub type SharedSerialHandle = Arc<Mutex<Box<dyn SerialPort>>>;
|
||||
|
||||
/// Timing constants for ATX operations
|
||||
pub mod timing {
|
||||
use std::time::Duration;
|
||||
@@ -39,8 +41,8 @@ pub struct AtxKeyExecutor {
|
||||
gpio_handle: Mutex<Option<LineHandle>>,
|
||||
/// Cached USB relay file handle to avoid repeated open/close syscalls
|
||||
usb_relay_handle: Mutex<Option<File>>,
|
||||
/// Cached Serial port handle
|
||||
serial_handle: Mutex<Option<Box<dyn SerialPort>>>,
|
||||
/// Cached Serial port handle (can be shared across power/reset executors)
|
||||
serial_handle: Mutex<Option<SharedSerialHandle>>,
|
||||
initialized: AtomicBool,
|
||||
}
|
||||
|
||||
@@ -56,6 +58,26 @@ impl AtxKeyExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new executor with a pre-opened shared serial handle.
|
||||
pub fn new_with_shared_serial(config: AtxKeyConfig, serial_handle: SharedSerialHandle) -> Self {
|
||||
Self {
|
||||
config,
|
||||
gpio_handle: Mutex::new(None),
|
||||
usb_relay_handle: Mutex::new(None),
|
||||
serial_handle: Mutex::new(Some(serial_handle)),
|
||||
initialized: AtomicBool::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
/// Open a serial relay device and wrap it for shared use.
|
||||
pub fn open_shared_serial(device: &str, baud_rate: u32) -> Result<SharedSerialHandle> {
|
||||
let port = serialport::new(device, baud_rate)
|
||||
.timeout(Duration::from_millis(100))
|
||||
.open()
|
||||
.map_err(|e| AppError::Internal(format!("Serial port open failed: {}", e)))?;
|
||||
Ok(Arc::new(Mutex::new(port)))
|
||||
}
|
||||
|
||||
/// Check if this executor is configured
|
||||
pub fn is_configured(&self) -> bool {
|
||||
self.config.is_configured()
|
||||
@@ -181,14 +203,11 @@ impl AtxKeyExecutor {
|
||||
self.config.device, self.config.pin
|
||||
);
|
||||
|
||||
let baud_rate = self.config.baud_rate;
|
||||
|
||||
let port = serialport::new(&self.config.device, baud_rate)
|
||||
.timeout(Duration::from_millis(100))
|
||||
.open()
|
||||
.map_err(|e| AppError::Internal(format!("Serial port open failed: {}", e)))?;
|
||||
|
||||
*self.serial_handle.lock().unwrap() = Some(port);
|
||||
let existing_handle = self.serial_handle.lock().unwrap().as_ref().cloned();
|
||||
if existing_handle.is_none() {
|
||||
let shared = Self::open_shared_serial(&self.config.device, self.config.baud_rate)?;
|
||||
*self.serial_handle.lock().unwrap() = Some(shared);
|
||||
}
|
||||
|
||||
// Ensure relay is off initially
|
||||
self.send_serial_relay_command(false)?;
|
||||
@@ -337,10 +356,14 @@ impl AtxKeyExecutor {
|
||||
// OFF: A0 01 00 A1
|
||||
let cmd = [0xA0, channel, state, checksum];
|
||||
|
||||
let mut guard = self.serial_handle.lock().unwrap();
|
||||
let port = guard
|
||||
.as_mut()
|
||||
let serial_handle = self
|
||||
.serial_handle
|
||||
.lock()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.ok_or_else(|| AppError::Internal("Serial relay not initialized".to_string()))?;
|
||||
let mut port = serial_handle.lock().unwrap();
|
||||
|
||||
port.write_all(&cmd)
|
||||
.map_err(|e| AppError::Internal(format!("Serial relay write failed: {}", e)))?;
|
||||
|
||||
Reference in New Issue
Block a user