Android hid 数据传输(device 端 )

news2025/1/11 14:44:05

最近一直在处理hid 数据需求,简而言之就是两台设备直接可以通过usb 线互相传递数据。

项目架构

为什么Device 端要采用HID(人机接口设备)的方式发送和接收数据呢?

主要是速度快,举个例子,就是鼠标移动,屏幕可以及时响应,用的也是这种协议。

因为Host端底层我们控制不了,不能保证都支持Hid 协议,所以Host 端采用跨平台方案,libusb 协议。

Liusb 网上介绍的很多啦,可以运行在各个平台,windows ,android.linux,是一种理想中的跨平台数据传输方案。

项目主要功能

1,sensor数据传输

2,TP 数据传输(按键传输同理)

项目主要技术点

1,TP数据监听

2,sensor数据监听

3,hid 数据传输丢失问题

4,HID 节点生成监听

5,开机启动native 服务处理数据

6,selinux 权限问题

技术实现

代码结构

TP数据监听

驱动所有的滑动 和 按键 上报都是通过节点的方式,不同平台节点有所差异,需要和驱动沟通。我试验的平台节点是:

#define INPUT_KEY_NODE "/dev/input/event1"
#define INPUT_TP_NODE "/dev/input/event3"

所以监听这两个就行了,我们这里采用的是poll 的方式,有数据的时候会回调,没有的话会阻塞

主要代码

void HidReceiver::listenThread()
{
    struct pollfd fds[IN_FILES];
    fds[0].events = POLLIN;
    fds[1].events = POLLIN;
    fds[2].events = POLLIN;
    int result;
    char buff[512];
//    sleep(1);
    LOGD("hid open");
    hid_fd = open(DEVICE_NODE, O_RDWR | O_NONBLOCK);
    int key_fd =  open(INPUT_KEY_NODE, O_RDWR | O_NONBLOCK);
    int tp_fd =  open(INPUT_TP_NODE, O_RDWR | O_NONBLOCK);
    LOGD("nod %d,%d,%d",hid_fd,key_fd,tp_fd);
    fds[0].fd = hid_fd;
    fds[1].fd = key_fd;
    fds[2].fd = tp_fd;


    unsigned char data[sizeof(input_event)];
    input_event dev_data;

    while(1){
        result = poll(fds, IN_FILES, -1);
        if (result == 0) {
            LOGD("Poll timeout");
        } else if(result > 0){
            if ((fds[0].revents & POLLIN)){ 
                int size = read(fds[0].fd, buff, sizeof(buff));
                if(size > 0){
                    process_event(buff);
                }
            }

            if ((fds[1].revents & POLLIN)){ 
                int size = read (fds[1].fd, (unsigned char*)data, sizeof(input_event));
                LOGD("size:%d", size);
                memcpy(&dev_data, data, sizeof(input_event));
                LOGD("Keyevent size:%d", dev_data.type);
                // sensordata.sensorType = 0x104;
                // sensordata.ievent = dev_data;
                // process_event(sensordata);
            }


            if ((fds[2].revents & POLLIN)){ 
                int size = read (fds[2].fd, (unsigned char*)data, sizeof(input_event));
                memcpy(&dev_data, data, sizeof(input_event));
                #if 0
                LOGD("abs size:%d", dev_data.type);
                if (dev_data.type == EV_ABS)
                {
                    if (dev_data.code == ABS_MT_POSITION_X)
                    {
                            x = dev_data.value;
                            if (x < 0) x = 0;
                            LOGD("rel X:%d", dev_data.value);
                    }
                    else if(dev_data.code == ABS_MT_POSITION_Y)
                    {
                            LOGD("\nx=%d,y=%d,dev_data.code=%d\n", x, y,dev_data.code);
                            y = dev_data.value;
                            if (y < 0) y = 0;
                            sensordata.sensorType = 0x104;
                            sensordata.abs_x = x;
                            sensordata.abs_y = y;
                            process_event(sensordata);
                            x = 0;
                            y = 0;
                    }
                }
                #endif
                if (dev_data.type == EV_KEY)
                {
                    LOGD("EV_KEY %d",dev_data.code);
                    switch(dev_data.code){
                        case KEY_KP5://双击
                        case KEY_DASHBOARD://单机
                        case KEY_F17://左滑
                        case KEY_ISO://右滑
                        case KEY_F16://上滑
                        case KEY_CONFIG://下滑
                            sensordata.sensorType = 0x104;
                            sensordata.type = dev_data.type;
                            sensordata.code = dev_data.code;
                            sensordata.value = dev_data.value;
                            sensordata.priority = 3;
                            process_event(sensordata);
                            break;             
                    }
                }
            }
        }    
    }
} 

