OpenHarmony 通俗易懂讲解Sensor订阅流程

恢弘志士之气,不宜妄自菲薄。这篇文章主要讲述OpenHarmony 通俗易懂讲解Sensor订阅流程相关的知识,希望能为你提供帮助。
?作者:黄昊?
Sensor 架构
虽然鸿蒙官方API给出的Sensor架构是分了多层,但是个人更喜欢将其按照个人理解分为三层:
上层用JS,中间层用C/C++语言,底层驱动使用C语言

传感器的注册流程也同样经过上面所说的三步走:
1 Js接口调用,接口参考API介绍

接口名
描述
on(type: SensorType, callback: AsyncCallback< Response> , options?: Options)
监听传感器数据变化。SensorType为支持订阅的传感器类型,callback表示订阅传感器的回调函数,options为设置传感器数据上报的时间间隔。
once(type: SensorType, callback: AsyncCallback< Response> )
监听传感器数据变化一次。SensorType为支持订阅的传感器类型,callback表示订阅传感器的回调函数。
off(type: SensorType, callback: AsyncCallback< void> )
取消订阅传感器数据。SensorType为支持的取消订阅的传感器类型,callback表示取消订阅传感器是否成功。
2 Framework层的逻辑处理(保存订阅者信息,保存sensor信息,保存Js回调)和数据透传(将Js层传递的参数透传给驱动层,以便打开,关闭,订阅传感器)
3 驱动层处理(打开,关闭传感器,定期上报感知信息)
目录
/base/sensors/sensor
├── frameworks# 框架代码
│└── native# sensor客户端代码
├── interfaces# 对外接口存放目录
│├── native# sensor native实现
│└── plugin# Js API
├── sa_profile# 服务名称和服务的动态库的配置文件
├── services# 服务的代码目录
│└── sensor# 传感器服务,包括加速度、陀螺仪等,上报传感器数据
└── utils# 公共代码,包括权限、通信等能力



各层之间的数据是如何传递
上层Js到中间层Framework 是通过NApi来处理的,NApi可以将Js传递的参数转化为C语言可以识别的数据类型。
如果对NApi感兴趣的朋友,可以参考NApi的官方文档:??https://nodejs.org/api/n-api.html??
中间层Framework通过实现驱动层所定义的函数指针来调用驱动代码。
本人从事的是Framework层的工作,所以这里主要介绍Framework层的代码逻辑。纯属个人理解,如有理解有误的地方,欢迎私信本人讨论。
Framework是什么?
Framework字面意思就是架构,框架。 它并不是一个进程,也不是一个简单的服务。
在Sensor的架构中,它是一套SDK+Service的整体逻辑。
SDK提供结构供上层是使用。
Service提供底层服务。
Framework包含SDK 和 Service,并能够桥接SDK和Service,使之能够通信。


下面开始结合代码讲解:
在介绍Sensor订阅的流程之前,需要先讲下service启动的时候做了哪些工作。因为Service的启动流程是在订阅流程之前的,需要了解它做了哪些工作,以便更好的理解订阅流程。
Service启动阶段
每一个服务在OS中都是一个独立的进程,传感器服务当然也不例外,在系统启动的时候,传感器服务SensorService就会被拉起。首先它会从Sensor驱动中获取相关信息,比如:硬件支持的传感器列表SensorList,每个传感器支持的最大采集周期和最小采集周期,每个传感器支持最大订阅者的数量。SensorService将这些信息保存在自身的内存中。且与Sensor的驱动相关联(能够调到驱动的接口)。同时注册SensorService的Callback到驱动中。
订阅和上报阶段
Js调用订阅流程,其实就是Js往SensorService发送订阅请求的过程,Js将Callback往下层注册。
SensorService上报传感数据的过程,也可以理解为SensorService回调Js Callback的过程。
当然,并不是简单的将Js的回调函数直接注册到SensorService中。SensorService也不会直接调用Js的callback。
中间会经历多次回调注册,打个简单的比方来说就是 Js.Callback() 注册到 FrameworkA模块,FrameworkA模块的回调函数 A.Callback()注册到FrameworkB模块,FrameworkB模块的回调函数 B.Callback() 注册到FrameworkC模块,FrameworkC模块的回调函数 C.Callback() 注册到driver驱动模块。
FrameworkC.Callback() FrameworkB.Callback();
FrameworkB.Callback() FrameworkA.Callback();
FrameworkA.Callback() Js.Callback();

