幽冥大陆(二十六)Rust 语言智慧农业电子秤读取——东方仙盟炼气期

代码

use eframe::egui;
use serialport::SerialPort;
use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;

// 应用状态结构体
struct ScaleApp {
    port_name: String,
    title: String,
    current_weight: String,
    status: String,
    stable_weight: Option<f64>,
    serial_port: Option<Box<dyn SerialPort>>,
    recent_weights: VecDeque<f64>,
    is_running: bool,
    // 用于跨线程通信的共享状态
    shared_state: Arc<Mutex<SharedState>>,
}

// 跨线程共享的状态
struct SharedState {
    current_weight: Option<f64>,
    stable_weight: Option<f64>,
    status: String,
    is_stable: bool,
}

impl ScaleApp {
    fn new(port_name: String, title: String) -> Self {
        let shared_state = Arc::new(Mutex::new(SharedState {
            current_weight: None,
            stable_weight: None,
            status: "等待数据稳定(连续6次相同值)...".to_string(),
            is_stable: false,
        }));

        Self {
            port_name: port_name.clone(),
            title,
            current_weight: "-- kg".to_string(),
            status: "等待数据稳定(连续6次相同值)...".to_string(),
            stable_weight: None,
            serial_port: None,
            recent_weights: VecDeque::with_capacity(6),
            is_running: true,
            shared_state: shared_state.clone(),
        }
    }

    // 初始化串口并启动读取线程
    fn init_serial(&mut self, ctx: &egui::Context) {
        let port_name = self.port_name.clone();
        let shared_state = self.shared_state.clone();
        let ctx = ctx.clone();

        // 尝试打开串口
        match serialport::new(port_name.clone(), 9600)
            .data_bits(serialport::DataBits::Eight)
            .parity(serialport::Parity::None)
            .stop_bits(serialport::StopBits::One)
            .timeout(Duration::from_millis(500))
            .open()
        {
            Ok(port) => {
                self.serial_port = Some(port);
                let mut port = self.serial_port.take().unwrap();

                // 更新状态
                let mut state = shared_state.lock().unwrap();
                state.status = format!("串口 {} 已打开,等待数据...", port_name);
                drop(state);

                // 启动串口读取线程
                thread::spawn(move || {
                    let mut buffer = String::new();
                    while !shared_state.lock().unwrap().is_stable {
                        match port.read_to_string(&mut buffer) {
                            Ok(_n) => {
                                if !buffer.is_empty() {
                                    let data = buffer.trim().to_string();
                                    buffer.clear();

                                    if let Some(weight) = try_parse_weight(&data) {
                                        let mut state = shared_state.lock().unwrap();
                                        state.current_weight = Some(weight);
                                        
                                        // 检查是否稳定
                                        let mut recent_weights = VecDeque::with_capacity(6);
                                        recent_weights.push_back(weight);
                                        
                                        // 保持最近6个数据
                                        while recent_weights.len() > 6 {
                                            recent_weights.pop_front();
                                        }

                                        // 检查是否连续6个相同值
                                        if recent_weights.len() == 6 && all_equal(&recent_weights) {
                                            state.stable_weight = Some(weight);
                                            state.is_stable = true;
                                            state.status = format!("数据稳定:{:.3} kg,自动确认中...", weight);
                                            ctx.request_repaint(); // 触发UI更新
                                            break;
                                        }
                                    }
                                }
                                ctx.request_repaint(); // 触发UI更新
                            }
                            Err(e) => {
                                let mut state = shared_state.lock().unwrap();
                                state.status = format!("数据读取错误:{}", e);
                                ctx.request_repaint();
                                break;
                            }
                        }
                    }
                });
            }
            Err(e) => {
                let mut state = shared_state.lock().unwrap();
                state.status = format!("串口 {} 打开失败:{}", port_name, e);
            }
        }
    }
}

