Android hid 数据传输(device 端 )

最近一直在处理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博客

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

在玩《黑神话:悟空》时游戏画面卡顿是什么原因?游戏画面卡顿要怎么解决?

《黑神话&#xff1a;悟空》游戏画面卡顿问题解析与解决方案 在探索《黑神话&#xff1a;悟空》这款引人入胜的游戏时&#xff0c;玩家可能会遇到游戏画面卡顿的困扰。本文将深入剖析《黑神话&#xff1a;悟空》游戏画面卡顿的原因&#xff0c;并提供实用的解决方法。 游戏画面…

小迪安全笔记 第四十四天 sql盲注 实战利用sql盲注 进行漏洞的利用

sql盲注的分类 什么是盲注 就是我们什么也不知道的情况下进行的注入 前边的注入 都是简单的注入 我们猜测 数据类型 之后 可以直接 union 去查 这种情况多用于 数据库增删查改中的 查 bool盲注也用于查 这个的情况的就是我们前边都试了 没有用 就需要…

【开源】A063—基于Spring Boot的农产品直卖平台的设计与实现

&#x1f64a;作者简介&#xff1a;在校研究生&#xff0c;拥有计算机专业的研究生开发团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看项目链接获取⬇️&#xff0c;记得注明来意哦~&#x1f339; 赠送计算机毕业设计600个选题ex…

mac port 安装redis 并设置为系统服务 自定义配置方法

mac系统中&#xff0c;port 包管理工具比brew的速度快N倍&#xff0c;今天就给大家分享一下在macos系统中如何使用 port安装 redis数据库并配置为服务自动启动和自定义redis.conf配置的方法。 1. 安装redis sudo port install redis 2. 启动redis服务 sudo port load redis …

在Docker中部署禅道,亲测可用

1、确保centos中已安装docker docker -v 2、启动docker systemctl start docker 3、可设置docker开机启动 systemctl enable docker.service 4、获取最新版禅道开源版镜像 docker pull idoop/zentao 5、运行镜像生成禅道容器【创建 /data/www /data/data 目录】 doc…

聚合支付系统官方个人免签系统三方支付系统稳定安全高并发

系统采用fastadmin框架独立全新开发&#xff0c;安全稳定,系统支持代理、商户、码商等业务逻辑。 针对最近一-些JD&#xff0c;TB等业务定制&#xff0c;子账号业务逻辑API 非常详细&#xff0c;方便内置对接! 注意&#xff1a;系统没有配置文档很使用教程&#xff0c;不清楚…

Flutter项目初始化android版

