You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Rust中设置Linux系统时钟?(火腿无线电野外无网场景)

在Rust中通过GPS Dongle为Linux系统设置时钟

一、核心逻辑

Linux下修改系统时钟必须拥有root权限,整体流程分为三步:

  1. 从GPS dongle的串口读取NMEA格式时间数据(优先取$GPRMC或$GPZDA语句,包含有效UTC时间)
  2. 解析NMEA字符串为Unix时间戳
  3. 通过系统调用或系统工具设置系统实时时钟

二、读取并解析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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 09:43:32