// 解析重量数据
fn try_parse_weight(data: &str) -> Option<f64> {
    // 提取数字部分(支持整数、小数)
    let numeric_part = data
        .chars()
        .filter(|c| c.is_ascii_digit() || *c == '.' || *c == '+' || *c == '-')
        .collect::<String>();
    
    numeric_part.parse().ok()
}

// 检查队列中所有元素是否相等(考虑浮点数精度)
fn all_equal(weights: &VecDeque<f64>) -> bool {
    if weights.is_empty() {
        return false;
    }
    let first = weights[0];
    weights.iter().all(|&w| (w - first).abs() < 0.001)
}

impl eframe::App for ScaleApp {
    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        // 从共享状态更新UI
        let state = self.shared_state.lock().unwrap();
        self.current_weight = if let Some(w) = state.current_weight {
            format!("{:.3} kg", w)
        } else {
            "-- kg".to_string()
        };
        self.status = state.status.clone();
        self.stable_weight = state.stable_weight;
        
        // 如果数据稳定,延迟关闭窗口
        if state.is_stable {
            thread::spawn({
                let frame = _frame.clone();
                move || {
                    thread::sleep(Duration::from_secs(1));
                    frame.close();
                }
            });
        }
        drop(state);

        // 构建UI
        egui::CentralPanel::default().show(ctx, |ui| {
            ui.heading(&self.title);
            ui.add_space(20.0);

            // 重量显示
            ui.horizontal(|ui| {
                ui.label("当前重量:");
                ui.label(&self.current_weight);
            });

            ui.add_space(20.0);

            // 状态显示
            ui.label(&self.status);

            ui.add_space(40.0);

            // 取消按钮
            if ui.button("取消").clicked() {
                self.is_running = false;
                _frame.close();
            }
        });
    }

    fn on_startup(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        // 启动时初始化串口
        self.init_serial(ctx);
    }

    fn on_close_event(&mut self) -> bool {
        // 关闭时清理资源
        self.is_running = false;
        true
    }
}

// 运行应用
fn run_app(port_name: String, title: String) -> Option<f64> {
    let options = eframe::NativeOptions {
        initial_window_size: Some(egui::vec2(300.0, 200.0)),
        resizable: false,
        ..Default::default()
    };

    let mut stable_weight = None;
    eframe::run_native(
        &title,
        options,
        Box::new(|_cc| {
            Box::new(ScaleApp::new(port_name.clone(), title.clone()))
        }),
    );

    // 获取最终稳定重量
    let state = Arc::try_unwrap(ScaleApp::new(port_name, title).shared_state)
        .unwrap()
        .into_inner()
        .unwrap();
    state.stable_weight
}

fn main() -> Result<(), eframe::Error> {
    // 示例:使用COM3端口和"生鲜电子秤采集"标题
    let port_name = "COM3".to_string();
    let title = "生鲜电子秤采集".to_string();
    
    if let Some(weight) = run_app(port_name.clone(), title.clone()) {
        // 显示结果对话框(简化版)
        println!("最终稳定重量:{:.3} kg", weight);
        
        // 实际应用中可以使用系统消息框
        // 这里使用eframe再显示一个结果窗口
        let result_title = "采集完成".to_string();
        let result_message = format!("最终稳定重量:{:.3} kg", weight);
        
        eframe::run_native(
            &result_title,
            eframe::NativeOptions {
                initial_window_size: Some(egui::vec2(300.0, 150.0)),
                resizable: false,
                ..Default::default()
            },
            Box::new(|_cc| {
                Box::new(ResultApp { message: result_message })
            }),
        )?;
    }
    
    Ok(())
}

// 结果显示窗口
struct ResultApp {
    message: String,
}

impl eframe::App for ResultApp {
    fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
        egui::CentralPanel::default().show(ctx, |ui| {
            ui.heading("采集完成");
            ui.add_space(20.0);
            ui.label(&self.message);
            ui.add_space(30.0);
            if ui.button("确定").clicked() {
                frame.close();
            }
        });
    }
}

