详解OpenHarmony Sensor模块订阅和回调 原创 精华

深开鸿
发布于 2022-4-14 13:57
浏览
5收藏

作者:黄昊

本文是为了和大家分享sensor从应用到框架层的实现,重点分析了传感器service启动、应用订阅、及接收订阅传感器数据的流程,并做了较为详细的代码说明,希望通过本文您能详细了解传感器模块的JS应用,Framework,以及驱动层是如何交互。

详解OpenHarmony Sensor模块订阅和回调-鸿蒙开发者社区

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

*前言*

传感器是鸿蒙系统中的众多组件之一,用于侦测环境中所发生的事件和变化。应用可以通过订阅某个传感器,从而当环境发生变化及时获取相关的状态信息。达到为各产品/业务提供低时延、低功耗的感知数据的目的。根据用途可分为以下六大类:

运动类:加速度、陀螺仪、重力、线性加速度传感器等

姿态类:旋转矢量、方向传感器等

环境类:磁力计、气压、湿度传感器等

光线类:环境光、接近光、色温传感器等

健康类:心率、心跳传感器等

其它:霍尔传感器、手握传感器等

*架构层级*

OpenHarmony架构图:

详解OpenHarmony Sensor模块订阅和回调-鸿蒙开发者社区

传感器架构图如下所示:

详解OpenHarmony Sensor模块订阅和回调-鸿蒙开发者社区

目录

sensor导入模块的示例代码如下:
/base/sensors/sensor
├── frameworks # 框架代码
│ └── native # sensor客户端代码
├── interfaces # 对外接口存放目录
│ ├── native # sensor native实现
│ └── plugin # Js API
├── sa_profile # 服务名称和服务的动态库的配置文件
├── services # 服务的代码目录
│ └── sensor # 传感器服务,包括加速度、陀螺仪等,上报传感器数据
└── utils # 公共代码,包括权限、通信等能力

源码分析

根据官方给出的架构,从上到下使用了三种语言,层用层使用JS,中间层(SDK,Sensor Framework,Sensor Service)用C++语言,驱动层(HDF,Hardware)使用C语言。

传感器的注册参数在各个模块中的传递过程分四步:

1 JS应用输入参数(传感器ID,时间间隔,最大延迟,回调函数)

2 SDK层将JS参数通过NApi转化为C++结构体,将订阅者管理起来,并建立Client端和Sensor Service端的链接桥梁

3 通过IPC通信,将注册信息传递给Sensor Service

4 Sensor Service通过HDF框架将参数传递给驱动层

SensorService启动阶段

在介绍Sensor订阅的流程之前,需要先讲下service启动的时候做了哪些工作。因为Service的启动流程是在订阅流程之前的,需要了解它做了哪些工作,以便更好的理解订阅流程。

每一个服务在OS中都是一个独立的进程,传感器服务当然也不例外,在系统启动的时候,传感器服务SensorService就会被拉起。首先它会从Sensor驱动中获取相关信息,比如:硬件支持的传感器列表SensorList,每个传感器支持的最大采集周期和最小采集周期,每个传感器支持最大订阅者的数量。SensorService将这些信息保存在自身的内存中。且与Sensor的驱动相关联(能够调到驱动的接口)。同时注册SensorService的Callback到驱动中。

也就是说,在启动阶段,SensorService和底层驱动之前的桥梁已经搭建好,SensorService可以调用驱动接口,驱动也可以将传感数据回调给SensorService。而这些和应用是否调用订阅没有关系。这对于后面理解传感信息从驱动经过多层一直回调到JS层很重要。

应用订阅和传感数据回调阶段

下面我会用一个简单模型来说明应用订阅传感器和传感数据上报流程,先有助于理解,后面会结合代码详细讲解订阅流程:

应用订阅传感器

Js调用订阅流程,其实就是Js往SensorService发送订阅请求的过程,Js将Callback往下层注册。

当然,并不是简单的将Js的回调函数直接注册到SensorService中。SensorService也不会直接调用Js的callback。

