java程序读取并控制串口设备

news2024/11/25 20:30:59

监听串口,接收它们发过来的数据,进行处理。

一、概况

前不久做的一个项目,需要读取水下传感器的数据。这些传感器通过串口与外界交互。我们写了一个java程序,接收传感器传送的数据,同时也下发命令,控制部分传感器。

二、运行环境

(一)硬件环境

串口的话,一般台式机主板有1、2个串口,如果只有2台传感器,那么通过串口线与台式机连接起来,就能直接访问了。
在这里插入图片描述

1、串口线

在这里插入图片描述

2、串口服务器

但如果有许多传感器,怎么办呢?这时候需要用到串口服务器,比如8口的串口服务器,同时接8个传感器,然后串口服务器通过网线与应用服务器相连。而在应用服务器,通过串口软件,将串口服务器的8个串口映射到本机。这样,就相当于8个传感器直接连到了应用服务器一样,应用服务器上的程序就能直接访问这8个传感器了。

在这里插入图片描述
串口服务器连接8个传感器的接口,既可以是串口,也可以是经过转换线后接入网口。
在这里插入图片描述

3、应用服务器映射串口服务器的串口

在这里插入图片描述

(二)串口软件

1、映射串口服务器的串口到本地

前面说过,要用串口软件将串口服务器的8个串口映射到本地。要达成这个效果,
1)首先要安装串口服务器的驱动程序
2)串口服务器提供的软件,将8个串口映射到本地
以moxa为例:
在这里插入图片描述
3)访问串口服务器提供的WEB管理页面,对每个串口的参数,如波特率等进行设置。这种设置,主要是针对传感器,假设每款传感器固定连接到某个串口。

2、模拟数据

使用串口设备似乎有个好处,就是有一些类似串口助手之类的软件,可以模拟向指定串口发送数据,利于测试。
在这里插入图片描述
在这里插入图片描述

三、代码

我们使用JAVA来接收数据和写入指令。

1、代码结构

在这里插入图片描述
因为开始时我们以为只须读取数据就好了,后来才将写入指令的功能加进去,所以代码文件的名字考虑不周,显得不够规范。
在这里插入图片描述

代码讲解

1、开始程序

开始程序主要是执行初始化工作,获取服务器中可用的串口。串口是独占的,如果有别的进程在用,那我们的程序就用不了。需要先中止别的进程,释放资源才行。

Receiver.java

public class Receiver implements ApplicationRunner {
    private List<PortReader> readerList = new ArrayList<PortReader>();//设备数据接收器,有多个,所以用数组
    private List<PortEx> curPortList = new ArrayList<>();//当前活跃端口。用于检查端口变化
    private PortSaver portSaver = null;//接收到的数据输出或转储处理器,只有1个,集中处理
    public static BlockingQueue<DataRow> queue = new LinkedBlockingQueue<>();//接收到的数据处理队列

    @Autowired
    @Qualifier("redisService1")
    RedisService redisService;

    //相关数据库表操作类
    @Autowired
    PortService portService;

    //以下为传感器类
    @Autowired
    PortCtdService ctdService;
    @Autowired
    PortCo2Service co2Service;
    @Autowired
    PortAdcpEquipService adcpEquipService;
    @Autowired
    PortAdcpCurrentService adcpCurrentService;
    @Autowired
    PortAdcpWaveService adcpWaveService;
    @Autowired
    PortAdcpInstrumentService adcpInstrumentService;
    @Autowired
    PortHydroService hydroService;

    public static void main(String[] args) {
        Receiver rec = new Receiver();
        rec.init();
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("start init reader ");
        init();
    }

    @PreDestroy
    public void exit() {
        System.out.println("Reader go home when system exit");
        for (PortReader reader : readerList) {
            if (reader != null) {
                reader.stopRead();
            }
        }
    }