代码中DEVICE_NODE  用于监听hid 数据的,这个后面说。

sensor数据监听

void SensorTransfer::listenThread()
{
    int64_t stamp;
    LOGD("listenThread");
    while (m_bListening)
    {
        ASensorEvent event;
        while (ASensorEventQueue_getEvents(m_pEvtQue, &event, 1) > 0)
        {
            stamp = event.timestamp;
            switch (event.type)
            {
            // case ASENSOR_TYPE_GYROSCOPE:
            //     printf("GYROSCOPE:(%llu, %f,%f,%f)\n", (unsigned long long)stamp, event.data[0], event.data[1], event.data[2]);
            //     break;
            case ASENSOR_TYPE_ACCELEROMETER:
                // printf("ACCELEROMETER: (%llu, %f,%f,%f)\n", (unsigned long long)stamp, event.data[0], event.data[1], event.data[2]);
                sensordata.stamp = stamp;
                sensordata.sensorType = 0x100;
                sensordata.xvalue = event.data[0];
                sensordata.yvalue = event.data[1];
                sensordata.zvalue = event.data[2];
                saveSensorData(sensordata);
                sensordata.priority = 1;
                break;
            case ASENSOR_TYPE_GRAVITY:
                // printf("GRAVITY: (%llu, %f,%f,%f)\n", (unsigned long long)stamp, event.data[0], event.data[1], event.data[2]);
                sensordata.stamp = stamp;
                sensordata.sensorType = 0x101;
                sensordata.xvalue = event.data[0];
                sensordata.yvalue = event.data[1];
                sensordata.zvalue = event.data[2];
                sensordata.priority = 1;
                saveSensorData(sensordata);

                break;
            case ASENSOR_TYPE_PROXIMITY:
                sensordata.stamp = stamp;
                sensordata.sensorType = 0x102;
                sensordata.lightvalue = event.data[0];
                sensordata.priority = 1;
                saveSensorData(sensordata);
                break;
            default:
                break;
            }
        }
        usleep(1000);
    }
} 

这个参考的一个博主的方案,主要是通过循环读取native sensor 数据。

监听HID节点删除添加

void HidReceiver::nodWatch(){
    int length, i = 0;
    int fd;
    int wd;
    char buffer[BUF_LEN];

    fd = inotify_init();
    if (fd < 0) {
        LOGD("inotify_init");
    }

    wd = inotify_add_watch(fd, DEV_NODE, 
                           IN_CREATE );
    if (wd < 0) {
        LOGD("inotify_add_watch");
    }

    LOGD("Monitoring directory: %s", DEV_NODE);
    bool monitor = true;
    while (monitor) {
        LOGD("start monitor");
        length = read(fd, buffer, BUF_LEN);  
        if (length < 0) {
            LOGD("read");
        }  
        i = 0;
        LOGD("read %d",length);
        while (i < length) {
            struct inotify_event *event = (struct inotify_event *) &buffer[i];
            LOGD("inotify_event %d",event->len);
            if (event->len) {
                LOGD("ievent->mask %d",event->mask);
                if (event->mask & IN_CREATE) {
                    LOGD("Created: %s", event->name);
                    if(strcmp(event->name,"hidg0") == 0){
                        LOGD("Created: hidg0");
                        monitor = false;
                        startListen();
                        inotify_rm_watch(fd, wd);
                        return;
                    }
                } else if (event->mask & IN_DELETE) {
                    LOGD("Deleted: %s", event->name);
                } else if (event->mask & IN_MODIFY) {
                    LOGD("Modified: %s", event->name);
                } else if (event->mask & IN_MOVED_FROM) {
                    LOGD("Moved from: %s", event->name);
                } else if (event->mask & IN_MOVED_TO) {
                    LOGD("Moved to: %s", event->name);
                }
            }
            i += EVENT_SIZE + event->len;
        }
    }
}

