安卓与串口通信-如何区分连接的设备?

前言与背景

一般来说,不管是在什么平台上需要与外接硬件交互,第一件事都是应该能够正确的识别出目标硬件。

例如在 Windows 上,当一个新的外设设备被插入到我们的电脑时,系统会通过 Hardware IDs 、Compatible IDs 来确定连接的是什么设备并为其选择或安装一个合适的驱动程序以供后续使用。

在获取到可用的驱动程序后 Windows 还会使用 Instance IDs 、 Device instance IDs 用于标识设备的唯一性。

同理,在我们安卓上与外接硬件设备通信之前我们首先要做的应该是正确的识别出哪个设备是我们需要与之交互的设备。

在之前的文章中,我们说过在安卓端有两种连接串口的方式,一种是使用 android-serialport-api,即 root 后直接读写 /dev/ttys 文件;另外一种则是不需要 root,使用安卓提供的 USB HOST 模式通过 USB API 与连接的 USB 设备通信。

对于第一种情况,我目前遇到的都是每个 /dev/ttys 路径对应的都是一个真实的物理接口,而且这个物理接口一般都是主板直连的 串口 而非 USB 转接口。换言之,哪个接口连的是哪个设备可以认为是固定的,只需要直接通过接口来识别即可,所以这种情况我们暂时不讨论。

对于第二种情况,一般来说也是和第一种情况类似,连接的设备基本都是固定的设备,所以要识别还是很简单的。

但是偏偏就是有不一般的时候,且听我慢慢道来。

通用的识别方式

使用 USB HOST 模式和串口设备通信的话,就需要遵循 USB 标准。

根据 USB 标准,每个 USB 设备都会有一个 供应商 ID(vendorId, Vid) 和 产品 ID(productId, Pid)。

它们都是一串 16 位的数字(两字节),用于向其他主机设备标识当前设备。

供应商 ID 是由 USB Implementers Forum 分配给特定的公司,也就是说同一家公司出的产品 Vid 都是一样的。

产品 ID 由生产公司自行分配,一般来说是同一型号的产品使用同一个 Pid 。

这两个 ID 是写死到硬件设备中的,并且会在设备插入时与描述信息和产品信息以及有关设备支持的通信协议附加信息一起发送给主机设备。

一般来说,只要确定了 Vid 和 Pid 基本就可以完全确定当前连接的设备。

在安卓端可以通过如下方式读取到当前已连接的所有设备的这两个 ID :

val usbManager = contex.getSystemService(Context.USB_SERVICE) as UsbManager
val deviceList = usbManager.deviceList
var deviceId = ""
for ((_, v) in deviceList) {deviceId += "productId=${v.productId}\nvendorId=${v.vendorId}\n===========\n"
}
println(deviceId)

但是,上面也说了,只有在一般情况下这个方法会好使,因为理想很丰满,现实很残酷。

每个供应商如果想要获取到 USB Implementers Forum 分配的 Vid 则需要缴纳 5000 美元的会员费,这就导致许多硬件厂商为了节约成本就不会去申请自己的 Vid。

但是没有 Vid 显然也不行啊,总不能自己瞎写一个吧。

于是,“聪明的”厂商们就动起了歪脑筋,那我不写我自己的 ID 不就得了,直接使用底层通信芯片的 ID 岂不是完美?

因为一般硬件厂商在做自己的硬件设备时底层通信使用的不是 USB 通信,在最终发布产品时为了增加 USB 支持会在产品中内嵌一个 USB 转换芯片,使其支持 USB 通信,所以它们才能直接使用 USB 转换芯片的 ID。

即使这是被 USB 标准所禁止的行为,但是为了节约成本,厂商们才不会管这么多呢。

这就导致出现了很多明明是两个八竿子打不着的厂商出的两个完全没有任何关联的硬件设备, Vid 和 Pid 却是一模一样的。