    private void init() {
        List<PortEx> ports = getAvailablePorts();//获取系统中可用的端口
        go(ports);

        portSaver = new PortSaver(queue, 
            redisService,
            ctdService,co2Service, adcpEquipService,adcpCurrentService,adcpWaveService,adcpInstrumentService,hydroService);//将各款传感器类都传入,方便集中处理
        portSaver.start();
    }
    private List<PortEx> getAvailablePorts() {//获取系统中可用的端口
        List<PortEx> availablePorts = new ArrayList<>();

        List<PortEx> ports = portService.queryByPageEx(null, PageRequest.of(0, 1000));

        Enumeration portList = CommPortIdentifier.getPortIdentifiers();
        while (portList.hasMoreElements()) {
            CommPortIdentifier portId = (CommPortIdentifier) portList.nextElement();
            if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL) {
                for (PortEx port : ports) {
                    if (portId.getName().equals(port.getPortId())) {
                        port.setCommPortIdentifier(portId);
                        availablePorts.add(port);
                        break;
                    }
                }
            }
        }

        return availablePorts;
    }
    private void go(List<PortEx> ports) {
        readerList.clear();
        curPortList.clear();

        for (PortEx port : ports) {
            if(!port.getSensorCategory().equals("LIGHT")){//如果是不用写入指令的设备。。。
                PortReader read = new PortReader(port.getCommPortIdentifier(), port, queue);
                read.setDaemon(true);//设置守护进程
                read.start();
                readerList.add(read);
                curPortList.add(port);
            }else{
                PortWriter writer = new PortWriter(port.getCommPortIdentifier(), port, DeviceManager.deviceMap);
                writer.setDaemon(true);
                writer.start();
            }
        }
    }
}

2、串口设备对象类

串口设备对象类,初始化串口,开启串口监听,接收设备信息流,都在这里完成。

Device.java

/**
 * 串口设备对象类
 * 初始化串口,开启串口监听,接收设备信息流,都在这里完成
 */
public class Device implements SerialPortEventListener {

    private PortEx port = null;//自定义的串口实体,包括端口号,波特率等等
    private SerialPort serialPort = null;
    private CommPortIdentifier portId = null;
    private InputStream inputStream;
    private OutputStream outputStream;

    private BlockingQueue<DataRow> queue = null;

    private Map<String,String> _temps = new HashMap<>();//用于暂存接收到的字符串,积攒到完整一条记录后才填入消息队列进行处理