实现说明

  1. 依赖库

    • serialport:处理串口通信
    • eframe 和 egui:构建跨平台 GUI 界面添加到Cargo.toml

    toml

    [dependencies]
    eframe = "0.22"
    egui = "0.22"
    serialport = "0.11"
    
  2. 功能特点

    • 与原 C# 版本功能完全一致,包括串口通信、数据解析和稳定性检测
    • 使用egui构建简洁的用户界面,支持跨平台运行(Windows/macOS/Linux)
    • 采用线程安全的共享状态(Arc<Mutex>)处理串口数据与 UI 交互
    • 考虑浮点数精度问题,使用小范围误差(0.001)判断相等性
    • 完整的错误处理和资源释放机制
  3. 使用方法

    1. 创建新的 Rust 项目并添加依赖
    2. 将代码保存为src/main.rs
    3. 根据实际设备修改main函数中的串口号(默认 "COM3")
    4. 运行:cargo run
  4. 注意事项

    • 数据解析逻辑(try_parse_weight函数)可能需要根据电子秤的实际协议调整
    • 串口参数(波特率等)需与电子秤匹配
    • Linux 系统可能需要额外权限才能访问串口(添加用户到dialout组)
    • macOS 系统串口路径格式为/dev/tty.usbserial-*

该实现充分利用了 Rust 的内存安全特性和现代 GUI 库,同时保持了与原程序一致的功能和用户体验

Rust 电子秤读取:精准计量的新力量

在数字化计量的时代,Rust 语言凭借其独特的优势,为电子秤数据读取带来了新的活力与可能性。通过对上述代码的分析,我们可以深入了解 Rust 在电子秤读取领域的重要意义、广泛应用场景以及初学者如何入手。

Rust 电子秤读取的重要意义

  1. 内存安全与性能高效:Rust 的所有权系统和借用规则确保了内存安全,避免了悬垂指针、空指针解引用等常见的内存错误。在电子秤数据读取场景中,这意味着程序能够稳定运行,不会因内存问题导致崩溃,保证了数据读取的连续性和准确性。同时,Rust 的零成本抽象特性使得生成的代码高效运行,能够快速处理电子秤传输的数据,满足实时性要求。
  2. 并发编程优势:在实际应用中,电子秤数据读取可能需要与其他任务并发执行,如 UI 更新、数据存储等。Rust 的标准库提供了强大的并发编程支持,通过std::sync模块,代码实现了线程安全的数据共享和同步。例如,Arc<Mutex<SharedState>>用于在主线程和串口读取线程之间安全地共享数据,确保数据一致性,提高了系统的整体性能和响应能力。
  3. 跨平台兼容性:Rust 具有良好的跨平台特性,可以在不同的操作系统上运行电子秤读取程序,无论是 Windows、Linux 还是 macOS。这使得开发者能够轻松地将电子秤数据读取应用部署到各种环境中,满足不同用户的需求。