我在工作中就遇到过这种情况,我司的某个安卓终端需要连接两个外接传感器设备,一个电子天平和一个 RFID 传感器,乍一看,这俩完全没有关联是吧,然而,它们的 productId 都是 29987 ,vendorId 都是 6790 ……

上面我们说过,Vid 是 USB 组织分配的,所以我们可以查的到这个 ID 对应的是哪家厂家:

1.jpg

(数据来自参考资料 2)

搜索这家公司的官网,并在其中查找这个产品 ID ,额,行吧,官网手册没有给出产品 ID,只有产品型号,但是无妨,我们可以在 这里 查询:

2.jpg

其实从型号名字已经能看出了,这确实是一个串口转 USB 芯片的 ID,但是严谨起见,我们还是在它官网搜一下这个型号:

3.jpg

这下确实证实了,这是一块串口转 USB 芯片。也就是说,这两个传感器都使用了同一家公司的同一型号的转换芯片,并且它们都没有买供应商 ID,所以用的都是转换芯片的 ID。

那么,对于这种情况,我们该如何分辨谁是谁?

配合其他辅助信息来识别

在上一节,我们说到由于大多数厂商都不会去遵循 USB 标准,所以单独靠 Vid 和 Pid 是无法区分出连接的设备的。

对于这种情况,我们可能还需要使用其他的辅助判断手段。

在 USB 标准中对于设备的描述信息还有一个叫 productName 的字段。

这个字段也是由设备厂商自行写入的,且最重要的是这个字段不是写死在硬件中的,是可修改的。

所以,我们可以要求厂商在出厂时将这个字段改成我们指定的名称或者我们也可以自行使用软件(可以找厂商要修改软件)将其改为指定的名称。

如此一来,Vid+Pid+productName 基本可以完成对连接设备的判断了。

在安卓中可以通过以下代码获取所有已连接设备的名称:

val usbManager = contex.getSystemService(Context.USB_SERVICE) as UsbManager
val deviceList = usbManager.deviceList
var deviceName = ""
for ((_, v) in deviceList) {deviceName += "productName=${v.productName}\n===========\n"
}
println(deviceName)

另外,如果还是不放心的话,大可直接把 USB 描述信息中的所有信息都加上做一个 Hash,然后用来做判断。

只是这种方法只适合用于判断唯一设备而不适合于用来判断同一型号的不同设备,因为 USB 描述信息中有些信息并不是同一个型号就都是一样的。

这里就直接给大伙看看 UVCCamera 用于获取唯一设备的代码:

public static final String getDeviceKeyName(final UsbDevice device, final String serial, final boolean useNewAPI) {if (device == null) return "";final StringBuilder sb = new StringBuilder();sb.append(device.getVendorId());            sb.append("#"); // API >= 12sb.append(device.getProductId());           sb.append("#"); // API >= 12sb.append(device.getDeviceClass());         sb.append("#"); // API >= 12sb.append(device.getDeviceSubclass());      sb.append("#"); // API >= 12sb.append(device.getDeviceProtocol());                      // API >= 12if (!TextUtils.isEmpty(serial)) {sb.append("#"); sb.append(serial);}if (useNewAPI && BuildCheck.isAndroid5()) {sb.append("#");if (TextUtils.isEmpty(serial)) {// ANDROID 10 以上会获取不到 SerialNumber , 会报错:SecurityException ,无权限读取该信息// 不过此处获取串口序列号仅用于计算设备的哈希值作为保存关于此设备的配置信息的 KEY// 包括是否申请了权限的这个信息,所以意味着会在申请权限前调用这个方法// 所以此处可以不做特殊处理,如果获取不到就不获取了,其他信息已经足以计算出这个设备的唯一哈希try {sb.append(device.getSerialNumber());    sb.append("#"); // API >= 21} catch (SecurityException e) {LogUtil.e(TAG, "getDeviceKeyName: 获取串口序列号失败", e);}}sb.append(device.getManufacturerName());    sb.append("#"); // API >= 21sb.append(device.getConfigurationCount());  sb.append("#"); // API >= 21if (BuildCheck.isMarshmallow()) {sb.append(device.getVersion());         sb.append("#"); // API >= 23}}return sb.toString();
}