1、本机系统环境 window11flutter sdk 版本flutter_windows_3.24.5-stable.zip (下载地址&#xff1a;https://docs.flutter.dev/get-started/install/windows/mobile)android studio 版本 Android Studio Giraffe | 2022.3.1vscode 2、VSCODE 配置flutter 安装flutter插件 …

(四)lerobot开源项目的主从臂的远程操作(带相机)(操作记录)

目录 《项目简介》 一、​B站视频参考&#xff08;推荐&#xff09; 二、确定两个usb相机的端口号 三、远程操作&#xff08;带相机&#xff09; 四、遇到问题 《项目简介》 项目地址&#xff1a;GitHub - huggingface/lerobot: &#x1f917; LeRobot: Making AI for Ro…

【HarmonyOS开发】超详细的ArkTS入门

安装DevEco Studio和新建项目就不多说了&#xff0c;可以移步官网 就可以把他们拆成这几个部分了&#xff0c;如果看不懂可以暂时忽略下面冒号后面的内容 装饰器&#xff1a;用于装饰类、结构、方法以及变量&#xff0c;并赋予其特殊的含义。如上述示例中Entry、Component和St…

ByConity ELT 小测

借着 ByConity 的邀测活动体验一下 bsp 能力&#xff0c;ByConity 也很贴心的提供了标准环境&#xff0c;下面开始体验。 测试环境 版本 配置 ByConity v1.0.1 集群规格 Worker&#xff1a;4 * 16core 64G Server&#xff1a;1 * 16core 64G TSO&#xff1a;1 * 4core 16…

【Android】从事件分发开始:原理解析如何解决滑动冲突

【Android】从事件分发开始&#xff1a;原理解析如何解决滑动冲突 文章目录 【Android】从事件分发开始&#xff1a;原理解析如何解决滑动冲突Activity层级结构浅析Activity的setContentView源码浅析AppCompatActivity的setContentView源码 触控三分显纷争&#xff0c;滑动冲突…

【C#】NET 9中LINQ的新特性-CountBy

前言 在 .NET 中,使用 LINQ 对元素进行分组并计算它们的出现次数时,需要通过两个步步骤。首先,使用 GroupBy方法根据特定键对元素进行分类。然后,再计算每个组元素包含个数。而随着 .NET 9 版本发布,引入了一些新特性。其中 LINQ 引入了一种新的方法 CountBy,本文一起来了…

【C++】深入理解 if-else 语句

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;if-else 语句基础概述1.1 if 语句1.2 else 语句注意事项&#xff1a; 1.3 单条语句和语句块的区别 &#x1f4af;else if 语句与嵌套 if2.1 else if 的本质2.2 else if 的优…

十七、监控与度量-Prometheus/Grafana/Actuator

文章目录 前言一、Spring Boot Actuator1. 简介2. 添加依赖2. 开启端点3. 暴露端点4. 总结 二、Prometheus1. 简介2. Prometheus客户端3. Prometheus服务端4. 总结 三、Grafana1. 简介2. Grafana安装3. Grafana配置 前言 系统监控‌ 在企业级的应用中&#xff0c;系统监控至关…

在c#控制台中使用Raylib-cs库,绘制控制小球和插入音频(附带c++中小球的控制代码)

下载网址 GitHub - chrisdill/raylib-cs: C# bindings for raylib, a simple and easy-to-use library to learn videogames programming 克隆库 克隆GitHub仓库-CSDN博客 1 .制作dll 点击 生成之后就会多出这些东西 2.在项目中添加dll 然后就导进来了 测试一下用例代码 …

400G智算网络助力知名自动驾驶企业算力训练提效

根据Gartner的最新趋势预测&#xff0c;自动驾驶技术正迅速发展&#xff0c;预计在未来几年内将带来显著的商业效益&#xff0c;特别是在决策智能和边缘人工智能领域。目前&#xff0c;一家领军企业正积极拥抱基于大模型的数字化转型之路&#xff0c;作为自动驾驶领域的佼佼者&…

STM32编码器接口及编码器测速模板代码

编码器是什么&#xff1f; 编码器是一种将角位移或者角速度转换成一连串电数字脉冲的旋转式传感 器&#xff0c;我们可以通过编码器测量到底位移或者速度信息。编码器从输出数据类型上 分&#xff0c;可以分为增量式编码器和绝对式编码器。 从编码器检测原理上来分&#xff0…

数组常见查找算法

文章目录 时间复杂度1. 顺序查找&#xff08;Linear Search&#xff09;2. 二分查找&#xff08;Binary Search&#xff09;3. 插值查找&#xff08;Interpolation Search&#xff09;4.分块查找5.哈希查找 时间复杂度 衡量算法执行时间随输入规模增长而增长的速度的一个概念。…

网际协议(IP)与其三大配套协议(ARP、ICMP、IGMP)

网际协议&#xff08;Internet Protocol&#xff0c;IP&#xff09;&#xff0c;又称互联网协议。是OSI中的网络层通信协议&#xff0c;用于跨网络边界分组交换。它的路由功能实现了互联互通&#xff0c;并从本质上建立了互联网。网际协议IP是 TCP/IP 体系中两个最主要的协议之…

CountDownLatch、Semaphore和Cyclicbarrier

文章目录 前言一、CountDownLatch1、概念2、源码3、代码示例&#xff1a;4、应用场景&#xff1a; 二、Semaphore1.概念2.源码3.代码示例4.应用场景 三、Cyclicbarrier1、简介2、源码3、代码示例4、应用场景&#xff1a;5、CyclicBarrier对比CountDownLatch 总结 前言 countDo…