    public Device(CommPortIdentifier Id, PortEx p, BlockingQueue<DataRow> queue) {
        try {
            this.port = p;
            this.portId = Id;
            this.queue = queue;
            serialPort = (SerialPort) portId.open(port.getPortName(), 2000);
            //设置波特率、数据位、停止位、检验位
            serialPort.setSerialPortParams(port.getBaudRate(),
                    port.getDataBits(),
                    port.getStopBits(),
                    port.getParity());
            //获取输入流
            inputStream = serialPort.getInputStream();
            outputStream = serialPort.getOutputStream();

            //设置串口监听
            serialPort.addEventListener(this);
            //设置开启监听
            serialPort.notifyOnDataAvailable(true);

            System.out.println("已初始化端口:" + portId.getName());
        } catch (PortInUseException e) {
            System.out.println(String.format("%s正在使用!", portId.getName()));
        } catch (TooManyListenersException e) {
            e.printStackTrace();
        } catch (UnsupportedCommOperationException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void open() {
        if(serialPort != null)  {
            System.out.println(String.format("%s打开中...",portId.getName()));
        } else {
            System.out.println(String.format("%s打开失败",portId.getName()));
        }
    }
    public boolean close() {
        try {
            serialPort.close();
        } catch(Exception ex){
            System.err.println(ex.getCause());
            return false;
        }
        try {
            inputStream.close();
        } catch (Exception ex) {
            System.err.println(ex.getCause());
            return false;
        }
        try {
            outputStream.close();
        } catch (Exception ex) {
            System.err.println(ex.getCause());
            return false;
        }
        System.out.println(String.format("%s关闭...%b",port.getPortName(),portId.isCurrentlyOwned()));
        return true;
    }    

    /**
     * 监听函数
     */
    public void serialEvent(SerialPortEvent serialPortEvent) {
        switch (serialPortEvent.getEventType()) {
            //获取到有效信息
            case SerialPortEvent.DATA_AVAILABLE:
                on_data_available();
                break;
            default:
                System.out.println("不知所谓");
                break;
        }
    }
    public void write(byte[] cmd) {//写入指令到设备
        try {
            outputStream.write(cmd);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /**
     * 读取串口信息
     */
    private void on_data_available() {
        if (inputStream != null) {
            try {
                int len = inputStream.available();
                byte[] readBuffer = new byte[len];
                len = inputStream.read(readBuffer);
                String txtData = new String(readBuffer, 0, len).trim();
                read(txtData);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    private void read(String txtData) {
        if (txtData != null && txtData.length() > 0) {
            String category = port.getSensorCategory();

            String keeper = "";
            if(_temps.containsKey(category)){
                keeper = _temps.get(category);
            }

            String[] lines = txtData.split("\r");
            for(int i = 0;i < lines.length;i++){
                String line = lines[i];
                keeper += line.trim();
                if(DataUtil.iswhole(category,keeper)){
                    sendIt(category,keeper);//信息处理。。。
                    keeper = "";
                }
                if(i < lines.length - 1) {//本line与下一个line之间有换行符。意味着已经结束
                    keeper = "";
                }
                _temps.put(category,keeper);
            }
        }
    }
    private void sendIt(String category,String line){
        try {
            DataRow row = new DataRow();

            //将数据写入row...
            。。。

            queue.put(row);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

3、串口设备管理器

读取数据的串口设备管理器,在这里操作Device对象。

PortReader.java

/**
 * 读取数据的串口设备管理器
 * 
 */
public class PortReader extends Thread {
    private CommPortIdentifier portId = null;
    private PortEx port = null;
    private Device device = null;
    private BlockingQueue<DataRow> queue = null;

    public PortReader(CommPortIdentifier id, PortEx p, BlockingQueue<DataRow> queue) {
        super();
        this.portId = id;
        this.queue = queue;
        this.port = p;
    }

    public void run() {
        device = new Device(portId, port, queue);
        device.open();
        DeviceManager.deviceMap.put(port.getPortId(), device);
    }

    public void stopRead() {
        if(device != null) device.close();
        System.out.println(getName() + " stop success when reader go home");
    }

    public PortEx getPort(){
        return this.port;
    }
}

4、数据处理类

将从消息队列中接收到的设备数据进行处理。

PortSaver.java

/**
 * 将读取到的设备数据输出或转储处理器
 */
public class PortSaver extends Thread {
    private BlockingQueue<DataRow> queue = null;
    private DataRow dataRow = null;
    private RedisService redisService;

    //以下为各传感器类
    private PortCtdService ctdService;
    private PortCo2Service co2Service;
    private PortAdcpEquipService adcpEquipService;
    private PortAdcpCurrentService adcpCurrentService;
    private PortAdcpWaveService adcpWaveService;
    private PortAdcpInstrumentService adcpInstrumentService;
    private PortHydroService hydroService;

    public PortSaver(BlockingQueue<DataRow> queue, RedisService redisService,
                     PortCtdService ctdService,
                     PortCo2Service co2Service,
                     PortAdcpEquipService adcpEquipService,
                     PortAdcpCurrentService adcpCurrentService,
                     PortAdcpWaveService adcpWaveService,
                     PortAdcpInstrumentService adcpInstrumentService,
                     PortHydroService hydroService) {
        super();
        this.queue = queue;
        this.redisService = redisService;

        this.ctdService = ctdService;
        this.co2Service = co2Service;
        this.adcpEquipService = adcpEquipService;
        this.adcpCurrentService = adcpCurrentService;
        this.adcpWaveService = adcpWaveService;
        this.adcpInstrumentService = adcpInstrumentService;
        this.hydroService = hydroService;
    }

    public void run() {
        if (queue != null) {
            while (true) {
                if (queue.size() > 0) {
                    try {
                        dataRow = queue.take();

                        System.out.println(dataRow.getMessage());

                        String[] values = dataRow.getMessage().trim().split(",");
                        String category = dataRow.getCategory();//串口类型
                        int sensorId = dataRow.getSensorId();
                        Object obj = DataUtil.getEntity(category, values);
                        dealIt(category, sensorId, obj);
                    } catch (Exception ex) {
                        // TODO Auto-generated catch block
                        System.err.println(ex.getCause());
                    }
                }
            }
        }
    }

    public void stopSave() {
        System.out.println(getName() + " stop success when reader go home");
    }

    private Map<String,PortAdcpCurrent> adcpCurrents = new HashMap<>();
    private void dealIt(String category, int sensorId, Object obj) {
        if (obj == null) return;

        //判断category对应的传感器类型,分别处理其数据
        。。。
    }

    。。。
}

四、小结

我们项目中用到的传感器,发送数据有一定频率,比如每分钟发一笔数据。每笔数据有固定的格式。但是,每笔数据有可能分为好几次发送,断断续续,所以处理时要注意。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1426408.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

车载电子电器架构 —— IP地址获取策略

车载电子电器架构 —— IP地址获取策略 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自…

DHCP简介

定义 动态主机配置协议DHCP&#xff08;Dynamic Host Configuration Protocol&#xff09;是一种用于集中对用户IP地址进行动态管理和配置的技术。即使规模较小的网络&#xff0c;通过DHCP也可以使后续增加网络设备变得简单快捷。 DHCP是在BOOTP&#xff08;BOOTstrap Protoc…

WiFi 7 的核心要点

目录 WiFi 7 是什么&#xff1f; WiFi 7 的主要feature功能&#xff1a; 320Mhz channel 4K QAM Multi-Link Operation (MLO)&#xff0c;多链路操作 512 block ACK OFDMA&#xff1a;multiple RUs to single STA. 总结&#xff1a;性能是第一优先级&#xff0c;WiFi 7&#xf…

Java的JVM学习一

一、java中的内存结构如何划分 栈和堆的区别&#xff1a; 栈负责处理运行&#xff0c;堆负债处理存储。 区域名称作用虚拟机栈用于存储正在执行的每个Java方法&#xff0c;以及其方法的局部变量表等。局部变量表存放了便器可知长度的各种基本数据类型&#xff0c;对象引用&am…

Vue3_基础使用

vue2的选项式与vue3的组合式区别&#xff1a; 选项式&#xff1a;vue2中数据与方法计算属性等等&#xff0c;针对一个数据的处理在不同的配置中&#xff0c;当业务复杂时很难维护&#xff0c;修改起来也不好查找。 vue3的组合式&#xff1a;将针对数据的方法计算属性等等放在一…

开源编辑器:ONLYOFFICE文档又更新了!

办公软件 ONLYOFFICE文档最新版本 8.0 现已发布&#xff1a;PDF 表单、RTL、单变量求解、图表向导、插件界面设计等更新。 什么是 ONLYOFFICE 文档 ONLYOFFICE 文档是一套功能强大的文档编辑器&#xff0c;支持编辑处理文本文档、电子表格、演示文稿、可填写的表单、PDF&#…

2024数学建模美赛赛题翻译速览来了!

美赛6点已经开赛了。 先带来美赛翻译速览吧&#xff0c;图片形式的&#xff0c;如果想要文档形式的可以直接点击领取&#xff1a; 免费资料​pan.baidu.com/s/1IjAMTI7V3i_GWexVUp9DrQ?pwdsmpp A&#xff1a; B&#xff1a; C&#xff1a; D&#xff1a; E&#xff1a; F&a…

鸿蒙开发-UI-页面路由

鸿蒙开发-UI-组件 鸿蒙开发-UI-组件2 鸿蒙开发-UI-组件3 鸿蒙开发-UI-气泡/菜单 文章目录 一、基本概念 二、页面跳转 1.router基本概念 2.使用场景 3.页面跳转参数传递 三、页面返回 1.普通页面返回 2.页面返回前增加一个询问框 1.系统默认询问框 2.自定义询问框 总…

在Windows系统中执行DOS命令

目录 一、用菜单的形式进入DOS窗口 二、通过IE浏览器访问DOS窗口 三、复制、粘贴命令行 四、设置窗口风格 1.颜色 2.字体 3.布局 4.选项 五、Windows系统命令行 由于Windows系统彻底脱离了DOS操作系统&#xff0c;所以无法直接进入DOS环境&#xff0c;只能通过第三方软…

UE4学习笔记 FPS游戏制作3 添加武器

文章目录 章节目标为骨骼添加武器挂载点添加武器 章节目标 本章节为手部添加一个武器挂载点&#xff0c;并挂载一个武器 为骨骼添加武器挂载点 添加挂载点需要以一个动画片段为基础&#xff0c;为骨骼添加挂载点。 首先找到我们需要的动画片段&#xff0c;通常是idle 双击打…

8、应急响应-战前溯源反制主机蜜罐系统HFishHIDSElkeidWazuh

用途&#xff1a;个人学习笔记&#xff0c;欢迎指正 目录 背景&#xff1a; 一、潮源反制-平台部署-蜜罐-Hfish 二、溯源反制-平台部署-HIDS-Wazuh 三、溯源反制-平台部署-HlDS-Elkeid-hub 背景&#xff1a; 攻击者对服务器存在着各种威胁行为&#xff0c;作为安全人员&am…

React实现组件扩展机制

在java中&#xff0c;SPI机制是Java中提供的一种服务发现机制。同样&#xff0c;前端也很需要这种机制&#xff0c;这样可以做到组件可插拔&#xff0c;可替换&#xff0c;减少相互冗余。 快速使用 1.扩展点使用 通过使用Extension组件定义扩展点&#xff0c;通过name标记扩展…

数据结构—动态查找表

动态查找介绍 1. 动态查找的引入&#xff1a;当查找表以线性表的形式组织时&#xff0c;若对查找表进行插入、删除或排序操作&#xff0c;就必须移动大量的记录&#xff0c;当记录数很多时&#xff0c;这种移动的代价很大。 2. 动态查找表的设计思想&#xff1a;表结构本身是…

亚信安慧的AntDB数据库:稳定可靠的保障

亚信安慧AntDB数据库在运营商自主可控替换项目中的成功应用&#xff0c;具有极其重要的意义。该数据库的落地&#xff0c;不仅为这一项目注入了强大的支持力量&#xff0c;还在更大程度上提升了整体的运营效能。作为一种高效可靠的数据库解决方案&#xff0c;AntDB引入了先进的…

k8s存储之PV、PVC

在k8s集群中&#xff0c;资源存储会散落到各个工作节点上&#xff0c;这样对用资源调用很不方便&#xff0c;那么k8s是如何实现存储资源共享的呢&#xff0c;本文浅尝辄止的探讨一下&#xff0c;k8s是通过pv、pvc实现的。 一、PV、PVC的概念 1、持久卷(PV&#xff09; pv是Pe…

防火墙用户认证、NAT、策略路由、DNS透明代理以及双机热备笔记

用户认证 防火墙管理员登录认证 --- 检验身份的合法性&#xff0c;划分身份权限 用户认证 --- 上网行为管理的一部分 用户&#xff0c;行为&#xff0c;流量 --- 上网行为管理三要素 用户认证的分类 上网用户认证 --- 三层认证 --- 所有的跨网段的通信都可以属于上网行为。…

【演讲比赛流程管理系统(C++版)】

一、演讲比赛程序需求 1.1、比赛规则 学校举行一场演讲比赛&#xff0c;共有12个人参加。比赛共两轮&#xff0c;第一轮为淘汰赛&#xff0c;第二轮为决赛 每名选手都有对应的编号&#xff0c;如10001~10012 比赛方式:分组比赛&#xff0c;每组6个人 第一轮分为两个小组&a…

Unity SRP 管线【第九讲:URP 点光源与聚光灯】

文章目录 CPU数据搜集GPU数据使用光照计算 CPU数据搜集 我们只能支持有限数量的其他灯。并将这些灯光数据&#xff08;位置、颜色、阴影强度、方向光光源、灯光遮蔽Probe、灯光层级Mask&#xff09;发送到GPU以供场景中所有物体渲染使用。 //ForwardLights.cs 额外光源数量与…

HarmonyOS4.0系统性深入开发33相对布局(RelativeContainer)

相对布局&#xff08;RelativeContainer&#xff09; 概述 RelativeContainer为采用相对布局的容器&#xff0c;支持容器内部的子元素设置相对位置关系。子元素支持指定兄弟元素作为锚点&#xff0c;也支持指定父容器作为锚点&#xff0c;基于锚点做相对位置布局。下图是一个…

postgresql|数据库|pg_repack插件的部署和使用

一&#xff0c; 表和索引的膨胀现象 Postgres SQL 实现的MVCC的机制不同于 oracle &#xff0c; mysql innodb 的 undo tablespace 的机制。 表上所用的更新和删除等操作的行为&#xff0c;都不会实际的删除或修改&#xff0c;而是标记为死元祖 &#xff08;dead rows or dead…