如何在Rust中设置Linux系统时钟?(火腿无线电野外无网场景)
在Rust中通过GPS Dongle为Linux系统设置时钟
一、核心逻辑
Linux下修改系统时钟必须拥有root权限,整体流程分为三步:
- 从GPS dongle的串口读取NMEA格式时间数据(优先取
$GPRMC或$GPZDA语句,包含有效UTC时间) - 解析NMEA字符串为Unix时间戳
- 通过系统调用或系统工具设置系统实时时钟
二、读取并解析GPS时间
使用serialport crate处理串口通信,自己实现轻量NMEA解析(避免依赖复杂库),配合chrono处理时间转换。
依赖配置(Cargo.toml)
[dependencies] serialport = "0.11" chrono = "0.4" nix = "0.26"
读取GPS时间代码
use serialport::SerialPort; use chrono::{NaiveDate, DateTime, Utc}; fn read_gps_utc_timestamp() -> Option<u64> { // 根据你的GPS设备调整串口路径(常见/dev/ttyUSB0或/dev/ttyACM0)和波特率 let mut port = serialport::new("/dev/ttyUSB0", 9600) .timeout(std::time::Duration::from_secs(10)) .open() .expect("无法打开GPS串口"); loop { let mut buffer = vec![0; 1024]; match port.read(buffer.as_mut_slice()) { Ok(n) => { let raw_data = String::from_utf8_lossy(&buffer[..n]); for line in raw_data.lines() { // 处理$GPRMC语句,仅当定位有效(A标识)时提取时间 if line.starts_with("$GPRMC") && line.contains(",A,") { let parts: Vec<&str> = line.split(',').collect(); if parts.len() < 10 { continue; } // 时间格式:HHMMSS.SSS,日期格式:DDMMYY let time_str = parts[1]; let date_str = parts[9]; // 解析时间分量 let hours = time_str[0..2].parse::<u32>().unwrap(); let minutes = time_str[2..4].parse::<u32>().unwrap(); let seconds = time_str[4..6].parse::<u32>().unwrap(); // 解析日期分量 let day = date_str[0..2].parse::<u32>().unwrap(); let month = date_str[2..4].parse::<u32>().unwrap(); let year = 2000 + date_str[4..6].parse::<u32>().unwrap(); // 构建UTC时间并转换为Unix时间戳 let naive_dt = NaiveDate::from_ymd_opt(year as i32, month, day) .and_then(|d| d.and_hms_opt(hours, minutes, seconds))?; let utc_dt: DateTime<Utc> = DateTime::from_utc(naive_dt, Utc); return Some(utc_dt.timestamp() as u64); } } } Err(e) => eprintln!("串口读取错误: {}", e), } } }
三、设置系统时钟
提供两种实现方式,按需选择:
方式1:直接调用系统API(推荐)
通过libc绑定调用clock_settime,性能更高且不依赖外部工具:
use libc::{clock_settime, CLOCK_REALTIME, timespec}; use std::ptr; fn set_system_clock(timestamp: u64) -> Result<(), String> { let ts = timespec { tv_sec: timestamp as i64, tv_nsec: 0, }; unsafe { if clock_settime(CLOCK_REALTIME, &ts) != 0 { return Err(format!("设置时钟失败: {}", std::io::Error::last_os_error())); } } Ok(()) }
需在Cargo.toml添加libc = "0.2"。
方式2:调用系统date命令
逻辑更简单,适合快速验证:
use std::process::Command; fn set_system_clock_via_date(timestamp: u64) -> Result<(), String> { let output = Command::new("date") .arg("-s") .arg(format!("@{}", timestamp)) .output() .map_err(|e| format!("执行date命令失败: {}", e))?; if !output.status.success() { let err_msg = String::from_utf8_lossy(&output.stderr); return Err(format!("date命令执行出错: {}", err_msg)); } Ok(()) }
四、完整整合代码
fn main() { // 检查是否为root权限 if !nix::unistd::Uid::effective().is_root() { eprintln!("错误:必须以root权限运行此程序!"); std::process::exit(1); } println!("正在读取GPS时间..."); match read_gps_utc_timestamp() { Some(timestamp) => { println!("获取到UTC时间戳: {}", timestamp); match set_system_clock(timestamp) { Ok(_) => println!("系统时钟设置成功!"), Err(e) => eprintln!("设置时钟失败: {}", e), } } None => eprintln!("超时未获取到有效GPS时间"), } }
关键注意事项
- 运行程序前必须加
sudo,否则无法修改系统时钟 - 确认GPS dongle已被系统识别,串口路径和波特率与设备匹配(可通过
dmesg或ls /dev/ttyUSB*查看) - GPS输出的是UTC时间,Linux会自动根据本地时区转换显示时间,无需额外处理
- 若GPS输出
$GPZDA语句,可调整解析逻辑适配该语句的字段格式
内容的提问来源于stack exchange,提问作者KC5JKN
相关产品推荐
相关产品推荐