按道理来说,至此本文就应该结束了,但是,但是又来了。

低效但最可靠的方式

在之前,我一直都是配合着上面两种方法做识别的处理,也一直没有出过什么意外。

因为之前我们的安卓终端是定制设备,外接硬件也是固定的几个硬件设备,所以对于设备识别倒也还算好处理。

但是,就怕哪天老板脑洞大开,给我们整点花活,没错,我的老板就开脑洞了。

他觉得定制终端不是很方便,所以想直接使用用户自己的普通手机来完成我们的业务流程。

一开始倒也没什么大问题,我依旧按照定制终端的写法写了程序,刚开始运行也确实没有什么问题。

但是,某天某同事气冲冲的找到我,质问我写的什么玩意儿,他怒吼到:我手机明明什么东西都没插,你为什么说我插上了 RFID 读卡器?现在我真的插上了读卡器却什么也读不出来了!

好家伙,给我说的一愣一愣的,赶紧借他手机过来检查了一下,检查结果又给我看的一愣一愣的。

我在程序中写入的这个读卡器的 ID,居然和他手机某个传感器的 ID 重复了!

因为使用的这款读卡器是新采购的,且没有写入产品名称,所以我也偷懒没有对产品名称做校验,只校验了 ID,但是令我万万没想到的是,手机在没有插入任何外设的情况下居然也能读到 USB 信息,一下子我已经不知道这个 USB 信息是从哪儿来的了。

不过这个问题也只是一个小插曲,我把产品名的判断加上就行了。

真正让我头痛的是后面的事情。

后来老板又觉得我们自己采购传感器做外设不太好,想直接连接成熟的成品读它们的数据就行了。

比如之前我们业务有需要称重的地方是使用自己采购的天平传感器自己设计硬件来读取数据的,现在需要改为直接连接市面上成熟的成品电子秤,从其中读取数据来使用。

那就接呗,初期拟定的是支持市占率最高的某两款不同型号的成品电子秤,在老板买来样品后,我一插上安卓设备就傻了。

熟悉的 Vid Pid 完全一致,行呗,我再拿产品名呗,一看……我人傻了,怎么连产品名都一摸一样啊!

这又是别人的成品自然不可能更改产品名,那咋办?两款秤使用的通信协议也不一样,所以必须做出区分才能正确的读出数据。

此时,如果想区分出当前连接的到底是哪款秤只有一个低效但是很有用的办法了,那就是使用两款秤的协议挨个发送请求,哪个有回复就说明现在连接的是哪款秤。

这就要求我们和秤的协议必须有一个不会破坏两个设备正常运行的“无害”指令以及这个指令必须要能有一个可分辨的正常回复,比如读取秤的版本号之类的就是理想的选择。

否则有时某些设备虽然协议不对,但是在收到指令后还是会回复一些乱码之类的,此时如果只是根据是否能收到回复来判断是否是特定设备也是很容易出错的。

另外一种情况就是,如果设备收到的是不支持的指令,则不会回复任何消息,此时我们只有通过等待连接是否超时才能判断是否是特定设备了。

这样的话,如果当前需要支持的设备比较少还好,如果后期需要支持的设备特别多的话,就意味着等待判断设备型号就需要非常长的一段时间了。

这就带来了最后一种终极解决方案,也是我目前采用的方案,那就是让用户自己去选择他连接的是哪个设备。

在我们使用了上述的方案判断设备后依旧无法完全判断是否是特定的设备时,我们就将选择权交给用户,由用户自己来确定连接的是哪个设备。

参考资料

  1. Device identification strings
  2. usb_vids
  3. How to identify the correct USB Device in LabVIEW

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

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

相关文章

看图学源码之 Atomic 类源码浅析二(cas + 分治思想的原子累加器)