回调过程也并不是简单的驱动直接回调Js的Callback(),而是驱动首先回调C.Callback(),C.Callback()回调B.Callback(), B.Callback()回调A.Callback(),A.Callback() 最终回调Js.Callback() 从而上报消息。
Sensor模块中的通信方式
要理解订阅流程,需要鸿蒙IPC模式的知识储备,因为SensorClient发送消息到SensorService的流程是通过IPC通信。
也需要socket相关的知识基础,因为SensorService上报消息是通过socket通信来实现。
【OpenHarmony 通俗易懂讲解Sensor订阅流程】至于为什么需要两种通信方式,个人理解是因为SensorClient发送消息到SensorService的频度并不高,只有用户主动调用Js接口的时候才会用到,无需保持常链接。 而SensorService上报消息是需要周期性上报,频度比较高,周期长。需要保持常链接。而socket更符合该场景。
在代码中这个socket通道叫做SensorDataChannel。其实就是一个socket_pair, 发送端SendFd_在SensorService侧, RecvFd_在SensorClient端。
鸿蒙IPC的介绍

本例是代码中的实际代码:
定义了一个接口类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再传递给驱动层,完成传感器的打开,关闭,设置传感器选项等功能。
订阅传感器的流程on()
由于网页编辑工具将整图插入后图片会变小,且放大后字迹模糊,所以只能将图片分块展示再此处,分client端,和service端进行讲解
client端
?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 ()。
SensorServiceProxy::EnableSensor ()打包从Js获取的参数:sensorId,g_samplingInterval(采样周期)和 g_reportInterval,并将包通过SendRequest()发送到SensorService。


service端


client端往service发送了两次IPC消息
在srevice端存在一个key:MessageId,value:消息处理handle 的map 
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, "%publics begin,%publicp", __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;

第一次是3.1.1.2发送过来的TransferDataChannel消息,根据上面的map,可以找到对应的处理handle:SensorServiceStub::CreateDataChannelInner
接下来分析此函数。
SensorServiceStub::CreateDataChannelInner()调用了
SensorBasicDataChannel::CreateSensorBasicChannel()建立通信链接,其实就是将对端创建并发过来的SendFd_保存在本端的channel中。
接着SensorServiceStub::CreateDataChannelInner()调用了SensorService::TransferDataChannel()。保存调用者的相关信息(比如PId,Uid),channel。更新ClientInfo的数据结构。
第一次是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来调用驱动,打开传感器了。
以上,打开传感器的On函数流程结束。
后期会介绍传感器从驱动上报消息到Js的流程。


扩展:
驱动的调用方式:首先驱动会定义自身所支持的操作(函数指针):
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("hhtest -driver- %publics in", __func__);
struct HdfSBuf *msg = HdfSBufObtainDefaultSize();
if (msg == NULL)
HDF_LOGE("hhtest -driver- %publics: Failed to obtain sBuf size", __func__);
return SENSOR_FAILURE;


if (!HdfSbufWriteInt32(msg, sensorId))
HDF_LOGE("hhtest -driver- %publics: Sensor write id failed", __func__);
HdfSBufRecycle(msg);
return SENSOR_FAILURE;


if (!HdfSbufWriteInt32(msg, SENSOR_OPS_IO_CMD_ENABLE))
HDF_LOGE("hhtest -driver- %publics: Sensor write enable failed", __func__);
HdfSBufRecycle(msg);
return SENSOR_FAILURE;


int32_t ret = SendSensorMsg(sensorId, msg, NULL);
if (ret != SENSOR_SUCCESS)
HDF_LOGE("hhtest -driver- %publics: Sensor enable failed, ret[%publicd]", __func__, ret);

HdfSBufRecycle(msg);

return ret;

再比如Register的实现:
int32_t Register(RecordDataCallback cb)

HDF_LOGE("%publics in", __func__);
struct SensorDevManager *manager = NULL;

CHECK_NULL_PTR_RETURN_VALUE(cb, SENSOR_NULL_PTR);
manager = GetSensorDevManager();
(void)OsalMutexLock(& manager-> mutex);
manager-> recordDataCb = cb;
(void)OsalMutexUnlock(& manager-> mutex);

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的采样周期。
同理,最大延迟maxReportDelayNs 也是类似方式。





    推荐阅读