10 个应用场景

  1. 工业生产质量控制:在汽车制造、电子设备生产等工业领域,电子秤用于测量零部件重量。Rust 程序读取电子秤数据,实时监控产品重量是否符合标准,及时发现生产过程中的质量问题,保证产品质量的稳定性。例如,汽车发动机的零部件生产中,通过精确的重量测量来确保产品的一致性。
  2. 商业零售结算:在超市、便利店等零售场所,电子秤与 Rust 程序结合,快速准确地获取商品重量,自动计算价格并生成小票。这提高了结算效率,减少人工计算误差,为顾客提供便捷的购物体验。
  3. 物流包裹称重与计费:物流行业利用电子秤称量包裹重量,Rust 程序读取数据并与物流管理系统集成,自动计算运费、更新库存信息。这有助于优化物流流程,提高物流企业的运营效率和管理水平。
  4. 食品加工配料控制:食品加工厂中,精确的配料称重是保证产品质量和口味一致性的关键。Rust 读取电子秤数据,按照预设配方准确控制原材料用量,实现自动化生产流程,确保每批次产品的质量稳定。
  5. 制药行业药品称量:药品生产对重量精度要求极高,Rust 电子秤读取程序用于对药品原料、中间体和成品进行精确称重,严格控制药品剂量,保障药品质量和安全性,符合药品生产的严格规范。
  6. 科研实验数据采集:在科研实验室中,电子秤用于精确称量化学试剂、生物样本等。Rust 程序读取重量数据并自动记录,提高实验数据的准确性和记录效率,为科研工作提供可靠的数据支持。
  7. 珠宝鉴定与交易:珠宝的价值与重量密切相关,高精度电子秤结合 Rust 程序读取重量数据,为珠宝鉴定和定价提供准确依据,确保珠宝交易的公平公正。
  8. 农业农产品收购与分级:在农产品收购环节,电子秤称重结合 Rust 程序,快速统计农产品重量,根据重量和质量进行分级定价,实现农产品收购的自动化和规范化管理。
  9. 环保废弃物回收计量:环保回收企业利用电子秤对回收的废弃物进行称重,Rust 程序读取数据,统计回收量,为废弃物处理和资源再利用提供数据支持,同时便于环保监管。
  10. 医疗保健体重监测:在医院、健身房等场所,电子秤用于测量人体体重。Rust 程序读取重量数据,与医疗信息系统或健身管理系统集成,方便医生或教练跟踪患者或会员的体重变化,提供个性化的健康建议。

初学者如何利用

  1. 学习基础知识
    • Rust 基础语法:初学者需要掌握 Rust 的基本语法,包括变量声明、数据类型、控制流、函数和结构体等。理解 Rust 的所有权系统、借用规则和生命周期概念,这是 Rust 编程的核心内容,对于编写安全高效的代码至关重要。
    • 串口通信知识:了解串口通信的基本原理,包括波特率、数据位、停止位和奇偶校验等概念。学习serialport库的使用方法,掌握如何配置和打开串口,以及从串口读取数据的操作。
    • GUI 编程:由于代码使用eframe库构建图形用户界面,初学者要学习egui的基本组件和布局方式,如CentralPanelhorizontallabelbutton等的使用方法,以及如何通过ctx.request_repaint()触发 UI 更新,实现交互式的用户界面。
  2. 实践与分析代码
    • 运行代码:将上述代码复制到 Rust 开发环境中,确保安装了所需的依赖库(eframeserialport)。运行程序,观察其功能,了解串口打开、数据读取、重量显示和稳定性判断等功能的实现过程。
    • 分析代码:仔细研读代码,理解每个结构体、函数和模块的作用。例如,分析ScaleApp结构体如何管理应用的状态,init_serial函数怎样初始化串口并启动读取线程,以及update函数如何更新 UI。逐步跟踪代码执行流程,掌握数据在各个模块之间的传递和处理逻辑。
  3. 扩展与创新
    • 简单修改:尝试对代码进行简单的修改和优化,例如调整串口参数,观察数据传输的变化;或者修改 UI 布局,使界面更加美观易用。通过这些实践,加深对代码和相关知识的理解。
    • 功能扩展:根据实际需求对程序进行功能扩展,比如添加数据存储功能,将读取到的重量数据保存到文件或数据库中;或者增加网络通信功能,将电子秤数据发送到远程服务器,实现数据的远程监控和管理。在扩展过程中,不断提升自己的编程能力和解决实际问题的能力。

阿雪技术观

让我们积极投身于技术共享的浪潮中,不仅仅是作为受益者,更要成为贡献者。无论是分享自己的代码、撰写技术博客,还是参与开源项目的维护和改进,每一个小小的举动都可能成为推动技术进步的巨大力量

Embrace open source and sharing, witness the miracle of technological progress, and enjoy the happy times of humanity! Let's actively join the wave of technology sharing. Not only as beneficiaries, but also as contributors. Whether sharing our own code, writing technical blogs, or participating in the maintenance and improvement of open source projects, every small action may become a huge force driving technological progress.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值