中间会经历多次回调注册,打个简单的比方来说就是 Js.Callback() 注册到 FrameworkA模块,FrameworkA模块的回调函数 A.Callback()注册到FrameworkB模块,FrameworkB模块的回调函数 B.Callback() 注册到FrameworkC模块,FrameworkC模块的回调函数 C.Callback() 注册到driver驱动模块。

传感数据回调给应用

SensorService上报传感数据的过程,也可以理解为SensorService回调Js Callback的过程。

回调过程也并不是简单的驱动直接回调Js的Callback(),而是驱动首先回调C.Callback(),C.Callback()回调B.Callback(), B.Callback()回调A.Callback(),A.Callback() 最终回调Js.Callback() 从而上报消息。

FrameworkC.Callback(){ FrameworkB.Callback(); }

FrameworkB.Callback(){ FrameworkA.Callback(); }

FrameworkA.Callback(){ Js.Callback(); }

订阅流程代码逻辑展示

下面结合实际代码来看下传感器的订阅流程:

JS接口
详解OpenHarmony Sensor模块订阅和回调-鸿蒙开发者社区
(https://s5.51cto.com/oss/202111/08/1501b8d7822c3e04ff2fec3fc4cbafb3.png)

由于网页编辑工具将整图插入后图片会变小,且放大后字迹模糊,所以只能将图片分块展示再此处,分client端,和service端进行讲解:

client端

详解OpenHarmony Sensor模块订阅和回调-鸿蒙开发者社区

详解OpenHarmony Sensor模块订阅和回调-鸿蒙开发者社区

1 Js调用On函数,通过NApi调用到Framework的sensor_js模块的On()函数,sensor_js.cpp里面所有的代码,都是和Js直接交互的。

2 On()解析Js传递过来的参数,并解析成C语言可以识别的数据类型,作为参数传递给SubScribeSensor()。

3 sensor_js模块的SubScribeSensor()会调用SensorAgent模块SubScribeSensor()。此函数做了三件事:

3.1 本Js进程(client)订阅Sersor服务

3.1.1 调用SensorAgentProxy::CreateSensorDataChannel()。

3.1.1.1 调用SensordataChannel::CreateSensorDataChannel(),创建socket_pair,建立socket通信通道,本端保留RecvFd_。并监听RecvFd_。

3.1.1.2 调用SensorServiceClinet::TransferDataChannel(),将本端的SendFd_ 和ClientStub_发送到service。SendFd_用于给Service端建立通信通道,ClientStub是用于给服务端感知client是否已经死亡。

通过SystemAbility相关接口获取SensorServiceProxy用于和Service进行IPC通信。

调用SensorServiceProxy::TransferDataChannel(),这里开始IPC通信,将SendFd_ 和 ClientStub_打包,通过SendRequest()方法,将包发送到SensorService。并关闭本端的SendFd_,只保留RecvFd_,这样本端只接收消息(从Service上报的消息)

3.2 根据Js传入的参数(采集周期),设置传感器的采集周期。

设置全局变量g_samplingInterval(采样周期)和 g_reportInterval(最大延迟,默认值为0,并未给set接口)。这两个变量会在3.3流程中使用到。

3.3 使能(打开)传感器

调用SensorAgentProxy::ActivateSensor (),初步判断参数是否合法,内部调用SensorServiceClient::EnableSensor()。

SensorServiceClient::EnableSensor()通过SystemAbility相关接口获取SensorServiceProxy用于和Service进行IPC通信。

调用SensorServiceProxy::EnableSensor ()打包从Js获取的参数:sensorId,g_samplingInterval(采样周期)和 g_reportInterval,

并将包通过SendRequest()发送到SensorService。

service端

详解OpenHarmony Sensor模块订阅和回调-鸿蒙开发者社区

详解OpenHarmony Sensor模块订阅和回调-鸿蒙开发者社区

在srevice端存在一个map(key:MessageId,value:消息处理handle )

std::unordered_map<uint32_t, SensorBaseFunc> baseFuncs_;

一系列处理handle:

ErrCode SensorEnableInner(MessageParcel &data, MessageParcel &reply);

ErrCode SensorDisableInner(MessageParcel &data, MessageParcel &reply);

ErrCode GetSensorStateInner(MessageParcel &data, MessageParcel &reply);

ErrCode RunCommandInner(MessageParcel &data, MessageParcel &reply);

ErrCode GetAllSensorsInner(MessageParcel &data, MessageParcel &reply);

ErrCode CreateDataChannelInner(MessageParcel &data, MessageParcel &reply);

ErrCode DestroyDataChannelInner(MessageParcel &data, MessageParcel &reply);

在构造函数中,初始化了baseFuncs_,这样可以根据发送过来的MessageId,直接定位到对应的处理handle。

SensorServiceStub::SensorServiceStub()
{
HiLog::Info(LABEL, "%{public}s begin, %{public}p", __func__, this);
baseFuncs_[ENABLE_SENSOR] = &SensorServiceStub::SensorEnableInner; baseFuncs_[DISABLE_SENSOR] = &SensorServiceStub::SensorDisableInner;
baseFuncs_[GET_SENSOR_STATE] = &SensorServiceStub::GetSensorStateInner; baseFuncs_[RUN_COMMAND] = &SensorServiceStub::RunCommandInner;
baseFuncs_[GET_SENSOR_LIST] = &SensorServiceStub::GetAllSensorsInner; baseFuncs_[TRANSFER_DATA_CHANNEL] = &SensorServiceStub::CreateDataChannelInner;
baseFuncs_[DESTROY_SENSOR_CHANNEL] = &SensorServiceStub::DestroyDataChannelInner;
}

client端往service发送了两次IPC消息:

第一次IPC消息是3.1.1.2发送过来的TransferDataChannel消息。

根据上面的map,可以找到对应的处理handle:SensorServiceStub::CreateDataChannelInner()

SensorServiceStub::CreateDataChannelInner()调用了SensorBasicDataChannel::CreateSensorBasicChannel()建立通信链接,其实就是将对端创建并发过来的SendFd_保存在本端的channel中。

接着SensorServiceStub::CreateDataChannelInner()调用了SensorService::TransferDataChannel()。保存调用者的相关信息(比如PId,Uid),channel。更新ClientInfo的数据结构。

第二次IPC消息是3.3发送过来的EnableSensor 消息,根据上面的map,可以找到对应的处理handle:SensorServiceStub::SensorEnableInner()

SensorServiceStub::SensorEnableInner()首先解析消息体中的SensorId,决定打开哪个Sensor,然后,会检查SensorId是否在黑名单sensorIdPermissions_中,如果不在,继续下面的流程。

SensorServiceStub::SensorEnableInner()接着调用SensorService::EnableSensor(),

里面做了两件事:

1 解析消息中的SensorId,以便确定打开哪个Sensor,根据SensorId来判断是否有打开权限(判断SensorId是否在黑名单sensorIdPermissions_中),接下来会判断从消息中解析出用户设置的采样周期和最大延迟。此时会根据策略来设置一个合适的采样周期和最大延迟,也就是说不同的应用针对同一个Sensor虽然设置了不同的采样周期,但是他们最后收到的消息是按照同一个最优采样频率来收到结果的。这个最优采样周期是如何获得的,请参考本文结尾的扩展部分(SenserService选取最优采样周期的策略)。

2 调用SensorServiceImpl::EnableSensor(),SensorServiceImpl是和驱动打交道的类,此类中有一个SensorInterface的成员,里面定义了驱动所支持的所有操作的。接下来就可以通过SensorInterface来调用驱动,打开传感器了。

以上,就是应用订阅传感器的全部流程。

传感信息回调流程代码逻辑展示

概括的讲,从sensor驱动到Js层的回调流程大体分为五部分:

驱动层触发 --> service端回调 --> 通过socket发送消息 --> Client端收到消息触发 --> Js回调

以下只展示回调过程,且只展示关键代码:

驱动层触发 --> service端回调

1 当驱动层有消息上报,首先回调:SensorServiceImpl::SensorDataCallback

int32_t SensorServiceImpl::SensorDataCallback(const struct SensorEvents *event) { //调用上一层的回调函数 (void)(reportDataCallback_->*reportDataCb_)(reinterpret_cast<const struct SensorEvent*>(event), reportDataCallback_);
//使阻塞的上报线程继续运行
dataCondition_.notify_one();
}

2 SensorServiceImpl::SensorDataCallback回调ReportDataCallback::ZReportDataCallback

int32_t ReportDataCallback::ZReportDataCallback(const struct SensorEvent* event, sptr<ReportDataCallback> cb) { struct SensorEvent eventCopy = { .sensorTypeId = event->sensorTypeId, .version = event->version, .timestamp = event->timestamp, .option = event->option, .mode = event->mode, .dataLen = event->dataLen }; eventCopy.data = new uint8_t[SENSOR_DATA_LENGHT]; if (memcpy_s(eventCopy.data, event->dataLen, event->data, event->dataLen) != EOK) { HiLog::Error(LABEL, "%{public}s copy data failed", __func__); return COPY_ERR; } //操作 ReportDataCallback::eventsBuf_(循环读写的一个SensorEvent数组) int32_t leftSize = CIRCULAR_BUF_LEN - cb->eventsBuf_.eventNum; int32_t toEndLen = CIRCULAR_BUF_LEN - cb->eventsBuf_.writePosition; if (toEndLen == 0) { cb->eventsBuf_.circularBuf[0] = eventCopy; cb->eventsBuf_.writePosition = 1 - toEndLen; } else { cb->eventsBuf_.circularBuf[cb->eventsBuf_.writePosition] = eventCopy; cb->eventsBuf_.writePosition += 1; } if (leftSize < 1) { cb->eventsBuf_.readPosition = cb->eventsBuf_.writePosition; } cb->eventsBuf_.eventNum += 1; if (cb->eventsBuf_.eventNum >= CIRCULAR_BUF_LEN) { cb->eventsBuf_.eventNum = CIRCULAR_BUF_LEN; } if (cb->eventsBuf_.writePosition == CIRCULAR_BUF_LEN) { cb->eventsBuf_.writePosition = 0; } return ERR_OK; }

service端回调 --> 通过socket发送消息

3 服务端上报线程解开阻塞,遍历ReportDataCallback::eventsBuf_,将待处理的Events,通过socket,发送到客户端。

//服务端的上报线程
int32_t SensorDataProcesser::ProcessEvents(sptr<ReportDataCallback> dataCallback) { ... std::unique_lock<std::mutex> lk(SensorServiceImpl::dataMutex_); SensorServiceImpl::dataCondition_.wait(lk); auto &eventsBuf = dataCallback->GetEventData(); ... int32_t eventNum = eventsBuf.eventNum; for (int32_t i = 0; i < eventNum; i++) { //处理待处理的Events EventFilter(eventsBuf); ... } return SUCCESS; }

void SensorDataProcesser::EventFilter(struct CircularEventBuf &eventsBuf) { SendEvents(channel, eventsBuf.circularBuf[eventsBuf.readPosition]); }

int32_t SensorDataProcesser::SendEvents(sptr<SensorBasicDataChannel> &channel, struct SensorEvent &event) { ... ReportData(channel, event); ... }

void SensorDataProcesser::ReportData(sptr<SensorBasicDataChannel> &channel, struct SensorEvent &event) { ... auto fifoCount = clientInfo_.ComputeBestFifoCount(sensorId, channel); if (fifoCount <= 0) { SendNoneFifoCacheData(cacheBuf, channel, event, periodCount); return; } SendFifoCacheData(cacheBuf, channel, event, periodCount, fifoCount); ... }

void SensorDataProcesser::SendFifoCacheData(std::unordered_map<uint32_t, struct SensorEvent> &cacheBuf, sptr<SensorBasicDataChannel> &channel, struct SensorEvent &event, uint64_t periodCount, uint64_t fifoCount) { ... SendRawData(cacheBuf, channel, fifoData->GetFifoCacheData()); ... }

void SensorDataProcesser::SendRawData(std::unordered_map<uint32_t, struct SensorEvent> &cacheBuf, sptr<SensorBasicDataChannel> channel, std::vector<struct SensorEvent> event) { ... auto ret = channel->SendData(transferEvents.data(), eventSize * sizeof(struct TransferSensorEvents)); ... }

int32_t SensorBasicDataChannel::SendData(const void *vaddr, size_t size) { ... do { //调用socket发送消息 length = send(sendFd_, vaddr, size, MSG_DONTWAIT | MSG_NOSIGNAL); } while (errno == EINTR); ... }

通过socket发送消息 --> Client端收到消息触发

4 客户端的监听者会走到OnReadable函数,有可读消息。

void MyFileDescriptorListener::OnReadable(int32_t fileDescriptor) { ... int32_t len = recv(fileDescriptor, receiveDataBuff_, sizeof(struct TransferSensorEvents) * RECEIVE_DATA_SIZE, NULL); while (len > 0) { int32_t eventSize = sizeof(struct TransferSensorEvents); int32_t num = len / eventSize; for (int i = 0; i < num; i++) { SensorEvent event = { .sensorTypeId = receiveDataBuff_[i].sensorTypeId, .version = receiveDataBuff_[i].version, .timestamp = receiveDataBuff_[i].timestamp, .option = receiveDataBuff_[i].option, .mode = receiveDataBuff_[i].mode, .dataLen = receiveDataBuff_[i].dataLen, .data = receiveDataBuff_[i].data }; //往上层回调, 回调的是在CreateSensorDataChannel()时注册的SensorAgentProxy::HandleSensorData channel_->dataCB_(&event, 1, channel_->privateData_); } len = recv(fileDescriptor, receiveDataBuff_, sizeof(struct TransferSensorEvents) * RECEIVE_DATA_SIZE, NULL); } }

MyFileDescriptorListener::OnReadable 回调 SensorAgentProxy::HandleSensorData

void SensorAgentProxy::HandleSensorData(struct SensorEvent *events, int32_t num, void *data) { ... struct SensorEvent eventStream; for (int32_t i = 0; i < num; ++i) { eventStream = events[i]; ... g_subscribeMap[eventStream.sensorTypeId]->callback(&eventStream); } }

Client端收到消息触发 --> Js回调

SensorAgentProxy::HandleSensorData 回调 sensor_js.cpp 的 DataCallbackImpl

static void DataCallbackImpl(SensorEvent *event) { ... //查询订阅应用列表,触发订阅应用的回调, g_onCallbackInfos中保存的着JS On回调函数指针 for (auto &onCallbackInfo: g_onCallbackInfos) { if ((int32_t)onCallbackInfo.first == sensorTypeId) { ... EmitAsyncCallbackWork((struct AsyncCallbackInfo *)(onCallbackInfo.second)); } } ... //查询调用Once应用列表,触发Once回调, g_onceCallbackInfos中保存着Js Once回调函数指针 struct AsyncCallbackInfo *onceCallbackInfo = g_onceCallbackInfos[sensorTypeId]; EmitAsyncCallbackWork((struct AsyncCallbackInfo *)(onceCallbackInfo)); ... }

Js回调指针时如何保存(注册)在C的结构体中?

static napi_value On(napi_env env, napi_callback_info info) { HiLog::Info(LABEL, "%{public}s in", __func__); size_t argc = 3; napi_value args[3]; napi_value thisVar; NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, &thisVar, NULL)); ... AsyncCallbackInfo *asyncCallbackInfo = new AsyncCallbackInfo { .env = env, .asyncWork = nullptr, .deferred = nullptr, }; //args[1] 就是Js的回调函数,通过NApi建立引用。 napi_create_reference(env, args[1], 1, &asyncCallbackInfo->callback[0]); g_onCallbackInfos[sensorTypeId] = asyncCallbackInfo; }

以上便是传感信息回调的全部流程。

扩展部分

Sensor模块中的通信方式

要理解订阅流程,需要鸿蒙IPC模式的知识储备,因为SensorClient发送消息到SensorService的流程是通过IPC通信。

也需要socket相关的知识基础,因为SensorService上报消息是通过socket通信来实现。

至于为什么需要两种通信方式,个人理解是因为SensorClient发送消息到SensorService的频度并不高,只有用户主动调用Js接口的时候才会用到,无需保持常链接。 而SensorService上报消息是需要周期性上报,频度比较高,周期长。需要保持常链接。而socket更符合该场景。

在代码中这个socket通道叫做SensorDataChannel。其实就是一个socket_pair, 发送端SendFd_在SensorService侧, RecvFd_在SensorClient端。

鸿蒙IPC的介绍

详解OpenHarmony Sensor模块订阅和回调-鸿蒙开发者社区

本例是代码中的实际代码:

定义了一个接口类ISensorService继承自IRemoteBroker,里面有一些需要子类实现的虚方法EnableSensor(),DisableSensor()等方法。

定义了一个SensorServiceProxy类 继承于ISensorService,里面实现了所有ISensorSercice的方法, 此处用了代理的设计模式(当你想使用一个类A时,但是类的编写者处于安全考虑,不希望你直接使用A类,于是构造一个形式上一摸一样的类ProxyA,给你使用,而你感知不到和用A类有何区别)。内部实现是通过将参数打包,再将包通过SendRequest()发送到SensorServiceStub,由SensorServiceStub来进行处理。

定义一个SensorServiceStub类继承自ISensorService,实现OnRemoteRequest(),OnRemoteRequest会根据收到的消息ID,来进行不同的处理。

SensorServiceProxy 和 SensorServiceStub 分别在不同的进程,SensorServiceProxy 在client端, SensorServiceStub在service端。

由于 SensorServiceStub在service端,就可以和驱动打交道,从而可以将client端的请求通过IPC传递service,然后service再传递给驱动层,完成传感器的打开,关闭,设置传感器选项等功能。

驱动的调用方式

首先驱动会定义自身所支持的操作(函数指针):

struct SensorInterface { int32_t (*GetAllSensors)(struct SensorInformation **sensorInfo, int32_t *count); int32_t (*Enable)(int32_t sensorId); int32_t (*Disable)(int32_t sensorId); int32_t (*SetBatch)(int32_t sensorId, int64_t samplingInterval, int64_t reportInterval); int32_t (*SetMode)(int32_t sensorId, int32_t mode); int32_t (*SetOption)(int32_t sensorId, uint32_t option); int32_t (*Register)(RecordDataCallback cb); int32_t (*Unregister)(void); };

再由具体驱动来按照参数列表和返回值,来实现具体操作

sensor驱动的实现部分是在sensor_controller.c中。

比如 Enable 的实现:

static int32_t EnableSensor(int32_t sensorId) { HDF_LOGE(" %{public}s in", __func__); ... if (ret != SENSOR_SUCCESS) { HDF_LOGE("hhtest -driver- %{public}s: Sensor enable failed, ret[%{public}d]", __func__, ret); } HdfSBufRecycle(msg); ... return ret; }

再比如Register的实现:

int32_t Register(RecordDataCallback cb) { HDF_LOGE("%{public}s in", __func__); struct SensorDevManager *manager = NULL; manager = GetSensorDevManager(); manager->recordDataCb = cb; return AddSensorDevServiceGroup(); }

然后通过赋值的方式,将这些函数实现赋值给SensorInterface

void GetSensorDeviceMethods(struct SensorInterface *device) { CHECK_NULL_PTR_RETURN(device); device->GetAllSensors = GetSensorInfo; device->Enable = EnableSensor; device->Disable = DisableSensor; device->SetBatch = SetSensorBatch; device->SetMode = SetSensorMode; device->SetOption = SetSensorOption; device->Register = Register; device->Unregister = Unregister; }

这样Service就可以通过SensorInterface来调用驱动的Enable,Disable,Register等方法了。

SenserService选取最优采样周期的策略

首先判断采样周期SamplingPeriod,是否在驱动的支持范围(min, max)之内。否在驱动的支持范围(min, max)之内。

如果小于min, 采样周期就取值min,

如果大于max, 采样周期就取值max,

如果在范围之内,本应用所对应的采样周期就取当前值SamplingPeriod

并且将当前应用的callingPid 和 SamplingPeriod 作为keypair保存在一个map中。如果多个应用调用同一个sensorID的 On 订阅流程, 上面的map 会有多组pair。

接着,遍历这个map,选取SamplingPeriod 最小的值, 和 参数SamplingPeriod比较, 选取较小者作为sensor的采样周期。

更多原创内容请关注:深开鸿技术团队

入门到精通、技巧到案例,系统化分享HarmonyOS开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2022-4-14 13:57:25修改
6
收藏 5
回复
举报
回复
    相关推荐