Hid 数据传输和数据丢失问题

hid 数据怎么传,其实很简单,写节点就可以了,但是数据量太大的时候,会出现写节点失败,同时,按键或者TP 等数据,也会丢失,sensor 数据丢失感知倒不是很大,但是按键和触摸这些传输失败,Host端就无法响应,体验会很差。

目前采用的方案是

Bufferqueue + 延时 解决HID 数据丢失的问题(生产者消费者模式)

priority_queue  解决用户主动操作的数据优先级问题,主要是TP 和 按键,保证优先响应

主要代码:

消费者

// 消费者线程,读取队列中的数据并发送
void SensorTransfer::consumer() {
    while (true) {
        std::unique_lock<std::mutex> lock(queueMutex);
        dataCondition.wait(lock, [this] { return !bufferQueue.empty(); });
        // if (!bufferQueue.empty())
        //     break; // 程序结束
        if(!bufferQueue.empty()){
            sensor_data data = bufferQueue.top();
            // 发送数据到 HID 设备
            int written = write(hid_fd,&data,sizeof(struct sensor_data));
            if(written >=0){
                bufferQueue.pop();
            }else{
                //LOGD("rewrite data result fail");
                std::this_thread::sleep_for(std::chrono::milliseconds(1)); // 控制发送速率
            }
            if(data.sensorType == 259){
                LOGD("consumer sn");
                std::this_thread::sleep_for(std::chrono::milliseconds(5)); 
            }
            if(data.sensorType == 260){
                LOGD("consumer KEY");
                std::this_thread::sleep_for(std::chrono::milliseconds(5)); 
            }
        }else{
            LOGD("consumer 等待");
        }
    }
}

生产者

int SensorTransfer::saveSensorData(sensor_data data) {
    //std::lock_guard<std::mutex> lock(queueMutex);
    if (bufferQueue.size() < MAX_QUEUE) { // 限制队列最大长度
        bufferQueue.push(data);
        sensor_data topdata = bufferQueue.top();
        if(topdata.sensorType != 259&&topdata.sensorType != 260){
            dataCondition.notify_one(); // 通知消费者线程
        }
    }else{
        LOGD("buffer is full");
    }
    return 0;
}

selinux 添加

这个是老一套了,之前也写过文章,可以参考这里直接贴上主要权限

新增hidtransfer.te

type hidtransfer, domain,mlstrustedsubject;
typeattribute hidtransfer coredomain;
type hidtransfer_exec, system_file_type, exec_type, file_type;
binder_use(hidtransfer)
init_daemon_domain(hidtransfer)

allow hidtransfer system_server:unix_stream_socket {read write};
allow hidtransfer tty_device:chr_file {write read getattr};
allow hidtransfer hid_device:chr_file { read getattr open ioctl write};
allow hidtransfer device:dir read;
allow hidtransfer system_server:binder call;
allow hidtransfer tty_device:chr_file ioctl;
allow hidtransfer serialno_prop:file { map getattr open read};
allow hidtransfer permission_service:service_manager find;
allow hidtransfer sensorservice_service:service_manager find;
allow hidtransfer input_device:chr_file { read write open };
allow hidtransfer input_device:dir { search };
allow hidtransfer device:dir watch;
allow hidtransfer system_server:fd use;

file_contexts

/system/bin/hidtransfer u:object_r:hidtransfer_exec:s0
/dev/hidg0 u:object_r:hid_device:s0