原子累加器 相较于上一节看图学源码 之 Atomic 类源码浅析一(cas 自旋操作的 AtomicXXX原子类)说的的原子类,原子累加器的效率会更高 XXXXAdder 和 XXXAccumulator 区别就是 Adder只有add 方法,Accumulator是可以进行自定义运算方…

大数据技术5:OLAP引擎对比分析

前言:数据仓库建设,初级的理解就是建表,将业务数据、日志数据、消息队列数据等,通过各种调度任务写入到表里供OLAP引擎使用。但要想建好数仓也是一个复杂、庞大的工程,比如要考虑:数据清洗、数据建模&#…

001 LLM大模型之Transformer 模型

参考《大规模语言模型--从理论到实践》 目录 一、综述 二、Transformer 模型 三、 嵌入表示层(位置编码代码) 一、综述 语言模型目标是建模自然语言的概率分布,在自然语言处理研究中具有重要的作用,是自然 语言处理基础任务之一…

第 119 场 LeetCode 双周赛题解

A 找到两个数组中的公共元素 模拟 class Solution { public:vector<int> findIntersectionValues(vector<int> &nums1, vector<int> &nums2) {unordered_set<int> s1(nums1.begin(), nums1.end()), s2(nums2.begin(), nums2.end());vector<…

【基于大数据的人肥胖程度预测分析与可控策略】

基于大数据的人肥胖程度预测分析与可控策略 前言数据获取与清洗数据挖掘与分类建模1. K-means聚类2. 层次聚类3. DBSCAN4. 分类建模 数据可视化模型肥胖程度预测分析与可控策略结语 前言 随着现代生活方式的改变&#xff0c;肥胖问题逐渐成为全球性的健康挑战。为了更好地理解…

实用篇 | 3D建模中Blender软件的下载及使用[图文详情]

本文基于数字人系列的3D建模工具Blender软件的安装及使用&#xff0c;还介绍了图片生成3D模型的AI工具~ 目录 1.Blender的下载 2.Blender的使用 3.安装插件(通过压缩包安装) 4.实例 4.1.Blender使用MB-Lab插件快速人体模型建构 4.1.1.点击官网&#xff0c;进行下载 4.1.…

Java TCP(一对一)聊天简易版

客户端 import java.io.*; import java.net.Socket; import java.util.Date; import javax.swing.*;public class MyClient {private JFrame jf;private JButton jBsend;private JTextArea jTAcontent;private JTextField jText;private JLabel JLcontent;private Date data;p…

C语言 题目

1.写一个函数算一个数的二进制(补码)表示中有几个1 #include<stdio.h>//统计二进制数中有几个1 //如13:1101 //需要考虑负数情况 如-1 结果应该是32// n 1101 //n-1 1100 //n 1100 //n-1 1011 //n 1000 //n-1 0111 //n 0000 //看n的变化 int funca(int c){int co…

css:flex布局中子元素高度height没有达到100%

目录 问题flex布局示例解决办法方式一方式二 参考 问题 css中使用flex布局中子元素高度height没有达到100% flex布局示例 希望实现两个盒子左右分布&#xff0c;内容垂直居中对齐 <style>.box {display: flex;align-items: center;border: 1px solid #eeeeee;}.box-l…

react新旧生命周期钩子

以下的内容根据尚硅谷整理。 旧生命钩子 辅助理解&#xff1a; 红色框&#xff1a;挂载时生命钩子蓝色框&#xff1a;更新时生命钩子绿色框&#xff1a;卸载时生命钩子 挂载时 如图所示&#xff0c;我们可以看到&#xff0c;在组件第一次挂载时会经历&#xff1a; 构造器&a…

stateflow——如何查看状态机中参数变化及状态机断点调试

法一&#xff1a;使用Data Inspector 点击“符号图窗”和“属性”&#xff0c;如图&#xff1b;在选择变量n并右键点击inspector&#xff0c;最后在logging&#xff0c;如图 法二&#xff1a;log active state 和法一类似使用data inspector查看&#xff0c;类似的查看方法和…

【每周一测】Java阶段四第三周学习

目录 1、关于分布式锁的说法&#xff0c;错误的是&#xff08; &#xff09; 2、JDK动态代理产生的代理类和委托类的关系是 3、下列关于ElasticSearch中基本概念描述错误的是 4、Spring Cloud 中&#xff0c;Feign 是什么&#xff1f; 5、在JavaScript中&#xff0c;可以使…

玩转大数据13: 数据伦理与合规性探讨

1. 引言 随着科技的飞速发展&#xff0c;数据已经成为了现代社会的宝贵资产。然而&#xff0c;数据的收集、处理和利用也带来了一系列的伦理和合规性问题。数据伦理和合规性不仅关乎个人隐私和权益的保护&#xff0c;还涉及到企业的商业利益和社会责任。因此&#xff0c;数据…

韵达快递单号查询,以表格的形式导出单号的每一条物流信息

批量查询韵达快递单号的物流信息&#xff0c;并以表格的形式导出单号的每一条物流信息。 所需工具&#xff1a; 一个【快递批量查询高手】软件 韵达快递单号若干 操作步骤&#xff1a; 步骤1&#xff1a;运行【快递批量查询高手】软件&#xff0c;第一次使用的伙伴记得先注册…

SOP(标准作业程序)和WI(操作指导书)的联系和区别

目录 1.SOP&#xff08;标准作业程序&#xff09;&#xff1a;2.WI&#xff08;操作指导书&#xff09;&#xff1a;3.SOP和WI的区别&#xff1a; 1.SOP&#xff08;标准作业程序&#xff09;&#xff1a; SOP: 所谓SOP&#xff0c;是 Standard Operation Procedure三个单词中…

【计算机网络实验】实验三 IP网络规划与路由设计(头歌)

目录 一、知识点 二、实验任务 三、头歌测试 一、知识点 IP子网掩码的两种表示方法 32位IP子网掩码&#xff0c;特点是从高位开始连续都是1&#xff0c;后面是连续的0&#xff0c;它有以下两种表示方法&#xff1a; 传统表示法&#xff0c;如&#xff1a;255.255.255.0IP前…

【WebSocket】使用ws搭建一个简单的在线聊天室

前言 什么是WebSockets&#xff1f; WebSockets 是一种先进的技术。它可以在用户的浏览器和服务器之间打开交互式通信会话。使用此 API&#xff0c;你可以向服务器发送消息并接收事件驱动的响应&#xff0c;而无需通过轮询服务器的方式以获得响应。 webscokets 包括webscoket…

中科院分区和JCR分区有什么区别

文章目录 名词解释学科划分不同参考的影响因子不同期刊分区不同期刊分区阈值不同 名词解释 中科院分区&#xff1a;又称“中科院JCR分区”&#xff0c;是中国科学院文献情报中心世界科学前沿分析中心的科学研究成果&#xff0c;期刊分区表数据每年底&#xff08;每年12月中下旬…

Python爬虫-实现批量抓取王者荣耀皮肤图片并保存到本地

前言 本文是该专栏的第12篇,后面会持续分享python爬虫案例干货,记得关注。 本文以王者荣耀的英雄皮肤为例,用python实现批量抓取“全部英雄”的皮肤图片,并将图片“批量保存”到本地。具体实现思路和详细逻辑,笔者将在正文结合完整代码进行详细介绍。注意,这里抓取的图片…

数据结构和算法-单链表

数据结构和算法-单链表 1. 链表介绍 链表是有序的列表&#xff0c;但是它在内存中是存储如下 图1 单链表示意图 小结: 链表是以节点的方式存储每个节点包含data域&#xff0c;next域&#xff0c;指向下一个节点。如图&#xff1a;发现链表的各个节点不一定是连续存储。比如地…