device.te

type hid_device,dev_type;

参考:

1.Android Native Sensor(C++)实例_sensor hal 陀螺仪读取数据实现代码-CSDN博客

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

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

相关文章

【Unity基础】Unity中Transform.forward的详解与应用

在Unity中&#xff0c;Transform.forward 是一个常用属性&#xff0c;它表示物体的“前方”方向&#xff0c;即物体本地坐标系中 Z 轴&#xff08;蓝色轴&#xff09;在世界坐标系中的方向。它动态反映物体的旋转情况&#xff0c;非常适合用于移动、检测、方向控制等场景。 什么…

基于 RWKV 的视觉语言模型 VisualRWKV 被 COLING 2025 接收!

基于 RWKV 的视觉语言模型 VisualRWKV 被 COLING 2025 接收&#xff01; COLING&#xff0c;国际计算语言学会议&#xff08;International Conference on Computational Linguistics&#xff09;&#xff0c;是自然语言处理和计算语言学领域的顶级国际会议&#xff08;CCF 推…

如何加强游戏安全,防止定制外挂影响游戏公平性

在现如今的游戏环境中&#xff0c;外挂始终是一个困扰玩家和开发者的问题。尤其是定制挂&#xff08;Customized Cheats&#xff09;&#xff0c;它不仅复杂且隐蔽&#xff0c;更能针对性地绕过传统的反作弊系统&#xff0c;对游戏安全带来极大威胁。定制挂通常是根据玩家的需求…

斯坦福李飞飞《AI Agent:多模态交互前沿调查》论文

多模态AI系统很可能会在我们的日常生活中无处不在。将这些系统具身化为物理和虚拟环境中的代理是一种有前途的方式&#xff0c;以使其更加互动化。目前&#xff0c;这些系统利用现有的基础模型作为构建具身代理的基本构件。将代理嵌入这样的环境中&#xff0c;有助于模型处理和…

Lua面向对象实现

Lua中的面向对象是通过表&#xff08;table&#xff09;来模拟类实现的&#xff0c;通过setmetatable(table,metatable)方法&#xff0c;将一个表设置为当前表的元表&#xff0c;之后在调用当前表没有的方法或者键时&#xff0c;会再查询元表中的方法和键&#xff0c;以此来实现…

flex布局容易忽略的角色作用

目录 清除浮动 作用于行内元素 flex-basis宽度 案例一&#xff1a; 案例二&#xff1a; 案例三&#xff1a; flex-grow设置权重 案例一&#xff1a; 案例二&#xff1a; 简写flex-grow:1 0 auto; flex作为一维布局,行和列的使用&#xff0c;忽略的小角色&#xff0c;大…

Arduino IDE for mac 无法加载界面

打开软件后&#xff0c;无法加载界面的问题 1.手动删除“~/Library/Arduino15”文件夹 2.终端中输入sudo nano /etc/hosts&#xff0c;在里面添加“127.0.0.1 localhost”

【短视频SEO矩阵源码开发技术解析——框架应用分享】

为了部署短视频SEO矩阵系统&#xff0c;需要遵循以下核心步骤&#xff1a;首先&#xff0c;需掌握一系列关键技术和知识&#xff0c;涵盖但不限于相关领域的专业技能。 为了确保短视频SEO矩阵系统源代码能够顺利部署&#xff0c;首先需要构建一个适宜的服务器环境。您可以选择…

探索前端世界的无限可能:玩转Excel文件

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

本地运行打包好的dist

首先输入打包命令 每个人设置不一样 一般人 是npm run build如果不知道可以去package.json里去看。 打包好文件如下 命令行输入 :npm i -g http-server 进入到dist目录下输入 命令cmd 输入 http-server 成功

鸿蒙 DevEco Studio 设置状态栏,调用setWindowSystemBarProperties不生效

参考文章&#xff1a;设置状态栏&#xff0c;调用setWindowSystemBarProperties不生效 我使用 setWindowSystemBarProperties 设置状态栏&#xff0c;不生效。 import window from ohos.window;export default {data: {title: World},setSystemBar() {var windowClass null;…

MacOS安装软件后无法启动报错:“已损坏,无法打开,你应该将它移到废纸篓“

目录 报错截图 解决方法 知识科普 报错截图 解决方法 1. 打开系统设置->安全性与隐私->选择任何来源 2. 如果打开没有看到"任何来源"&#xff0c;如果不开启“任何来源”的选项&#xff0c;会直接影响到无法运行的第三方应用。开启“任何来源”的方法如下&a…

Linux-用户和权限

文章目录 一. 用户1. 用户分类① root用户(超级管理员)② 普通用户Ⅰ. 创建普通用户命令 ③ root用户与普通用户Ⅰ. 权限区别Ⅱ. 切换用户命令Ⅲ. sudo命令Ⅳ. 为普通用户配置sudo认证 2. 用户组① 用户,用户组② 创建用户组命令② 删除用户组命令② 用户管理命令③ getent 二.…

Flutter动画(二)内建隐式动画Widget

动画效果介绍中给出了选择动画的决策树&#xff1a; 使用动画框架不在我们讨论的话题内。flutter支持的动画包括隐式动画和显式动画。 隐式动画和显式动画 隐式动画和显示动画是两种不同的动画实现方式&#xff0c;它们的主要区别在于控制权和动画的重复性。 隐式动画&#…

【笔记2-5】ESP32:freertos消息队列

主要参考b站宸芯IOT老师的视频&#xff0c;记录自己的笔记&#xff0c;老师讲的主要是linux环境&#xff0c;但配置过程实在太多问题&#xff0c;就直接用windows环境了&#xff0c;老师也有讲一些windows的操作&#xff0c;只要代码会写&#xff0c;操作都还好&#xff0c;开发…

211高校的VMware迁移之路:迁至深信服云平台,更高效、更稳定

某211高校为国家 “双一流” 建设高校、省一流大学&#xff0c;在教育领域占据举足轻重的地位。其教学单位构成丰富多元&#xff0c;学科体系广泛而全面。然而&#xff0c;学校面临着VMware虚拟化平台维保到期、服务器老化等严峻挑战&#xff0c;严重干扰了教学、科研及管理工作…

【Matlab】将所有打开的图像批量保存为JPG格式

将Matlab中所有打开的图像批量保存为JPG格式 前言一、实现步骤1. 获取所有打开的图像句柄2. 遍历并保存图像 总结 前言 在使用Matlab进行数据分析或图像处理时&#xff0c;我们经常会生成多个图像以便观察和比较。有时&#xff0c;为了方便分享或存档&#xff0c;我们需要将这…

Linux实现地址转换和抓包

1.Linux实现地址转换 1.1 SNAT和DNAT NAT:地址转换SNAT:源地址转换DNAT:目的地址转换 内网——》外网&#xff1a;内网色的ip不能直接和公网ip通信&#xff0c;必须要把内网的地址转换成和公网ip通信的地址 外网——》内网&#xff1a;外网也不能直接和内网通信&#xff0c…

RocketMQ 过滤消息 基于tag过滤和SQL过滤

RocketMQ 过滤消息分为两种&#xff0c;一种tag过滤&#xff0c;另外一种是复杂的sql过滤。 tag过滤 首先创建producer然后启动&#xff0c;在这里创建了字符串的数组tags。字符串数组里面放置了多个字符串&#xff0c;然后去发送15条消息。 15条消息随着i的增长&#xff0c;…

[Redis#14] 持久化 | RDB | bgsave | check-rdb | 灾备

目录 0.概述 持久化的策略 1 RDB 1.1 触发机制 1.2 流程说明 1.3 RDB 的优缺点 0.概述 在学习 MySQL 数据库时&#xff0c;我们了解到事务的四个核心特性&#xff1a;原子性、一致性、持久性和隔离性。这些特性确保了数据库操作的安全性和可靠性。当我们转向 Redis 时&a…