From 180ad3b238f2bb1f94ff919f604c291efd27482d Mon Sep 17 00:00:00 2001 From: sunxuejiao Date: Thu, 27 Jan 2022 18:40:55 +0800 Subject: [PATCH] add vibrator file Signed-off-by: sunxuejiao --- .../driver/driver-peripherals-sensor-des.md | 1300 ++++++++--------- .../driver/driver-peripherals-vibrator-des.md | 369 +++++ ...3\350\277\220\350\241\214\345\233\276.png" | Bin 0 -> 19972 bytes ...1\345\212\250\346\250\241\345\236\213.png" | Bin 0 -> 19926 bytes ...0\350\277\220\350\241\214\345\233\276.png" | Bin 0 -> 20385 bytes 5 files changed, 1005 insertions(+), 664 deletions(-) mode change 100644 => 100755 zh-cn/device-dev/driver/driver-peripherals-sensor-des.md create mode 100755 zh-cn/device-dev/driver/driver-peripherals-vibrator-des.md create mode 100755 "zh-cn/device-dev/driver/figures/Sensor\351\251\261\345\212\250\346\250\241\345\236\213\350\277\220\350\241\214\345\233\276.png" create mode 100755 "zh-cn/device-dev/driver/figures/Vibrator\351\251\261\345\212\250\346\250\241\345\236\213.png" create mode 100755 "zh-cn/device-dev/driver/figures/Vibrator\351\251\261\345\212\250\350\277\220\350\241\214\345\233\276.png" diff --git a/zh-cn/device-dev/driver/driver-peripherals-sensor-des.md b/zh-cn/device-dev/driver/driver-peripherals-sensor-des.md old mode 100644 new mode 100755 index 9d60860c65..79cdd29fe4 --- a/zh-cn/device-dev/driver/driver-peripherals-sensor-des.md +++ b/zh-cn/device-dev/driver/driver-peripherals-sensor-des.md @@ -1,31 +1,64 @@ # SENSOR -- [概述](#section3634112111) -- [接口说明](#section20930112117478) -- [开发步骤](#section1140943382) -- [开发实例](#section257750691) -- [测试指导](#section106021256121219) +- [概述](##概述) + - [功能简介](###功能简介) + - [运作机制](###运作机制) + +- [开发指导](##开发指导) + - [接口说明](#section188213414114) + - [开发步骤](#section7893102915819) + - [开发实例](#section257750691) + - [调测验证](#section106021256121219) + + +## 概述 -## 概述 +### 功能简介 -Sensor(传感器)驱动模块为上层Sensor服务系统提供稳定的Sensor基础能力API,包括Sensor列表查询、Sensor启停、Sensor订阅及去订阅,Sensor参数配置等功能;基于HDF(Hardware Driver Foundation)驱动框架开发的Sensor驱动模型,实现跨操作系统迁移,器件差异配置等功能。Sensor驱动模型如下图1所示: +Sensor设备作为外接设备重要组成模块,通过Sensor驱动模型屏蔽硬件器件差异,为上层Sensor服务系统提供稳定的Sensor基础能力接口,包括Sensor列表查询、Sensor启停、Sensor订阅及取消订阅,Sensor参数配置等功能;Sensor设备驱动的开发是基于HDF驱动框架基础上,结合操作系统适配层(OSAL)和平台驱动接口(比如I2C/SPI/UART总线等平台资源)能力,屏蔽不同操作系统和平台总线资源差异,实现Sensor驱动“一次开发,多系统部署”的目标。Sensor驱动模型如[图1](#fig10451455446)所示: **图 1** Sensor驱动模型图 -![](figures/Sensor驱动模型图.png "Sensor驱动模型图") +![Sensor驱动模型图](figures/Sensor%E9%A9%B1%E5%8A%A8%E6%A8%A1%E5%9E%8B%E5%9B%BE.png) -Sensor驱动模型对外开放的API接口能力如下: +### 运作机制 + +通过介绍Sensor驱动模型的加载以及运行流程,对模型内部关键组件以及关联组件之间的关系进行了划分,整体加载流程如[图2](#Sensor驱动模型图)所示: + +**图 2** Sensor驱动模型运行图 -- 提供Sensor HDI(Hardware Driver Interface)能力接口,简化服务开发。 -- 提供Sensor驱动模型能力接口:依赖HDF驱动框架实现Sensor器件驱动的注册、加载、去注册、器件探测等能力,提供同一类型Sensor器件驱动归一接口, 寄存器配置解析操作接口,总线访问抽象接口,平台抽象接口。 -- 提供开发者实现的能力接口:依赖HDF驱动框架的HCS(HDF Configuration Source)配置管理,根据同类型Sensor差异化配置,实现Sensor器件参数序列化配置和器件部分操作接口,简化Sensor器件驱动开发。 +![Sensor驱动模型运行图](figures/Sensor%E9%A9%B1%E5%8A%A8%E6%A8%A1%E5%9E%8B%E8%BF%90%E8%A1%8C%E5%9B%BE.png) -## 接口说明 +Sensor驱动模型以标准系统Hi3516DV300产品中的加速度传感器驱动为例,介绍整个驱动加载及运行流程: + +1. 从device info HCS 的Sensor Host里读取Sensor设备管理配置信息。 +2. HDF配置框架从HCB数据库解析Sensor设备管理配置信息,并关联对应设备驱动。 +3. 加载并初始化Sensor设备管理驱动。 +4. Sensor设备管理驱动向HDI发布Sensor基础能力接口。 +5. 从device info HCS 的Sensor Host里读取加速度传感器驱动配置信息。 +6. 加载加速度传感器抽象驱动,调用初始化接口,完成Sensor器件驱动资源分配和数据处理队列创建。 +7. 从accel_xxx_config HCS里读取加速度传感器差异化驱动配置和私有化配置信息。 +8. 加速度传感器差异化驱动,调用通用配置解析接口,完成器件属性信息解析,器件寄存器解析。 +9. 加速度传感器差异化驱动完成器件探测,并分配加速度传感器配置资源,完成加速度传感器差异化接口注册。 +10. 加速度传感器探测成功之后,加速度传感器差异化驱动通知加速度传感器抽象驱动,注册加速度传感器设备到Sensor设备管理中。 + +## 开发指导 + +### 接口说明 + +Sensor驱动模型对外开放的API接口能力如下: -Sensor驱动模型对HDI开放的API接口功能,参考表1。 +- 提供Sensor HDI(Hardware Driver Interface)能力接口,简化服务开发。 +- 提供Sensor驱动模型能力接口: + - 依赖HDF驱动框架实现Sensor器件驱动的注册,加载,去注册,器件探测等能力。 + - 提供同一类型Sensor器件驱动归一接口, 寄存器配置解析操作接口,总线访问抽象接口,平台抽象接口。 +- 提供开发者实现的能力接口:依赖HDF驱动框架的HCS(HDF Configuration Source)配置管理,根据同类型Sensor差异化配置,实现Sensor器件参数序列化配置和器件部分操作接口,简化Sensor器件驱动开发。 + +Sensor驱动模型对外开放的API接口能力的具体实现参考[表1](#table203963834718): **表 1** Sensor驱动模型对外API接口功能介绍 + - - - - - - - - - - - - - - - - - -

功能分类

接口名

@@ -38,68 +71,56 @@ Sensor驱动模型对HDI开放的API接口功能,参考表1。

int32_t GetAllSensors(struct SensorInformation **sensorInfo, int32_t *count)

获取系统中注册的所有传感器信息,一种类型传感器信息包括传感器名字、设备厂商、固件版本号、硬件版本号、传感器类型编号、传感器标识、最大量程、精度、功耗。

+

获取系统中注册的所有传感器信息,一组完整传感器信息包括传感器名字、设备厂商、固件版本号、硬件版本号、传感器类型编号、传感器标识、最大量程、精度、功耗。

配置操作

int32_t Enable(int32_t sensorId)

使能一种传感器设备,只有数据订阅者使能传感器后,才能获取订阅的传感器数据。

+

使能指定传感器设备,只有数据订阅者使能传感器后,才能获取订阅的传感器数据。

int32_t Disable(int32_t sensorId)

去使能一种传感器设备。

+

去使能指定传感器设备。

int32_t SetBatch(iint32_t sensorId, int64_t samplingInterval, int64_t reportInterval)

设置一种传感器的数据采样间隔和数据上报间隔。

+

设置指定传感器的数据采样间隔和数据上报间隔。

int32_t SetMode(int32_t sensorTypeId, SensorUser *user, int32_t mode)

+

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)

-

订阅者去注册传感器数据回调函数。

-

接口实例

+

int32_t Register(sensorId, RecordDataCallback cb);

const struct SensorInterface *NewSensorInterfaceInstance(void)

-

创建传感器接口实例。

+

订阅者根据不同sensorId注册传感器数据回调函数,系统会将获取到的传感器数据上报给订阅者。

int32_t FreeSensorInterfaceInstance(void)

+

int32_t Unregister(sensorId, RecordDataCallback cb)

释放传感器接口实例。

+

订阅者根据sensorId和回调函数注销对应订阅者的传感器数据回调函数。

+Sensor驱动模型对驱动开发者开放的功能接口,驱动开发者无需实现,直接使用,参考[表2](#table1156812588320): -Sensor驱动模型对驱动开发者开放的功能接口,驱动开发者无需实现,直接使用,参考表2: - -**表 2** Sensor驱动模型对驱动开发者开放的功能接口列表 + **表2** Sensor驱动模型对驱动开发者开放的功能接口列表 + - @@ -125,7 +146,7 @@ Sensor驱动模型对驱动开发者开放的功能接口,驱动开发者无 - @@ -137,16 +158,6 @@ Sensor驱动模型对驱动开发者开放的功能接口,驱动开发者无 - - - - - -

功能分类

接口名

@@ -115,7 +136,7 @@ Sensor驱动模型对驱动开发者开放的功能接口,驱动开发者无

添加当前类型的传感器设备到传感器设备管理。

int32_t DeleteSensorDevice(int32_t sensorId)

+

int32_t DeleteSensorDevice(const struct SensorBasicInfo *sensorBaseInfo)

删除传感器设备管理里指定的传感器设备。

上报指定类型传感器的数据到用户侧。

Sensor抽象总线和平台操作接口

+

Sensor抽象总线

int32_t ReadSensor(struct SensorBusCfg *busCfg, uint16_t regAddr, uint8_t *data, uint16_t dataLen)

按照配置的总线方式,传感器配置数据写入寄存器。

int32_t CreateSensorThread(struct OsalThread *thread, OsalThreadEntry threadEntry, char *name, void *entryPara)

-

创建指定传感器的定时线程,用于传感器数据上报处理。

-

void DestroySensorThread(struct OsalThread *thread, uint8_t *status);

-

销毁传感器创建的定时线程。

-

通用配置操作接口

int32_t SetSensorRegCfgArray(struct SensorBusCfg *busCfg, const struct SensorRegCfgGroupNode *group);

@@ -185,11 +196,12 @@ Sensor驱动模型对驱动开发者开放的功能接口,驱动开发者无
-Sensor驱动模型要求驱动开发者实现的接口功能,参考表3。 +Sensor驱动模型要求驱动开发者实现的接口功能,参考[表3](#table1083014911336): **表 3** Sensor驱动模型要求驱动开发者实现的接口列表 + - - - - - -

功能分类

接口名

@@ -202,22 +214,17 @@ Sensor驱动模型要求驱动开发者实现的接口功能,参考表3。

int32_t init(void)

传感器设备探测成功后,需要对传感器器设备初始化配置。

-

int32_t GetInfo(struct SensorBasicInfo *info)

-

从传感器设备的HCS配置里,获取当前传感器设备的基本信息。

+

传感器器设备探测成功后,需要对传感器器设备初始化配置。

int32_t Enable(void)

根据当前传感器设备的HCS配置,下发传感器设备使能操作组的寄存器配置。

+

根据当前传感器器设备的HCS配置,下发传感器设备使能操作组的寄存器配置。

int32_t Disable(void)

根据当前传感器设备的HCS配置,下发传感器设备去使能操作组的寄存器配置。

+

根据当前传感器器设备的HCS配置,下发传感器设备去使能操作组的寄存器配置。

int32_t SetBatch(int64_t samplingInterval, int64_t reportInterval)

@@ -242,613 +249,578 @@ Sensor驱动模型要求驱动开发者实现的接口功能,参考表3。
+接口实现参考[开发实例](#section257750691)章节。 -接口实现参考[SENSOR](#section257750691)章节。 - -## 开发步骤 - -Sensor驱动是基于HDF框架、Platform和OSAL基础接口进行开发,不区分操作系统和芯片平台,为不同Sensor器件提供统一的驱动模型。本篇开发指导以加速度计传感器为例,介绍传感器驱动开发。 - -1. 加速度计传感器驱动注册。HDF驱动框架会提供统一的驱动管理模型,通过加速计传感器模块配置信息,识别并加载对应模块驱动。 -2. 加速度计传感器驱动初始化和去初始化。HDF驱动框架通过init入口函数,依次启动传感器设备驱动加载和分配传感器设备数据配置资源。HDF驱动框架通过release函数,释放驱动加载的资源和配置。 -3. 加速度计传感器寄存器组配置解析。不同类型传感器需要在hcs里配置器件对应的HCS配置文件,然后再设备驱动启动过程中探测器件是否在位,然后加载对应的配置文件,生成配置的结构体对象。 -4. 加速度计传感器驱动操作接口实现。实现各个类型传感器归一化驱动接口,如init,GetInfo,Enable,Disable,SetBatch,SetMode,SetOption,ReadSensorData等函数,完成传感器驱动配置下发和数据上报功能。 +### 开发步骤 +1. 基于HDF驱动框架,按照驱动Driver Entry程序,完成加速度抽象驱动开发,主要由Bind、Init、Release、Dispatch函数接口实现。 +2. 完成加速度传感器驱动的设备信息配置。 +3. 完成加速度传感器抽象驱动内部接口开发,包括Enable、Disable、SetBatch、SetMode、SetOption、AccelCreateCfgData、AccelReleaseCfgData、AccelRegisterChipOps接口实现。 +4. 基于HDF驱动框架,按照驱动Driver Entry程序,完成加速度传感器差异化驱动开发,主要有Bind、Init、Release、Dispatch函数接口实现。 +5. 完成加速度传感器差异化驱动中差异化接口ReadData函数实现。 +6. 新增文件脚本适配。 >![](../public_sys-resources/icon-note.gif) **说明:** ->传感器驱动模型已经提供一部分能力集,包括驱动设备管理能力,抽象总线和平台操作接口能力,通用配置操作接口能力,配置解析操作接口能力,接口参考[表2](#table1156812588320)。需要开发人员实现部分有:1、传感器部分操作接口([表3](#table1083014911336));2、传感器HCS差异化数据配置;3、驱动基本功能验证。 - -## 开发实例 - -基于HDF驱动模型,加载启动加速度计传感器驱动,代码形式如下,具体原理可参考[HDF驱动开发指南](driver-hdf-development.md)。加速度计传感器选择通讯接口方式为I2C,厂家选择博世BMI160加速度计传感器。 - -1. 加速度计传感器驱动入口注册 - -- 加速度计传感器驱动入口函数实现 - -``` -/* 注册加速度计传感器入口数据结构体对象 */ -struct HdfDriverEntry g_sensorAccelDevEntry = { - .moduleVersion = 1, /* 加速度计传感器模块版本号 */ - .moduleName = "HDF_SENSOR_ACCEL", /* 加速度计传感器模块名,要与device_info.hcs文件里的加速度计moduleName字段值一样*/ - .Bind = BindAccelDriver, /* 加速度计传感器绑定函数 */ - .Init = InitAccelDriver, /* 加速度计传感器初始化函数 */ - .Release = ReleaseAccelDriver, /* 加速度计传感器资源释放函数 */ -}; - -/* 调用HDF_INIT将驱动入口注册到HDF框架中,在加载驱动时HDF框架会先调用Bind函数,再调用Init函数加载该驱动,当Init调用异常时,HDF框架会调用Release释放驱动资源并退出 */ -HDF_INIT(g_sensorAccelDevEntry); -``` - -- 加速度计传感器设备配置描述 - -加速度计传感器模型使用HCS作为配置描述源码,HCS配置字段详细介绍参考[配置管理](driver-hdf-manage.md)介绍。 - -``` -/* 加速度计传感器设备HCS配置 */ -device_sensor_accel :: device { - device0 :: deviceNode { - policy = 1; /* policy字段是驱动服务发布的策略 */ - priority = 105; /* 驱动启动优先级(0-200),值越大优先级越低,建议默认配100,优先级相同则不保证device的加载顺序 */ - preload = 2; /* 驱动按需加载字段,0表示加载,2表示不加载 */ - permission = 0664; /* 驱动创建设备节点权限 */ - moduleName = "HDF_SENSOR_ACCEL"; /* 驱动名称,该字段的值必须和驱动入口结构的moduleName值一致 */ - serviceName = "sensor_accel"; /* 驱动对外发布服务的名称,必须唯一 */ - deviceMatchAttr = "hdf_sensor_accel_driver"; /* 驱动私有数据匹配的关键字,必须和驱动私有数据配置表中的match_attr值相等 */ - } -} -``` - -1. 加速度计传感器驱动初始化和去初始化 - -- 初始化入口函数init - -``` -/* 加速度计传感器驱动对外提供的服务绑定到HDF框架 */ -int32_t BindAccelDriver(struct HdfDeviceObject *device) -{ - CHECK_NULL_PTR_RETURN_VALUE(device, HDF_ERR_INVALID_PARAM); - - static struct IDeviceIoService service = { - .object = {0}, - .Dispatch = DispatchAccel, - }; - device->service = &service; - - return HDF_SUCCESS; -} -/*在探测到器件在位后,需要调用RegisterAccelChipOps注册差异化适配函数*/ -int32_t RegisterAccelChipOps(struct AccelOpsCall *ops) -{ - struct AccelDrvData *drvData = NULL; - - CHECK_NULL_PTR_RETURN_VALUE(ops, HDF_ERR_INVALID_PARAM); - - drvData = AccelGetDrvData(); - drvData->ops.Init = ops->Init; - drvData->ops.ReadData = ops->ReadData; - return HDF_SUCCESS; -} -/* 挂载加速度计传感器驱动归一化的接口函数 */ -static int32_t InitAccelOps(struct SensorDeviceInfo *deviceInfo) -{ - struct AccelDrvData *drvData = AccelGetDrvData(); - - (void)memset_s((void *)deviceInfo, sizeof(*deviceInfo), 0, sizeof(*deviceInfo)); - deviceInfo->ops.GetInfo = SetAccelInfo; - deviceInfo->ops.Enable = SetAccelEnable; - deviceInfo->ops.Disable = SetAccelDisable; - deviceInfo->ops.SetBatch = SetAccelBatch; - deviceInfo->ops.SetMode = SetAccelMode; - deviceInfo->ops.SetOption = SetAccelOption; - - if (memcpy_s(&deviceInfo->sensorInfo, sizeof(deviceInfo->sensorInfo), - &drvData->accelCfg->sensorInfo, sizeof(drvData->accelCfg->sensorInfo)) != EOK) { - HDF_LOGE("%s: copy sensor info failed", __func__); - return HDF_FAILURE; - } - /* 传感器类型标识可以在数据HCS配置文件里面配置,也可以在此处 */ - drvData->accelCfg->sensorInfo.sensorTypeId = SENSOR_TAG_ACCELEROMETER; - drvData->accelCfg->sensorInfo.sensorId = SENSOR_TAG_ACCELEROMETER; - - return HDF_SUCCESS; -} -/* 传感器寄存器初始化操作 */ -static int32_t InitAccelAfterConfig(void) -{ - struct SensorDeviceInfo deviceInfo; - - if (InitAccelConfig() != HDF_SUCCESS) { - HDF_LOGE("%s: init accel config failed", __func__); - return HDF_FAILURE; - } - - if (InitAccelOps(&deviceInfo) != HDF_SUCCESS) { - HDF_LOGE("%s: init accel ops failed", __func__); - return HDF_FAILURE; - } - - if (AddSensorDevice(&deviceInfo) != HDF_SUCCESS) { - HDF_LOGE("%s: add accel device failed", __func__); - return HDF_FAILURE; - } - - return HDF_SUCCESS; -} -/*通过器件探测函数,挂载器件差异化函数接口*/ -static int32_t DetectAccelChip(void) -{ - int32_t num; - int32_t ret; - int32_t loop; - struct AccelDrvData *drvData = AccelGetDrvData(); - CHECK_NULL_PTR_RETURN_VALUE(drvData->accelCfg, HDF_ERR_INVALID_PARAM); - - num = sizeof(g_accelDetectIfList) / sizeof(g_accelDetectIfList[0]); - for (loop = 0; loop < num; ++loop) { - if (g_accelDetectIfList[loop].DetectChip != NULL) { - ret = g_accelDetectIfList[loop].DetectChip(drvData->accelCfg); - if (ret == HDF_SUCCESS) { - drvData->detectFlag = true; - break; - } - } - } - - if (loop == num) { - HDF_LOGE("%s: detect accel device failed", __func__); - drvData->detectFlag = false; - return HDF_FAILURE; - } - return HDF_SUCCESS; -} -/* 加速度计传感器驱动初始化入口函数,主要功能为对传感器私有数据的结构体对象进行初始化,传感器HCS数据配置对象空间分配,传感器HCS数据配置初始化入口函数调用,传感器设备探测是否在位功能,传感器数据上报定时器创建,传感器归一化接口挂载,传感器设备注册功能 */ -int32_t InitAccelDriver(struct HdfDeviceObject *device) -{ - /* 获取传感器私有数据结构体对象 */ - struct AccelDrvData *drvData = AccelGetDrvData(); - - /* 同类型传感器不同厂家设备探测时,判断此类型传感器是否已经在位,若已经在位,无需再继续探测,直接返回 */ - if (drvData->detectFlag) { - HDF_LOGE("%s: accel sensor have detected", __func__); - return HDF_SUCCESS; - } - - CHECK_NULL_PTR_RETURN_VALUE(device, HDF_ERR_INVALID_PARAM); - /* 分配存放传感器数据配置的私有结构体数据对象,需要在驱动释放时释放分配的资源空间 */ - drvData->accelCfg = (struct SensorCfgData *)OsalMemCalloc(sizeof(*cfg)); - if (drvData->accelCfg == NULL) { - HDF_LOGE("%s: malloc sensor config data failed", __func__); - return HDF_FAILURE; - } - - drvData->accelCfg->regCfgGroup = &g_regCfgGroup[0]; - /* 初始化传感器配置数据主要是解析传感器通讯总线配置类型信息,传感器基本信息,传感器属性信息,传感器是否在位信息,寄存器分组信息 */ - if (GetSensorBaseConfigData(device->property, drvData->accelCfg) != HDF_SUCCESS) { - HDF_LOGE("%s: get sensor base config failed", __func__); - goto Base_CONFIG_EXIT; - } - - if (DetectAccelChip() != HDF_SUCCESS) { - HDF_LOGE("%s: accel sensor detect device no exist", __func__); - goto DETECT_CHIP_EXIT; - } - drvData->detectFlag = true; - if (ParseSensorRegConfig(drvData->accelCfg) != HDF_SUCCESS) { - HDF_LOGE("%s: detect sensor device failed", __func__); - goto REG_CONFIG_EXIT; - } - - if (InitAccelAfterConfig() != HDF_SUCCESS) { - HDF_LOGE("%s: init accel after config failed", __func__); - goto INIT_EXIT; - } - - HDF_LOGI("%s: init accel driver success", __func__); - return HDF_SUCCESS; - -INIT_EXIT: - DestroySensorThread(&drvData->thread, &drvData->threadStatus); - (void)DeleteSensorDevice(SENSOR_TAG_ACCELEROMETER); -REG_CONFIG_EXIT: - ReleaseSensorAllRegConfig(drvData->accelCfg); - (void)ReleaseSensorBusHandle(&drvData->accelCfg->busCfg); -DETECT_CHIP_EXIT: - drvData->detectFlag = false; -BASE_CONFIG_EXIT: - drvData->accelCfg->root = NULL; - drvData->accelCfg->regCfgGroup = NULL; - OsalMemFree(drvData->accelCfg); - drvData->accelCfg = NULL; - return HDF_FAILURE; -} - -/* 释放驱动初始化时分配的资源 */ -void ReleaseAccelDriver(struct HdfDeviceObject *device) -{ - (void)device; - struct AccelDrvData *drvData = NULL; - - drvData = AccelGetDrvData(); - (void)DestroySensorThread(&drvData->thread, &drvData->threadStatus); - (void)DeleteSensorDevice(SENSOR_TAG_ACCELEROMETER); - drvData->detectFlag = false; - - if (drvData->accelCfg != NULL) { - drvData->accelCfg->root = NULL; - drvData->accelCfg->regCfgGroup = NULL; - ReleaseSensorAllRegConfig(drvData->accelCfg); - (void)ReleaseSensorBusHandle(&drvData->accelCfg->busCfg); - OsalMemFree(drvData->accelCfg); - drvData->accelCfg = NULL; - } - - drvData->initStatus = false; -} -``` - -1. 加速度计传感器寄存器组配置信息 - -加速度计传感器数据配置只需要按照模板配置即可,基于模板配置的解析功能已经在**InitSensorConfigData**函数完成,只需初始化时调用即可。如果有新增配置项,需要同步修改此函数。 - -``` -加速度计传感器数据配置模板(accel_config.hcs) -root { - sensorAccelConfig { - accelChipConfig { - /* 传感器设备信息模板 */ - template sensorInfo { - sensorName = "accelerometer"; /* 加速度计名字,字符最大长度16字节 */ - vendorName = "borsh_bmi160"; /* 传感器设备厂商,字符最大长度16字节 */ - firmwareVersion = "1.0"; /* 传感器固件版本号,默认1.0,字符最大长度16字节 */ - hardwareVersion = "1.0"; /* 传感器硬件版本号,默认1.0,字符最大长度16字节 */ - sensorTypeId = 1; /* 传感器类型编号,详见{@link SensorTypeTag} */ - sensorId = 1; /* 传感器的标识号,有传感器驱动开发者定义,推荐用{@link SensorTypeTag}枚举 */ - maxRange = 8; /* 传感器的最大量程,根据开发者需要配置 */ - precision = 0; /* 传感器的精度,与上报数据配合使用,上报数据结构体{@link SensorEvents } */ - power = 230; /* 传感器的功耗 */ - } - /* 传感器使用的总线类型和配置信息模板 */ - template sensorBusConfig { - busType = 0; /* 0:i2c 1:spi */ - busNum = 6; /* 芯片上分配给传感器的器件号 */ - busAddr = 0; /* 芯片上分配给传感器的地址 */ - regWidth = 1; /* 传感器寄存器地址宽度 */ - regBigEndian = 0; /* 传感器寄存器大小端 */ - } - /* 传感器设备属性模板 */ - template sensorAttr { - chipName = ""; /* 传感器芯片名字 */ - chipIdRegister = 0xf; /* 传感器在位检测寄存器地址 */ - chipIdValue = 0xd1; /* 校验传感器在位检测寄存器值 */ - } - } - } -} - -/* 根据不同器件硬件差异,修改模板配置,不修改的就会默认采用模板配置 */ -root { - sensorAccelConfig { - accel_bmi160_chip_config : accelChipConfig { - match_attr = "hdf_sensor_accel_driver"; /* 需要和加速度计传感器设备配置match_attr字段保持一致 */ - accelInfo :: sensorInfo { - vendorName = "borsh_bmi160"; - sensorTypeId = 1; - sensorId = 1; - } - accelBusConfig :: sensorBusConfig { - busType = 0; /* i2c通讯方式 */ - busNum = 6; - busAddr = 0x68; - regWidth = 1; /* 1字节位宽 */ - } - accelAttr :: sensorAttr { - chipName = "bmi160"; - chipIdRegister = 0x00; - chipIdValue = 0xd1; - } - accelRegConfig { - /* regAddr: 寄存器地址 - value: 寄存器值 - mask: 寄存器值的掩码 - len: 寄存器值的数据长度(字节) - delay: 配置寄存器延时(ms) - opsType:操作类型 0-无 1-读 2-写 3-读并检查 4-位更新 - calType: 计算类型 0-无 1-写 2-取反 3-异或 4-左移 5-右移 - shiftNum: 移动位数 - debug: 调试开关,0-调试关闭 1-调试打开 - save: 保存数据开关,0-不保存数据 1-保存数据 +> +>- 传感器驱动模型已经提供一部分能力集,包括驱动设备管理能力、抽象总线和平台操作接口能力、通用配置操作接口能力、配置解析操作接口能力,接口参考[表2](#table1156812588320)。 +> +>- 需要开发人员实现部分有: +> +> - 传感器部分操作接口([表3](#table1083014911336))。 +> +> - 传感器HCS差异化数据配置。 +> - 驱动基本功能验证。 + +### 开发实例 + +基于HDF驱动模型,加载启动加速度计传感器驱动,代码形式如下,具体原理可参考[HDF驱动开发指南](driver-hdf-development.md)。本例中加速度传感器选择博世BMI160,其通讯接口方式选择I2C。 + +1. 加速度传感器驱动入口注册 + + - 加速度传感器驱动入口函数实现 + + ``` + /* 注册加速度计传感器入口数据结构体对象 */ + struct HdfDriverEntry g_sensorAccelDevEntry = { + .moduleVersion = 1, //加速度计传感器模块版本号 + .moduleName = "HDF_SENSOR_ACCEL", //加速度计传感器模块名,要与device_info.hcs文件里的加速度计moduleName字段值一样 + .Bind = BindAccelDriver, // 加速度计传感器绑定函数 + .Init = InitAccelDriver, // 加速度计传感器初始化函数 + .Release = ReleaseAccelDriver, // 加速度计传感器资源释放函数 + }; + + /* 调用HDF_INIT将驱动入口注册到HDF框架中,在加载驱动时HDF框架会先调用Bind函数,再调用Init函数加载该驱动,当Init调用异常时,HDF框架会调用Release释放驱动资源并退出 */ + HDF_INIT(g_sensorAccelDevEntry); + ``` + + - 加速度传感器设备配置描述 + + 加速度传感器模型使用HCS作为配置描述源码,HCS配置字段请参考[配置管理](driver-hdf-manage.md)介绍。 + + ``` + /* 加速度计传感器设备HCS配置 */ + device_sensor_accel :: device { + device0 :: deviceNode { + policy = 1; // 驱动服务发布的策略 + priority = 110; // 驱动启动优先级(0-200),值越大优先级越低,建议配置为100,优先级相同则不保证device的加载顺序 + preload = 0; // 驱动按需加载字段,0表示加载,2表示不加载 + permission = 0664; // 驱动创建设备节点权限 + moduleName = "HDF_SENSOR_ACCEL"; // 驱动名称,该字段的值必须和驱动入口结构的moduleName值一致 + serviceName = "sensor_accel"; // 驱动对外发布服务的名称,必须唯一 + deviceMatchAttr = "hdf_sensor_accel_driver"; // 驱动私有数据匹配的关键字,必须和驱动私有数据配置表中的match_attr值相等 + } + } + ``` + +2. 加速度传感器驱动操作接口实现 + + 开发者需要根据每种类型的传感器实现归一化接口。 + + ``` + /* 不使用函数暂时置空 */ + static int32_t SetAccelInfo(struct SensorBasicInfo *info) + { + (void)info; + + return HDF_ERR_NOT_SUPPORT; + } + /* 下发使能寄存器组的配置 */ + static int32_t SetAccelEnable(void) + { + int32_t ret; + struct AccelDrvData *drvData = AccelGetDrvData(); + + CHECK_NULL_PTR_RETURN_VALUE(drvData, HDF_ERR_INVALID_PARAM); + CHECK_NULL_PTR_RETURN_VALUE(drvData->accelCfg, HDF_ERR_INVALID_PARAM); + + if (drvData->enable) { + HDF_LOGE("%s: Accel sensor is enabled", __func__); + return HDF_SUCCESS; + } + + ret = SetSensorRegCfgArray(&drvData->accelCfg->busCfg, drvData->accelCfg->regCfgGroup[SENSOR_ENABLE_GROUP]); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s: Accel sensor enable config failed", __func__); + return ret; + } + + ret = OsalTimerCreate(&drvData->accelTimer, SENSOR_TIMER_MIN_TIME, AccelTimerEntry, (uintptr_t)drvData); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s: Accel create timer failed[%d]", __func__, ret); + return ret; + } + + ret = OsalTimerStartLoop(&drvData->accelTimer); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s: Accel start timer failed[%d]", __func__, ret); + return ret; + } + drvData->enable = true; + + return HDF_SUCCESS; + } + /* 下发去使能寄存器组的配置 */ + static int32_t SetAccelDisable(void) + { + int32_t ret; + struct AccelDrvData *drvData = AccelGetDrvData(); + + CHECK_NULL_PTR_RETURN_VALUE(drvData, HDF_ERR_INVALID_PARAM); + CHECK_NULL_PTR_RETURN_VALUE(drvData->accelCfg, HDF_ERR_INVALID_PARAM); + + if (!drvData->enable) { + HDF_LOGE("%s: Accel sensor had disable", __func__); + return HDF_SUCCESS; + } + + ret = SetSensorRegCfgArray(&drvData->accelCfg->busCfg, drvData->accelCfg->regCfgGroup[SENSOR_DISABLE_GROUP]); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s: Accel sensor disable config failed", __func__); + return ret; + } + + ret = OsalTimerDelete(&drvData->accelTimer); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s: Accel delete timer failed", __func__); + return ret; + } + drvData->enable = false; + + return HDF_SUCCESS; + } + /* 配置传感器采样率和数据上报间隔 */ + static int32_t SetAccelBatch(int64_t samplingInterval, int64_t interval) + { + (void)interval; + + struct AccelDrvData *drvData = NULL; + + drvData = AccelGetDrvData(); + CHECK_NULL_PTR_RETURN_VALUE(drvData, HDF_ERR_INVALID_PARAM); + + drvData->interval = samplingInterval; + + return HDF_SUCCESS; + } + /* 设置传感器工作模式,当前支持实时模式 */ + static int32_t SetAccelMode(int32_t mode) + { + return (mode == SENSOR_WORK_MODE_REALTIME) ? HDF_SUCCESS : HDF_FAILURE; + } + + static int32_t SetAccelOption(uint32_t option) + { + (void)option; + return HDF_SUCCESS; + } + + /* 设置传感器可选配置 */ + static int32_t SetAccelOption(uint32_t option) + { + (void)option; + return HDF_ERR_NOT_SUPPORT; + } + ``` + +3. 加速度传感器驱动初始化和去初始化 + + ``` + /* 加速度计传感器驱动对外提供的服务绑定到HDF框架 */ + int32_t AccelBindDriver(struct HdfDeviceObject *device) + { + CHECK_NULL_PTR_RETURN_VALUE(device, HDF_ERR_INVALID_PARAM); + + struct AccelDrvData *drvData = (struct AccelDrvData *)OsalMemCalloc(sizeof(*drvData)); + if (drvData == NULL) { + HDF_LOGE("%s: Malloc accel drv data fail!", __func__); + return HDF_ERR_MALLOC_FAIL; + } + + drvData->ioService.Dispatch = DispatchAccel; + drvData->device = device; + device->service = &drvData->ioService; + g_accelDrvData = drvData; + return HDF_SUCCESS; + } + + /* 挂载加速度计传感器驱动归一化的接口函数 */ + static int32_t InitAccelOps(struct SensorCfgData *config, struct SensorDeviceInfo *deviceInfo) + { + CHECK_NULL_PTR_RETURN_VALUE(config, HDF_ERR_INVALID_PARAM); + + deviceInfo->ops.Enable = SetAccelEnable; + deviceInfo->ops.Disable = SetAccelDisable; + deviceInfo->ops.SetBatch = SetAccelBatch; + deviceInfo->ops.SetMode = SetAccelMode; + deviceInfo->ops.SetOption = SetAccelOption; + + if (memcpy_s(&deviceInfo->sensorInfo, sizeof(deviceInfo->sensorInfo), + &config->sensorInfo, sizeof(config->sensorInfo)) != EOK) { + HDF_LOGE("%s: Copy sensor info failed", __func__); + return HDF_FAILURE; + } + + return HDF_SUCCESS; + } + /* 提供给差异化驱动的初始化接口,完成加速度器件基本配置信息解析(加速度信息,加速度总线配置,加速度器件探测寄存器配置),器件探测,器件寄存器解析 */ + static int32_t InitAccelAfterDetected(struct SensorCfgData *config) + { + struct SensorDeviceInfo deviceInfo; + CHECK_NULL_PTR_RETURN_VALUE(config, HDF_ERR_INVALID_PARAM); + /* 初始化加速度计接口函数 */ + if (InitAccelOps(config, &deviceInfo) != HDF_SUCCESS) { + HDF_LOGE("%s: Init accel ops failed", __func__); + return HDF_FAILURE; + } + /* 注册加速度计设备到传感器管理模块 */ + if (AddSensorDevice(&deviceInfo) != HDF_SUCCESS) { + HDF_LOGE("%s: Add accel device failed", __func__); + return HDF_FAILURE; + } + /* 器件寄存器解析 */ + if (ParseSensorRegConfig(config) != HDF_SUCCESS) { + HDF_LOGE("%s: Parse sensor register failed", __func__); + (void)DeleteSensorDevice(&config->sensorInfo); + ReleaseSensorAllRegConfig(config); + return HDF_FAILURE; + } + return HDF_SUCCESS; + } + struct SensorCfgData *AccelCreateCfgData(const struct DeviceResourceNode *node) + { + …… + /* 如果探测不到器件在位,返回进行下个器件探测 */ + if (drvData->detectFlag) { + HDF_LOGE("%s: Accel sensor have detected", __func__); + return NULL; + } + if (drvData->accelCfg == NULL) { + HDF_LOGE("%s: Accel accelCfg pointer NULL", __func__); + return NULL; + } + /* 设备基本配置信息解析 */ + if (GetSensorBaseConfigData(node, drvData->accelCfg) != HDF_SUCCESS) { + HDF_LOGE("%s: Get sensor base config failed", __func__); + goto BASE_CONFIG_EXIT; + } + /* 如果探测不到器件在位,返回进行下个器件探测 */ + if (DetectSensorDevice(drvData->accelCfg) != HDF_SUCCESS) { + HDF_LOGI("%s: Accel sensor detect device no exist", __func__); + drvData->detectFlag = false; + goto BASE_CONFIG_EXIT; + } + drvData->detectFlag = true; + /* 器件寄存器解析 */ + if (InitAccelAfterDetected(drvData->accelCfg) != HDF_SUCCESS) { + HDF_LOGE("%s: Accel sensor detect device no exist", __func__); + goto INIT_EXIT; + } + return drvData->accelCfg; + …… + } + /* 加速度计传感器驱动初始化入口函数,主要功能为对传感器私有数据的结构体对象进行初始化,传感器HCS数据配置对象空间分配,传感器HCS数据配置初始化入口函数调用,传感器设备探测是否在位功能,传感器数据上报定时器创建,传感器归一化接口挂载,传感器设备注册功能 */ + int32_t InitAccelDriver(struct HdfDeviceObject *device) + { + int32_t AccelInitDriver(struct HdfDeviceObject *device) + { + …… + /* 工作队列资源初始化 */ + if (InitAccelData(drvData) != HDF_SUCCESS) { + HDF_LOGE("%s: Init accel config failed", __func__); + return HDF_FAILURE; + } + /* 分配加速度配置信息资源 */ + drvData->accelCfg = (struct SensorCfgData *)OsalMemCalloc(sizeof(*drvData->accelCfg)); + if (drvData->accelCfg == NULL) { + HDF_LOGE("%s: Malloc accel config data failed", __func__); + return HDF_FAILURE; + } + /* 挂接寄存器分组信息 */ + drvData->accelCfg->regCfgGroup = &g_regCfgGroup[0]; + …… + return HDF_SUCCESS; + } + + /* 释放驱动初始化时分配的资源 */ + void AccelReleaseDriver(struct HdfDeviceObject *device) + { + CHECK_NULL_PTR_RETURN(device); + struct AccelDrvData *drvData = (struct AccelDrvData *)device->service; + CHECK_NULL_PTR_RETURN(drvData); + /* 器件在位,释放已分配资源 */ + if (drvData->detectFlag) { + AccelReleaseCfgData(drvData->accelCfg); + } + OsalMemFree(drvData->accelCfg); + drvData->accelCfg = NULL; + /* 器件在位,销毁工作队列资源 */ + HdfWorkDestroy(&drvData->accelWork); + HdfWorkQueueDestroy(&drvData->accelWorkQueue); + OsalMemFree(drvData); + } + ``` + +4. 加速度传感器差异化驱动私有HCS配置实现 + + - 为了方便开发者使用传感器HCS私有配置,在sensor_common.hcs里面定义通用的传感器配置模板,加速度传感器直接引用模板修改对应的属性值即可。 + + ``` + accel sensor common config template + root { + sensorAccelConfig { + accelChipConfig { + /* 传感器设备信息模板 */ + template sensorInfo { + sensorName = "accelerometer"; // 加速度计名字,字符最大长度16字节 + vendorName = "borsh_bmi160"; // 传感器设备厂商,字符最大长度16字节 + firmwareVersion = "1.0"; // 传感器固件版本号,默认1.0,字符最大长度16字节 + hardwareVersion = "1.0"; // 传感器硬件版本号,默认1.0,字符最大长度16字节 + sensorTypeId = 1; // 传感器类型编号,详见{@link SensorTypeTag} + sensorId = 1; // 传感器的标识号,有传感器驱动开发者定义,推荐用{@link SensorTypeTag}枚举 + maxRange = 8; // 传感器的最大量程,根据开发者需要配置 + accuracy = 0; // 传感器的精度,与上报数据配合使用,上报数据结构体{@link SensorEvents } + power = 230; // 传感器的功耗 + } + /* 传感器使用的总线类型和配置信息模板 */ + template sensorBusConfig { + busType = 0; // 0:i2c 1:spi + busNum = 6; // 芯片上分配给传感器的器件号 + busAddr = 0; // 芯片上分配给传感器的地址 + regWidth = 1; // 传感器寄存器地址宽度 + regBigEndian = 0; // 传感器寄存器大小端 + } + /* 传感器设备属性模板 */ + template sensorAttr { + chipName = ""; // 传感器芯片名字 + chipIdRegister = 0xf; // 传感器在位检测寄存器地址 + chipIdValue = 0xd1; // 校验传感器在位检测寄存器值 + } + } + } + } + ``` + + - 开发者使用传感器HCS配置,在accel_config.hcs里面配置通用的传感器模板,加速度传感器直接引用模板并修改对应的属性值,在此基础上新增寄存器配置,生成accel_bmi160_config.hcs配置文件。 + + ``` + /* 根据不同器件硬件差异,修改模板配置,不修改的就会默认采用模板配置 */ + #include "accel_config.hcs" + root { + accel_bmi160_chip_config : sensorConfig { + match_attr = "hdf_sensor_accel_bmi160_driver"; + sensorInfo :: sensorDeviceInfo { + vendorName = "borsh_bmi160"; // max string length is 16 bytes + sensorTypeId = 1; // enum SensorTypeTag + sensorId = 1; // user define sensor id + } + sensorBusConfig:: sensorBusInfo { + busType = 0; // 0:i2c 1:spi + busNum = 6; + busAddr = 0x68; + regWidth = 1; // 1btye + } + sensorIdAttr :: sensorIdInfo{ + chipName = "bmi160"; + chipIdRegister = 0x00; + chipIdValue = 0xd1; + } + sensorRegConfig { + /* regAddr: register address + value: config register value + len: size of value + mask: mask of value + delay: config register delay time (ms) + opsType: enum SensorOpsType 0-none 1-read 2-write 3-read_check 4-update_bit + calType: enum SensorBitCalType 0-none 1-set 2-revert 3-xor 4-left shift 5-right shift + shiftNum: shift bits + debug: 0-no debug 1-debug + save: 0-no save 1-save */ - /* 传感器寄存器操作分组,按照分组进行有序配置 */ - /* 寄存器地址, 寄存器值, 寄存器值的掩码, 寄存器值的数据长度, 配置寄存器延时, 操作类型, 计算类型, 移动位数, 调试开关, 保存开关 */ + /* regAddr, value, mask, len, delay, opsType, calType, shiftNum, debug, save */ /* 初始化寄存器组 */ - initSeqConfig = [ - 0x7e, 0xb6, 0xff, 1, 5, 2, 0, 0, 0, 0, - 0x7e, 0x10, 0xff, 1, 5, 2, 0, 0, 0, 0 - ]; - /* 使能寄存器组 */ - enableSeqConfig = [ - 0x7e, 0x11, 0xff, 1, 5, 2, 0, 0, 0, 0, - 0x41, 0x03, 0xff, 1, 0, 2, 0, 0, 0, 0, - 0x40, 0x08, 0xff, 1, 0, 2, 0, 0, 0, 0 - ]; - /* 去使能寄存器组 */ - disableSeqConfig = [ - 0x7e, 0x10, 0xff, 1, 5, 2, 0, 0, 0, 0 - ]; - } - } - } -} -``` - -1. 加速度计传感器驱动操作接口实现 - -开发者需要根据每种类型的传感器实现归一化接口。 - -``` -/* 不使用函数暂时置空 */ -static int32_t SetAccelInfo(struct SensorBasicInfo *info) -{ - (void)info; - - return HDF_ERR_NOT_SUPPORT; -} -/* 下发使能寄存器组的配置 */ -static int32_t SetAccelEnable(void) -{ - int32_t ret; - struct AccelDrvData *drvData = AccelGetDrvData(); - - CHECK_NULL_PTR_RETURN_VALUE(drvData->accelCfg, HDF_ERR_INVALID_PARAM); - ret = SetSensorRegCfgArray(&drvData->accelCfg->busCfg, drvData->accelCfg->regCfgGroup[SENSOR_ENABLE_GROUP]); - if (ret != HDF_SUCCESS) { - HDF_LOGE("%s: accel sensor disable config failed", __func__); - return HDF_FAILURE; - } - - drvData->threadStatus = SENSOR_THREAD_RUNNING; - - return HDF_SUCCESS; -} -/* 下发去使能寄存器组的配置 */ -static int32_t SetAccelDisable(void) -{ - int32_t ret; - struct AccelDrvData *drvData = AccelGetDrvData(); - - CHECK_NULL_PTR_RETURN_VALUE(drvData->accelCfg, HDF_ERR_INVALID_PARAM); - - ret = SetSensorRegCfgArray(&drvData->accelCfg->busCfg, drvData->accelCfg->regCfgGroup[SENSOR_DISABLE_GROUP]); - if (ret != HDF_SUCCESS) { - HDF_LOGE("%s: accel sensor disable config failed", __func__); - return HDF_FAILURE; - } - - drvData->threadStatus = SENSOR_THREAD_STOPPED; - - return HDF_SUCCESS; -} -/* 配置传感器采样率和数据上报间隔 */ -static int32_t SetAccelBatch(int64_t samplingInterval, int64_t interval) -{ - (void)interval; - - struct AccelDrvData *drvData = AccelGetDrvData(); - drvData->interval = samplingInterval; - - return HDF_SUCCESS; -} -/* 设置传感器工作模式,当前支持实时模式 */ -static int32_t SetAccelMode(int32_t mode) -{ - return (mode == SENSOR_WORK_MODE_REALTIME) ? HDF_SUCCESS : HDF_FAILURE; -} -/* 设置传感器可选配置 */ -static int32_t SetAccelOption(uint32_t option) -{ - (void)option; - return HDF_ERR_NOT_SUPPORT; -} -``` - -- 差异化处理接口 - - ``` - /* 器件探测时,如果探测成功,则注册差异化处理函数到accel驱动模型里 */ - int32_t DetectAccelBim160Chip(struct SensorCfgData *data) - { - int32_t ret; - struct AccelOpsCall ops; - CHECK_NULL_PTR_RETURN_VALUE(data, HDF_ERR_INVALID_PARAM); - - if (strcmp(ACCEL_CHIP_NAME_BMI160, data->sensorAttr.chipName) != 0) { - return HDF_SUCCESS; - } - ret = InitAccelPreConfig(); - if (ret != HDF_SUCCESS) { - HDF_LOGE("%s: init BMI160 bus mux config", __func__); - return HDF_FAILURE; - } - if (DetectSensorDevice(data) != HDF_SUCCESS) { - return HDF_FAILURE; - } - - /* 差异化处理函数 */ - ops.Init = InitBmi160; - ops.ReadData = ReadBmi160Data; - ret = RegisterAccelChipOps(&ops); - if (ret != HDF_SUCCESS) { - HDF_LOGE("%s: register BMI160 accel failed", __func__); - (void)ReleaseSensorBusHandle(&data->busCfg); - return HDF_FAILURE; - } - return HDF_SUCCESS; - } - /* 初始化处理函数 */ - static int32_t InitBmi160(struct SensorCfgData *data) - { - int32_t ret; - - CHECK_NULL_PTR_RETURN_VALUE(data, HDF_ERR_INVALID_PARAM); - ret = SetSensorRegCfgArray(&data->busCfg, data->regCfgGroup[SENSOR_INIT_GROUP]); - if (ret != HDF_SUCCESS) { - HDF_LOGE("%s: bmi160 sensor init config failed", __func__); - return HDF_FAILURE; - } - return HDF_SUCCESS; - } - /* 数据处理函数 */ - int32_t ReadBmi160Data(struct SensorCfgData *data) - { - int32_t ret; - struct AccelData rawData = { 0, 0, 0 }; - int32_t tmp[ACCEL_AXIS_NUM]; - struct SensorReportEvent event; - - (void)memset_s(&event, sizeof(event), 0, sizeof(event)); - - ret = ReadBmi160RawData(data, &rawData, &event.timestamp); - if (ret != HDF_SUCCESS) { - return HDF_FAILURE; - } - - event.sensorId = SENSOR_TAG_ACCELEROMETER; - event.option = 0; - event.mode = SENSOR_WORK_MODE_REALTIME; - - rawData.x = rawData.x * BMI160_ACC_SENSITIVITY_2G; - rawData.y = rawData.y * BMI160_ACC_SENSITIVITY_2G; - rawData.z = rawData.z * BMI160_ACC_SENSITIVITY_2G; - - tmp[ACCEL_X_AXIS] = (rawData.x * SENSOR_1K_UNIT) / SENSOR_CONVERT_UNIT; - tmp[ACCEL_Y_AXIS] = (rawData.y * SENSOR_1K_UNIT) / SENSOR_CONVERT_UNIT; - tmp[ACCEL_Z_AXIS] = (rawData.z * SENSOR_1K_UNIT) / SENSOR_CONVERT_UNIT; - - event.dataLen = sizeof(tmp); - event.data = (uint8_t *)&tmp; - ret = ReportSensorEvent(&event); - return ret; - } - ``` - -- 数据处理函数 - -创建传感器定时器,按照配置的采样率定时采样,并上报给数据订阅者。 - -``` -/* 传感器定时工作线程 */ -static int32_t ReadAccelDataThreadWorker(void *arg) -{ - (void)arg; - int64_t interval; - struct AccelDrvData *drvData = AccelGetDrvData(); - - drvData->threadStatus = SENSOR_THREAD_START; - while (true) { - if (drvData->threadStatus == SENSOR_THREAD_RUNNING) { - if (drvData->ops.ReadData != NULL) { - (void)drvData->ops.ReadData(drvData->accelCfg); - } - interval = OsalDivS64(drvData->interval, (SENSOR_CONVERT_UNIT * SENSOR_CONVERT_UNIT)); - OsalMSleep(interval); - } else if (drvData->threadStatus == SENSOR_THREAD_DESTROY) { - break; - } else { - OsalMSleep(ACC_DEFAULT_SAMPLING_200_MS / SENSOR_CONVERT_UNIT / SENSOR_CONVERT_UNIT); - } - - if ((!drvData->initStatus) || (drvData->interval < 0) || drvData->threadStatus != SENSOR_THREAD_RUNNING) { - continue; - } - } - - return HDF_SUCCESS; -} -/* 创建传感器定时器和器件初始化 */ -static int32_t InitAccelConfig(void) -{ - int32_t ret; - struct AccelDrvData *drvData = AccelGetDrvData(); - - if (drvData->threadStatus != SENSOR_THREAD_NONE && drvData->threadStatus != SENSOR_THREAD_DESTROY) { - HDF_LOGE("%s: accel thread have created", __func__); - return HDF_SUCCESS; - } - - ret = CreateSensorThread(&drvData->thread, ReadAccelDataThreadWorker, "hdf_sensor_accel", drvData); - if (ret != HDF_SUCCESS) { - HDF_LOGE("%s: accel create thread failed", __func__); - drvData->threadStatus = SENSOR_THREAD_NONE; - return HDF_FAILURE; - } - - CHECK_NULL_PTR_RETURN_VALUE(drvData->ops.Init, HDF_ERR_INVALID_PARAM); - - ret = drvData->ops.Init(drvData->accelCfg); - if (ret != HDF_SUCCESS) { - HDF_LOGE("%s: accel create thread failed", __func__); - drvData->threadStatus = SENSOR_THREAD_NONE; - return HDF_FAILURE; - } - drvData->initStatus = true; - return HDF_SUCCESS; -} -``` - -- 主要的数据结构 - -``` -/* 传感器转换单位*/ -#define SENSOR_CONVERT_UNIT 1000 -#define SENSOR_1K_UNIT 1024 -/* 传感器2g对应灵敏度转换值 */ -#define BMI160_ACC_SENSITIVITY_2G 61 -/* 传感器数据采样寄存器地址 */ -#define BMI160_ACCEL_X_LSB_ADDR 0X12 -#define BMI160_ACCEL_X_MSB_ADDR 0X13 -#define BMI160_ACCEL_Y_LSB_ADDR 0X14 -#define BMI160_ACCEL_Y_MSB_ADDR 0X15 -#define BMI160_ACCEL_Z_LSB_ADDR 0X16 -#define BMI160_ACCEL_Z_MSB_ADDR 0X17 -/* 传感器数据维度 */ -enum AccelAxisNum { - ACCEL_X_AXIS = 0, - ACCEL_Y_AXIS = 1, - ACCEL_Z_AXIS = 2, - ACCEL_AXIS_NUM = 3, -}; -/* 传感器每个维度值 */ -struct AccelData { - int32_t x; - int32_t y; - int32_t z; -}; -/* 传感器私有数据结构体 */ -struct AccelDrvData { - bool detectFlag; - uint8_t threadStatus; - uint8_t initStatus; - int64_t interval; - struct SensorCfgData *accelCfg; - struct OsalThread thread; - struct AccelOpsCall ops; -}; -/* 差异化适配函数 */ -struct AccelOpsCall { - int32_t (*Init)(struct SensorCfgData *data); - int32_t (*ReadData)(struct SensorCfgData *data); -}; -``` - -## 测试指导 + initSeqConfig = [ + 0x7e, 0xb6, 0xff, 1, 5, 2, 0, 0, 0, 0, + 0x7e, 0x10, 0xff, 1, 5, 2, 0, 0, 0, 0 + ]; + /* 使能寄存器组 */ + enableSeqConfig = [ + 0x7e, 0x11, 0xff, 1, 5, 2, 0, 0, 0, 0, + 0x41, 0x03, 0xff, 1, 0, 2, 0, 0, 0, 0, + 0x40, 0x08, 0xff, 1, 0, 2, 0, 0, 0, 0 + ]; + /* 去使能寄存器组 */ + disableSeqConfig = [ + 0x7e, 0x10, 0xff, 1, 5, 2, 0, 0, 0, 0 + ]; + } + } + } + ``` + +5. 加速度传感器差异化驱动实现 + + - 定义加速度传感器差异化驱动对应的HdfDriverEntry对象,其中Driver Entry入口函数定义如下: + + ``` + struct HdfDriverEntry g_accelBmi160DevEntry = { + .moduleVersion = 1, + .moduleName = "HDF_SENSOR_ACCEL_BMI160", + .Bind = Bmi160BindDriver, + .Init = Bmi160InitDriver, + .Release = Bmi160ReleaseDriver, + }; + HDF_INIT(g_accelBmi160DevEntry); + ``` + + - Bind驱动接口实例化。 + + ``` + int32_t Bmi160BindDriver(struct HdfDeviceObject *device) + { + CHECK_NULL_PTR_RETURN_VALUE(device, HDF_ERR_INVALID_PARAM); + struct Bmi160DrvData *drvData = (struct Bmi160DrvData *)OsalMemCalloc(sizeof(*drvData)); + if (drvData == NULL) { + HDF_LOGE("%s: Malloc Bmi160 drv data fail", __func__); + return HDF_ERR_MALLOC_FAIL; + } + drvData->ioService.Dispatch = DispatchBMI160; + drvData->device = device; + device->service = &drvData->ioService; + g_bmi160DrvData = drvData; + return HDF_SUCCESS; + } + ``` + + - Init驱动接口实例化。 + + ``` + int32_t Bmi160InitDriver(struct HdfDeviceObject *device) + { + …… + /* 加速度计差异化初始化配置 */ + ret = InitAccelPreConfig(); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s: Init BMI160 bus mux config", __func__); + return HDF_FAILURE; + } + /* 创建传感器配置数据接口,完成器件探测,私有数据配置解析 */ + drvData->sensorCfg = AccelCreateCfgData(device->property); + if (drvData->sensorCfg == NULL) { + return HDF_ERR_NOT_SUPPORT; + } + /* 注册差异化接口 */ + ops.Init = NULL; + ops.ReadData = ReadBmi160Data; + ret = AccelRegisterChipOps(&ops); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s: Register BMI160 accel failed", __func__); + return HDF_FAILURE; + } + /* 初始化器件配置 *、 + ret = InitBmi160(drvData->sensorCfg); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s: Init BMI160 accel failed", __func__); + return HDF_FAILURE; + } + return HDF_SUCCESS; + } + ``` + + - Release驱动接口实例化。 + + ``` + void Bmi160ReleaseDriver(struct HdfDeviceObject *device) + { + CHECK_NULL_PTR_RETURN(device); + struct Bmi160DrvData *drvData = (struct Bmi160DrvData *)device->service; + CHECK_NULL_PTR_RETURN(drvData); + AccelReleaseCfgData(drvData->sensorCfg); + drvData->sensorCfg = NULL; + OsalMemFree(drvData); + } + ``` + +6. 加速度传感器差异化函数接口实现 + + 需要开发者实现的ReadBmi160Data接口函数,在Bmi160InitDriver函数里面注册此函数。 + + ``` + int32_t ReadBmi160Data(struct SensorCfgData *data) + { + int32_t ret; + struct AccelData rawData = { 0, 0, 0 }; + int32_t tmp[ACCEL_AXIS_NUM]; + struct SensorReportEvent event; + (void)memset_s(&event, sizeof(event), 0, sizeof(event)); + ret = ReadBmi160RawData(data, &rawData, &event.timestamp); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s: BMI160 read raw data failed", __func__); + return HDF_FAILURE; + } + event.sensorId = SENSOR_TAG_ACCELEROMETER; + event.option = 0; + event.mode = SENSOR_WORK_MODE_REALTIME; + …… + ret = ReportSensorEvent(&event); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s: BMI160 report data failed", __func__); + } + return ret; + } + ``` + +7. 主要的数据结构 + + ``` + /* 传感器2g对应灵敏度转换值 */ + #define BMI160_ACC_SENSITIVITY_2G 61 + /* 传感器数据采样寄存器地址 */ + #define BMI160_ACCEL_X_LSB_ADDR 0X12 + #define BMI160_ACCEL_X_MSB_ADDR 0X13 + #define BMI160_ACCEL_Y_LSB_ADDR 0X14 + #define BMI160_ACCEL_Y_MSB_ADDR 0X15 + #define BMI160_ACCEL_Z_LSB_ADDR 0X16 + #define BMI160_ACCEL_Z_MSB_ADDR 0X17 + #define BMI160_STATUS_ADDR 0X1B + /* 传感器数据维度 */ + enum AccelAxisNum { + ACCEL_X_AXIS = 0, + ACCEL_Y_AXIS = 1, + ACCEL_Z_AXIS = 2, + ACCEL_AXIS_NUM = 3, + }; + /* 传感器每个维度值 */ + struct AccelData { + int32_t x; + int32_t y; + int32_t z; + }; + /* 传感器私有数据结构体 */ + struct AccelDrvData { + struct IDeviceIoService ioService; + struct HdfDeviceObject *device; + HdfWorkQueue accelWorkQueue; + HdfWork accelWork; + OsalTimer accelTimer; + bool detectFlag; + bool enable; + int64_t interval; + struct SensorCfgData *accelCfg; + struct AccelOpsCall ops; + }; + /* 差异化适配函数 */ + struct AccelOpsCall { + int32_t (*Init)(struct SensorCfgData *data); + int32_t (*ReadData)(struct SensorCfgData *data); + }; + ``` + +### 调测验证 驱动开发完成后,在传感器单元测试里面开发自测试用例,验证驱动基本功能。测试环境采用开发者自测试平台。 ``` -/* 标识是否上报传感器数据 */ -static int32_t g_sensorDataFlag = 0; -/* 保持获取的传感器接口实例地址 */ -static const struct SensorInterface *g_sensorDev = nullptr; +static int32_t g_sensorDataFlag = 0; //标识是否上报传感器数据 +static const struct SensorInterface *g_sensorDev = nullptr; //保持获取的传感器接口实例地址 /* 订阅者注册数据上报函数 */ static int SensorTestDataCallback(struct SensorEvents *event) @@ -883,14 +855,14 @@ void HdfSensorTest::TearDownTestCase() /* 传感器驱动测试验证 */ HWTEST_F(HdfSensorTest,TestAccelDriver_001, TestSize.Level0) { - int32_t sensorInterval = 1000000000; /* 数据采样率单位纳秒 */ - int32_t pollTime = 5; /* 数据采样时间单位秒 */ - int32_t accelSensorId = 1; /* 加速度计传感器类型标识为1 */ + int32_t sensorInterval = 1000000000; // 数据采样率单位纳秒 + int32_t pollTime = 5; // 数据采样时间单位秒 + int32_t accelSensorId = 1; // 加速度传感器类型标识为1 int32_t count = 0; int ret; struct SensorInformation *sensorInfo = nullptr; - ret = g_sensorDev->Register(SensorTestDataCallback) + ret = g_sensorDev->Register(0, TraditionSensorTestDataCallback) EXPECT_EQ(SENSOR_NULL_PTR, ret); ret = g_sensorDev->GetAllSensors(&sensorInfo, &count); @@ -917,7 +889,7 @@ HWTEST_F(HdfSensorTest,TestAccelDriver_001, TestSize.Level0) g_sensorDataFlag = 0; EXPECT_EQ(0, ret); - ret = g_sensorDev->Unregister(); + ret = g_sensorDev->Unregister(0, TraditionSensorTestDataCallback); EXPECT_EQ(0, ret); } ``` diff --git a/zh-cn/device-dev/driver/driver-peripherals-vibrator-des.md b/zh-cn/device-dev/driver/driver-peripherals-vibrator-des.md new file mode 100755 index 0000000000..b0be50aa68 --- /dev/null +++ b/zh-cn/device-dev/driver/driver-peripherals-vibrator-des.md @@ -0,0 +1,369 @@ +# Vibrator +- [概述](##概述) + - [功能简介](###功能简介) + - [运作机制](###运作机制) +- [开发指导](##开发指导) + - [接口说明](###接口说明) + - [开发步骤](###开发步骤) + - [开发实例](###开发实例) + +## 概述 + +### 功能简介 + +为了快速开发传感器驱动,基于HDF(Hardware Driver Foundation)驱动框架开发了马达驱动模型。马达设备模型抽象,屏蔽设备驱动与系统交互的实现,为硬件服务层提供统一稳定的驱动接口能力,为驱动开发者提供开放的接口实现和抽象的配置接口能力。用于不同操作系统马达设备部件的部署指导和马达设备部件驱动的开发。马达驱动模型如[图1](马达驱动模型图)所示: + +**图 1** 马达驱动模型图 + +![Vibrator驱动模型](figures/Vibrator%E9%A9%B1%E5%8A%A8%E6%A8%A1%E5%9E%8B.png) + +### 运作机制 + +通过介绍马达驱动模型的加载以及运行流程,对模型内部关键组件以及关联组件之间的关系进行了划分,整体加载流程如[图2](#马达驱动运行图)所示: + +**图2** 马达驱动运行图 + +![Vibrator驱动运行图](figures/Vibrator%E9%A9%B1%E5%8A%A8%E8%BF%90%E8%A1%8C%E5%9B%BE.png) + +马达驱动模型以标准系统Hi3516DV300产品为例,介绍整个驱动加载及运行流程: + +1. 从device info HCS 的Vibrator Host读取Vibrator管理配置信息。 +2. 解析Vibrator配置信息,并关联对应设备驱动。 +3. 从linear_vibrator_config HCS读取Vibrator数据配置信息。 +4. 解析Vibrator数据配置信息,并关联对应Haptic驱动。 +5. 客户端下发Vibrator Stub控制到服务端。 +6. 服务端调用Vibrator Stub控制。 +7. 启动马达抽象驱动接口。 +8. Haptic中起线程,解析效果模块。 +9. Haptic调用马达抽象驱动中的Start接口。 +10. 马达抽象驱动调用马达差异化驱动中的Start接口。 + +## 开发指导 + +### 接口说明 + +马达驱动模型支持静态HCS配置和动态参数两种振动效果配置能力。马达硬件服务调用StartOnce接口动态配置持续振动;调用Start接口启动静态配置的振动效果。马达驱动模型对HDI开放的API接口能力,参考[表1](马达驱动模型对外API接口能力介绍)。 + +**表 1** 马达驱动模型对外API接口能力介绍 + +| 接口名 | 功能描述 | +| -------------------------------------- | -------------------------------------------------------- | +| int32_t StartOnce(uint32_t duration) | 按照指定持续时间触发振动马达,duration为振动持续时长。 | +| int32_t Start(const char *effectType) | 按照指定预置效果启动马达,effectType表示预置的预置效果。 | +| int32_t Stop(enum VibratorMode mode) | 按照指定的振动模式停止马达振动。 | + +### 开发步骤 + +Vibrator驱动模型为上层马达硬件服务层提供稳定的马达控制能力接口,包括马达一次振动、马达效果配置震动、马达停止。基于HDF(Hardware Driver Foundation)驱动框架开发的马达驱动模型,实现跨操作系统迁移、器件差异配置等功能。马达具体的开发步骤如下: + +1. 基于HDF驱动框架,按照驱动Driver Entry程序,完成马达抽象驱动开发,主要由Bind、Init、Release、Dispatch函数接口实现,配置资源和HCS解析。 +2. 创建马达效果模型,解析马达效果HCS配置。 +3. 完成马达振动和停止接口开发,会根据振动效果的模式创建和销毁定时器。 +4. 马达驱动模型提供给开发者马达驱动差异化接口,开发者实现差异化接口。 + +### 开发实例 + +1. 马达驱动的初始化和去初始化 + + - 调用HDF_INIT将驱动入口注册到HDF框架中,在加载驱动时HDF框架会先调用Bind函数,再调用Init函数加载该驱动。当Init调用异常时,HDF框架会调用Release释放驱动资源并退出马达驱动模型,使用HCS作为配置描述源码。HCS配置字段详细介绍参考[配置管理](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/driver/driver-hdf-manage.md)。其中Driver Entry入口函数定义如下: + + ``` + /* 注册马达抽象驱动入口数据结构体对象 */ + struct HdfDriverEntry g_vibratorDriverEntry = { + .moduleVersion = 1, //马达模块版本号 + .moduleName = "HDF_VIBRATOR", //马达模块名,要与device_info.hcs文件里的马达moduleName字段值一样 + .Bind = BindVibratorDriver, //马达绑定函数 + .Init = InitVibratorDriver, //马达初始化函数 + .Release = ReleaseVibratorDriver, //马达资源释放函数 + }; + + HDF_INIT(g_vibratorDriverEntry); + ``` + + - 基于HDF驱动框架,按照驱动Driver Entry程序,完成马达抽象驱动开发,主要由Bind、Init、Release、Dispatch函数接口实现。 + + ``` + /* 马达驱动对外发布的能力 */ + static int32_t DispatchVibrator(struct HdfDeviceIoClient *client, + int32_t cmd, struct HdfSBuf *data, struct HdfSBuf *reply) + { + int32_t loop; + + for (loop = 0; loop < sizeof(g_vibratorCmdHandle) / sizeof(g_vibratorCmdHandle[0]); ++loop) { + if ((cmd == g_vibratorCmdHandle[loop].cmd) && (g_vibratorCmdHandle[loop].func != NULL)) { + return g_vibratorCmdHandle[loop].func(data, reply); + } + } + + return HDF_SUCCESS; + } + + /* 马达驱动对外提供的服务绑定到HDF框架 */ + int32_t BindVibratorDriver(struct HdfDeviceObject *device) + { + struct VibratorDriverData *drvData = NULL; + CHECK_VIBRATOR_NULL_PTR_RETURN_VALUE(device, HDF_FAILURE); + + drvData = (struct VibratorDriverData *)OsalMemCalloc(sizeof(*drvData)); + CHECK_VIBRATOR_NULL_PTR_RETURN_VALUE(drvData, HDF_ERR_MALLOC_FAIL); + + drvData->ioService.Dispatch = DispatchVibrator; + drvData->device = device; + device->service = &drvData->ioService; + g_vibratorDrvData = drvData; + + return HDF_SUCCESS; + } + + /* 马达驱动初始化入口函数*/ + int32_t InitVibratorDriver(struct HdfDeviceObject *device) + { + struct VibratorDriverData *drvData = NULL; + + drvData->mode = VIBRATOR_MODE_BUTT; + drvData->state = VIBRATOR_STATE_IDLE; + ...... + if (CreateVibratorHaptic(device) != HDF_SUCCESS) { + HDF_LOGE("%s: init workQueue fail!", __func__); + return HDF_FAILURE; + } + + return HDF_SUCCESS; + } + + /* 释放马达驱动初始化时分配的资源 */ + void ReleaseVibratorDriver(struct HdfDeviceObject *device) + { + struct VibratorDriverData *drvData = NULL; + ...... + (void)DestroyVibratorHaptic(); + (void)OsalMutexDestroy(&drvData->mutex); + (void)OsalMemFree(drvData); + g_vibratorDrvData = NULL; + } + ``` + + - 马达设备管理模块负责系统中马达器件接口发布,在系统启动过程中,HDF框架机制通过马达 Host里设备HCS配置信息,加载设备管理驱动。 + + ``` + /* 马达设备HCS配置 */ + vibrator :: host { + hostName = "vibrator_host"; + device_vibrator :: device { + device0 :: deviceNode { + policy = 2; //驱动服务发布的策略 + priority = 100; //驱动启动优先级(0-200),值越大优先级越低,建议配置100,优先级相同则不保证device的加载顺序 + preload = 0; //驱动按需加载字段,0表示加载,2表示不加载 + permission = 0664; //驱动创建设备节点权限 + moduleName = "HDF_VIBRATOR"; //驱动名称,该字段的值必须和驱动入口结构的moduleName值一致 + serviceName = "hdf_misc_vibrator"; //驱动对外发布服务的名称,必须唯一 + deviceMatchAttr = "hdf_vibrator_driver"; //驱动私有数据匹配的关键字,必须和驱动私有数据配置表中的match_attr值相等 + } + } + ``` + +2. 创建马达效果模型,解析马达效果HCS配置。 + + - 创建马达效果模型。 + + ``` + /* 创建马达效果模型,分配资源,解析马达HCS配置 */ + int32_t CreateVibratorHaptic(struct HdfDeviceObject *device) + { + struct VibratorHapticData *hapticData = NULL; + CHECK_VIBRATOR_NULL_PTR_RETURN_VALUE(device, HDF_FAILURE); + + hapticData = (struct VibratorHapticData *)OsalMemCalloc(sizeof(*hapticData)); + CHECK_VIBRATOR_NULL_PTR_RETURN_VALUE(hapticData, HDF_ERR_MALLOC_FAIL); + g_vibratorHapticData = hapticData; + hapticData->supportHaptic = false; + + if (OsalMutexInit(&hapticData->mutex) != HDF_SUCCESS) { + HDF_LOGE("%s: fail to init mutex", __func__); + goto EXIT; + } + + DListHeadInit(&hapticData->effectSeqHead); + + /* 解析马达效果HCS配置 */ + if (ParserVibratorHapticConfig(device->property) != HDF_SUCCESS) { + HDF_LOGE("%s: parser haptic config fail!", __func__); + goto EXIT; + } + + return HDF_SUCCESS; + EXIT: + OsalMemFree(hapticData); + return HDF_FAILURE; + } + ``` + + - 马达效果模型使用HCS作为配置描述源码,HCS配置字段详细介绍参考[配置管理](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/driver/driver-hdf-manage.md)。 + + ``` + /* 马达数据配置模板(vibrator_config.hcs) */ + root { + vibratorConfig { + boardConfig { + match_attr = "hdf_vibrator_driver"; //需要和马达设备配置match_attr字段保持一致 + vibratorAttr { + /* 0:转子 1:线性 */ + deviceType = 1; //设备类型 + supportPreset = 1; //支持的预设类型 + } + vibratorHapticConfig { + haptic_clock_timer { + effectName = "haptic.clock.timer"; + type = 1; // 0 内置模式, 1 时间序列 + seq = [600, 600, 200, 600]; // 时间序列 + } + haptic_default_effect { + effectName = "haptic.default.effect"; + type = 0; + seq = [0, 3, 800, 1]; + } + } + } + } + } + ``` + +3. 完成马达振动和停止接口开发,会根据振动效果的模式创建和销毁定时器。 + + 马达硬件服务调用StartOnce接口动态配置持续振动时间;调用StartEffect接口启动静态配置的振动效果,为驱动开发者提供抽象的配置接口能力。 + + ``` + /* 按照指定持续时间触发振动马达,duration为振动持续时长 */ + static int32_t StartOnce(struct HdfSBuf *data, struct HdfSBuf *reply) + { + uint32_t duration; + int32_t ret; + struct VibratorEffectCfg config; + struct VibratorDriverData *drvData = GetVibratorDrvData(); + (void)reply; + ...... + config.cfgMode = VIBRATOR_MODE_ONCE; + config.duration = duration; + config.effect = NULL; + /* 据振动效果的模式创建 */ + ret = StartHaptic(&config); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s: start haptic fail!", __func__); + return ret; + } + + return HDF_SUCCESS; + } + + /* 按照预置效果启动马达,effectType表示预置的预置效果 */ + static int32_t StartEffect(struct HdfSBuf *data, struct HdfSBuf *reply) + { + int32_t ret; + const char *effect = NULL; + struct VibratorEffectCfg config; + struct VibratorDriverData *drvData = GetVibratorDrvData(); + (void)reply; + ...... + config.cfgMode = VIBRATOR_MODE_PRESET; + config.duration = 0; + config.effect = effect; + + ret = StartHaptic(&config); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s: start haptic fail!", __func__); + return ret; + } + + return HDF_SUCCESS; + } + + /* 按照指定的振动模式停止马达振动 */ + static int32_t Stop(struct HdfSBuf *data, struct HdfSBuf *reply) + { + int32_t ret; + int32_t mode; + struct VibratorDriverData *drvData = GetVibratorDrvData(); + (void)reply; + ...... + /* 停止马达效果振动,销毁马达定时器 */ + ret = StopHaptic(); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s: stop haptic fail!", __func__); + return ret; + } + + (void)OsalMutexLock(&drvData->mutex); + drvData->mode = VIBRATOR_MODE_BUTT; + (void)OsalMutexUnlock(&drvData->mutex); + + return HDF_SUCCESS; + } + ``` + +4. 马达驱动模型提供给开发者马达驱动差异化接口,开发者实现差异化接口。 + + - 此接口在差异化器件驱动初始化成功时,注册差异实现接口,方便实现器件差异的驱动接口。 + + ``` + /* 注册马达差异化实现接口 */ + int32_t RegisterVibrator(struct VibratorOps *ops) + { + struct VibratorDriverData *drvData = GetVibratorDrvData(); + + CHECK_VIBRATOR_NULL_PTR_RETURN_VALUE(ops, HDF_FAILURE); + CHECK_VIBRATOR_NULL_PTR_RETURN_VALUE(drvData, HDF_FAILURE); + + (void)OsalMutexLock(&drvData->mutex); + drvData->ops.Start = ops->Start; + drvData->ops.StartEffect = ops->StartEffect; + drvData->ops.Stop = ops->Stop; + (void)OsalMutexUnlock(&drvData->mutex); + + return HDF_SUCCESS; + } + ``` + + - 马达驱动模型提供给开发者马达驱动差异化接口,具体实现如下: + + ``` + /* 按照指定持续时间触发马达线性驱动 */ + static int32_t StartLinearVibrator() + { + int32_t ret; + struct VibratorLinearDriverData *drvData = GetLinearVibratorData(); + CHECK_VIBRATOR_NULL_PTR_RETURN_VALUE(drvData, HDF_FAILURE); + ...... + ret = GpioWrite(drvData->gpioNum, GPIO_VAL_LOW); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s: pull gpio%d to %d level failed", __func__, drvData->gpioNum, GPIO_VAL_LOW); + return ret; + } + return HDF_SUCCESS; + } + + /* 按照预置振动效果启动马达线性驱动 */ + static int32_t StartEffectLinearVibrator(uint32_t effectType) + { + (void)effectType; + HDF_LOGE("%s: vibrator set build-in effect no support!", __func__); + return HDF_SUCCESS; + } + + /* 按照指定的振动模式停止马达线性驱动 */ + static int32_t StopLinearVibrator() + { + int32_t ret; + struct VibratorLinearDriverData *drvData = GetLinearVibratorData(); + CHECK_VIBRATOR_NULL_PTR_RETURN_VALUE(drvData, HDF_FAILURE); + ...... + ret = GpioWrite(drvData->gpioNum, GPIO_VAL_HIGH); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s: pull gpio%d to %d level failed", __func__, drvData->gpioNum, GPIO_VAL_HIGH); + return ret; + } + return HDF_SUCCESS; + } + ``` + + + diff --git "a/zh-cn/device-dev/driver/figures/Sensor\351\251\261\345\212\250\346\250\241\345\236\213\350\277\220\350\241\214\345\233\276.png" "b/zh-cn/device-dev/driver/figures/Sensor\351\251\261\345\212\250\346\250\241\345\236\213\350\277\220\350\241\214\345\233\276.png" new file mode 100755 index 0000000000000000000000000000000000000000..50db641d4d4b2927b4c52fe257c3b513f786cb68 GIT binary patch literal 19972 zcmd41Rd^iDvMtzRX0Vu@zd>zRkl_ zf7QJ*GAgoibyh@1W~oR^NPNfx02<;VO6p2nny>%>faveZ0|wX!0Z5CADv^QyEdoHv zj2sP7?I0Z#=_1$%;X z{yl!7K-MphFNb%Bm)*~TqkzBczN-OefCkUN6UB?UFGc5o6~?#S0^sAMf+?hXeoyAg zDg_ua_xXC9owFe`*#BKXxu7wq&xyCmjXYw6)1YHImy;(g;?z1tK8#%6R%s){C?Ohz zuxWzqPEh+K8uQZn{B$I^yUwdh@Id*U1T9%JRs-HE#J-bv9$)#x*+9}&8FLpGPqhjTp&?6|J31m6l`Klw#dvf zB;IU-2W|}fi(dc!EZYpMNuGsTTc6hO!0W%P4p-flrA>2J5nrglElOofgyU}v33;qK zR4-gZcwtf$S-T$Z!H}Lu@zS{&w~F^yGyTdQ@14_2z;YKfNs5g%H6i`x`p+LHh&^uH z##z~5QtF3rx(_g{SHV{k9cp3g**JJl#vb0xy$ZJOy{h0|Ro2<(0^~%TafO{|3`U9M zD-e8x{#aoT(Q?K75v6hKeywzL0`FVe7jkl#9MI2y`u2Mgc%q-_cjjf-=$tAPs!=rM zkDl3ox(s1f<;_5Hn`QcZx#Gzyaf00yyjH2DYu<}2X%+SV)z3Sa_CXm~jr1f+(oP6fltc?dnKFSi`;$RfnA3b4h z@&pxf1A`WISVSOV*};Ery0C6Dh6o;+VeZ7WcMmR^ zX>ZN9B8iOs3*nvbbL60q=Wk>0VAYwM;9N&z-7k&y*5!hRo`N`Y`hGY$=_UsXYR$C!*AJ;#34q_MM3U z!gM@;W3PM)S+{eToM%H!HKpRdYH^O3F#|lDE;{3KD3GCd4Ao3E2^~?eb`X4B>R24* z0x7y_T**KEVO9!pe;*Ew`shH&f-oh(UEvlG-wJ#ACD8~WZFKoh=~zIlE{dAh_NGg2 zpz7SnQ1dH*=kT`zxuiVp1>0ttsTTv&)_x)F?(D9|a&-DKs!hjn9^}L(T(mR>c+NAt zLG!${Sx91cvC{|MgCEvbQ{Pbj&!bkb$Wx78N+a~VfZ|SSe?cO|w<|^e?%Uv&m~Ydz z2fJ4RY&3|PO z_8xe+4Iqlt)I%~}Wnzu2xOnLE&(`Oj;hNL~CiJs>>-dPFpR5Q=MzC61!Ydt2k|6y~ z(k0hQeQD9iYn-C&^pr}FYRO;X5_A!j2UG#crLr+ovtm4a0-rK%A5~sSYHauEn4+@f z)Zx^^Ryf68z26Y-SJ7oCnTzF`VAN-93(d)~NS+69W{52VIQoU~=q6_IKh!I`)wmK8MT1$3N4Xgna}kq-sFrlN8_K|O?rd2h zdB^2RKsI3lkQ5C}_Lz!wyL4No%|>0w5}M{mf)CNnS!_|ik|(G~HkFd1MP0?af2a@# zE{OL6rfwT1!L~*py?1Jow(N9y=5eheJ{e>SvTisg8vpGy z?AOz5N_X-76AxS6@E5d)z zX4SInW}In=&%2gk$dMjExKZUIuuCsLam{N9Od6^+L)#UidxR|_T1iw6R2YP15% z{BpK|qbM_^g`Qh53T46u!h^qYM!hWfyY_9p$^z^jy+3#d=E&LO$*85+J}AqYXPqc! zBer?#LPT<$93)yq7^cKT69!*+2NNo{OU=(+t-yM4%F=;axCc5{7TH(uz>Kj!wzo$7 zTlA#6aI|s7hkz82SrONsc7iZ$AP-2?A%Wh!WqFKA;3i_O0mk_O?`S*&`{39UQOGrg z>?>f+9X)`n(;C7$k(fShbmead_^)t>VgxJM#q>8Ux)uvc^bNyjDf;){_#>0$D^KVQ z#MfzJ_5D|S{Pns29qs#JFDv`92t&Id%f#2neIn5j zTit&0Ls4dSb#8eoWeWX?X^?qFroy*AxA#U=WcF}tzbRr0TK;C3c4e`czZ4SxOkhmp zvf7NZanx@N#qYyhTPM?VLBqtB`Cqdi5cdvQG?dQ2?(_dS4M7DpAWB62ho*7jK)}~L z$v58wjlh`UIx$!~s)40K08$3EV231}>;O+&jS6^HrnKdm4#?N4e#pu$@iK@O8k#wd zPCv)?*tKvP)s^C*Wby@v#5L+}?*c`bu)3x`3*S+BHS>}6J^qJ%oinQ9$e<`O`0DLX9_Po4-#5WJLUl@^xeU9`AoO30rjA*eT zVf6r39`;oV4J!yQda0Wqw~#h&CI{8VZz#0rlfJKL##I|m-kH@HcPR?uu{nL)TGF~o z3{5-v@}~l6>LvLkpcxfW(_&RL0!k=hP1O!57kOtM_*;wzE#_)Ze=S|~{vFX?JQNGz zY$pw$uvUoB2KRTLgIfAIv)y5gUR9h|(fO6-#5cj+>z8*_8+JsZ`_;!ZR5q1Pe&<9e zYFV>scu2`LsN)+9A*h=JK}E42N-l1Gd=0-8r?t*wFXTpFoBVUBUuki`5HL4# zX4RH9SC*rCL7?B@Ra^w=%WFi&7SnPs7%k`K(Cy*D7@Xu5|CujE-6h2jzMC?}cazZ-7; zDI+#gFc&_2=+-Nf!B@g*Lw13rAEV%?_DDLYgCO8w0j6JGI@E)`{MI?EH=n_lkgjaZ zZkV#*Q_SAOSF#YtL^|=L-(UMGh#CE2#G~0%h8q$oQ1!SZ%P-*AS116Q!{74HIy7tc z(tfzgyElb7|B1@`-p6q))}%?>Mh>gOORp3+>(3$u!GisGa!Tzw+!s_Xm((oIdJX#u zRtbPEoY4p+AZSSZs(kj~87`*jzndyJ5_(BY&S-`0SE1eB86Lk}SQAV(-T``U*zT9W zX^(_O5ja*Wx_iHTN{VYiYOusVPkN7L4qG>%ZKAJ#5MVn0dIuB0R|G697t>fyuBbgc zetfC#uc^d=IXAMluge}cH1pSy1>xp@x>i%b_3Tc!Lf;RbUE?-T4C{Q!&X_m->?VXT zR3RlqbL=YnVvNHo0jJXabS=NY$nOjUp^daBKtuENq0j#F81=^pUR82SqP#C~!0trM zw+DO|smqN*PsYL5J&~+GP3KL40{rk1@_h#&d&+6Y{{SI4AOnfMv^Qp-+dq|!uXjqA zI$sIdvLwG7vrj)*XJAd%J#btecEuuVD}a(~PFFb?*B>b`+{^l|D+FaaOA)ER*6jaW zC(bMqX3OVEDYD^nY#fzmg0N2lA9RMdC4$%vxgEH}HT{}7CmPN%sZ59A;t2p?sbDhh zk{ED&-`S?c?24Ig6k-Ww=`M-p`R$>8P$Z~vKx$C=XIm%H+f*e!wD9eYgub86{SCea zKDv087?TRK%8=@}uj28KyL~a(ErBNR5Bst3v}n9;JxR^cCr+gb>(`!-)u>ah5MI0d zxm*|=B)ddVVoiloK#hPSnO#8~-@fLpL=&uS{{FLsk}BkG{m%*XR7)7nf$OovmU}4A_@&>A#o7KUr%;y|#gnp=9YuC%+@Y&! zWy-yL4{^e7VJ%dN&^mKX?9yDei5N__P7+Vd z=@heysaP_Ozwb|RXf-m8Fmc-BW;z9>v6-o@(Qx>$ZX?Kd{Da9ZRhZe`c21NWQ62~? zxBLugxYjuJc3*0WDzpRNc($~Qp+fD_!6*W@Ct!6nYNb#Wc(}%@y=G=!R9aGF1S+n| zZ7A-AM-0uS93`*+-j;+N{3&|Tinksa!wS3h{!M9isbbU}*P=!qzgH@I{%T<=VjEjn4%feTCyo2~EM&g^NoV1$3DnGk4AS__ zQm7j3e-4)Z^<>HS&lT(p(D~T;EWHVk${XiT-z4^;?UD?nPL|+Ij!(ZH(y+ASRg*7a zOTa~r)}VZ$qYW-Lc5}bJcIQG+v)LrJA2dt>+E~;lW2M`OkZa9-r)_X!p_R2=-5{nm zQ*ks#ng5VAwFRfOIdW!|Y=y&pkahy|{!dCuvjD7<)RWC0GG?CkXS^4YUn~zBAz#c4$>i)6tw~|^z%!CDcle9r)(2JC4%35pVx$2Ho^{sG&j5b>3 zAO6jvQy7I!X6J!8mBCsiA~@_vO1U`k`XJ3H(Qjb4u=PW$?#*)X5r_hZwPsJ)V8F@v z4w>ptBgvj+sKUA}B1SL&yFHkY;04YlLzsM%Nv0;!)@(u}!41K9zD>y) z=iW=l($1Whl#`<}6#^W58hwvjLy^eFZ$#M8i-L(O!~rID74=lUQl@RJk23W{4Pw^H z9iufFRD(DWhRCP|)vsdCxsn zGYmE$(9{_z`^(qL@KG2?1Q78}EWdKJ{$0{U@w;uWZhfSTzjmz z|AOpQuAoJj3+i~|bGGDI_2szlZ;->$N`RQk;V517Ld6TPiJ}=5iX`IELzN^89xX5e zBv(458xUU@oZu&6d0^Mq))}c8kCll41W>dvt^$1WVgr{SDun&Mj8v{FczoxB5O!1s zb&ZpHx&S8q!VdeJ%`@g*d?-V6Zb#CSRIb8atxo8;uu4-l?irDzrH=bsefZr~LvzE8 zl(ran?7XpF{Nx}P8)f31WR7avgfCO7BWFvX$&kiW!74@{g|u6K_4;7kHA@?HQ;jx< zymPfP{u`-=l!?1}?YL}1n5Drp=eMJG8=W;|#Y=TqyVVDo4)Sx^{T>@tglLHAPA_=# zswx_CPHW^be|}^$!-*!fyoQHwZxh8QMC&wZ6o`hl?wkX7-+=?V`U`I_8FnwP}) zV|IkeMBB{TA}@0f^A68G8#LnE`!=$0r{CjO;y#chlg=O+sb9Pk?94?Nj6Q#a9*8*$ zO7H##398g%dxyyx$ddgm6IDF1RkB!=yS32sd)$ZXx2@#p3(sS|Ai1p-H@NEcZ*VZ;K8>zdNgW@4A0RBMvoi>GN&ezflt^rn3Q*F0UrQb1oA(Eu{=Bd!cRuQ%0!T~*oI znB~OCDo-t$sPWmeHL}&MLlTE!F-g?Cbp>eBuksr2S$YbC#9sI35pL?y_A-5$f8@_*!14cZ)GS z2cO%&+@vF`jlj+g(H6G==6PLi@ZUWkm{hrM6X|fJgkdd@IU`PuRfJS?=PLo+XHKgS zD-93u+sv}u+CePsTLZJgJ{v#WSBi4^!w3wN&M|A@NMkn`g7}JQhN-XpsREIm3(SqO z1sfmDxyLDP{0Pg7cr3z)@L)(~H?^+ZYAwO&H{wNz8z&N@-XSjQ`@>IqJEW5pIcl6# zF8e3veDL*2uC7xQcnQ|Q^WCWm!|{n=-CDEnFvf!1dX*h^rcVxjV{eEoPp3NjMm$SS zuoK!LUT7q)cb{)Qo$b}zY-Q0UN1wc*Ts zE$U@Yt4}n7o8tdDy6C#=B9}YiewK7M47WIDs)dE{dJLID={TLWd+8hH$)z^Ky!v`w zfFoJu_R1apt(T=2kEKQV-7}LCO|VgvMRH?Lhywpd0E>pDOJ-?xs~so92;fz~nlL|5 z$lj@tQT>k8{hr407yQ~CrQs=8XPAiX;B@CS^3PsDwxu41S0tMG1Jl$^7Tp4PSEd9i zFWC0ZYAOy`TCi9mai%D=n4cShA4TJm*Yv-zf1tw1TNl1dm)z^E3BJ*|pYNmk9r#W` zj^a4l_K<-b5}Hfuz6b@y6QAhh2hvk?tk`dRX@j&aXYzxcK`5hD3Btf>Vp~>DAHiiR z!H1lU-Q_5V8O~qowzGBFKm{AQJ-KD*GHprGipFsRADg@A&~z1{j5B>ss=lcThV(KX zVU<l=0&lT9!W@A8S#$?g1FaGk@!qKUp+xQKr>Kod(GuxE5sibZ$wC0kytV{EgXBb(14hMu= zzMRml1^LHTx#Mq-2~qS%m5;MUS8g|)Fnn`be?b2=vg0{re8|V=mi0w z2tz6R&VLXrp0I6vzd?dDfz#F#v~ChLCHUpLzkIR!F9NIIBqyH7kx=Hhy(}Q^)q&D7 zpAjGKUHcgfsI=#(m-VW{U(-7#$gQ5pR*Kj)30%z$&<*o!eu|QX!RQEqZPa#=hQJ+Mb`{)f%ah%LsmfVeKobD}P zY%J6OBr)X&6KOQG((Tx3c0ceLE=%j${eFLu+vn~r2#+ni(-Sow=)efdA1f3*;1A6_ znP$e)pJBqk9XjH88ljkB61Q2hmtcUMWHhtnaLy}({Je*TYjsXx_t@QIG2@qh^`^6^ zY;$J*hHddoe|P83&}pJdbh-)pC%y!)zEQ!LRf@s$zE{qk=7QwJzUMUjc)p*w7E4px zTo1vQJH$kB$flLuKYTgmwTVze^)1E17tH^#fvo6{cmJ?V?@}X6Iz(5-W2cZ`R)8A< z6Y?JUYFDZOY**W!&~=es04f?DnZ>B)*n{fKmUL$DJICnCoar#muC@))wjycdumU?H zp7U=*44p2WF8eo{MA1jNIN6-sQ1g5a&68lA;sWe;1zLR4!I&0xS+x~4bkrd&uqEpp zfY5EZk1P>H?oWtsz^ai4&6?=Q2jA#b;^0tb?>EDy#qZG85si3}0(smO`M!wescI8N zx`S#v{&vSBTFt@+TspyM?ja_k^*V-3%t#`PT9JDKaA1#X&vVY#F=IZKE2>xJ*3!P1 ztHY4aUJi4GnIz8wqhc%=f@&p^8r542tZ8t#tmXw7zgthHk|mxj3o9@&Ad{KmBn4la zL=&A5K9$>KzFgBUlgT;P;0C7aRGmv7HemJ&q-hdwT&K*azHv|eRL9m?zYNLZ!l2o{ z&vCBAkLk)+L2tHD%GUE?K_Yj8rXa}r)N9&eVU0G4+%b9A+&eQM3fK0Zj%Gog7K8d8 zp01*J)U)@f80O;p+p*160oSmImSxnO`q#Jy;@he7ZoJ=~SR1>`B7-LC_NXaED?->B5s@F?ugP*ul)S09d5nlC zQy8qMyRvB{1X<|D<6mxtrXhr*vIit?P*_nE@<*QYN6EPt)~VP-JH?UVA)I2a8r1FZ zj#zBl=KnJ}2n9e-qKb6%*PKsAT-EVuXdQeCM-&sG+oZVtketf_Umw0JP$$O36tzPp z&|3nFOlyE@X=#6^(PMPqp|s^~Ra z+(94TP?a*OXZi7Gjet3lfL%TYwp7u|#<890X!My_Z14NZkEIo@V54S8j=t5uizZ{? z=A;e&`HxAe$jNCZG>P=Ks-vvk;S~toy0eRcmlDC}3iQlQ<}%is=qL3<;Ov)GPFEIM zunl=OXnF@-B5~c>>ymi<>zhh2O_==aauQF4M5t$3LI*TVj`FF>?1K?rnq1}HN$Tkt zuO!Fke)9@UE?yMk&qj8Y_PwexwI!LH}ItWR|3T#PN9wY1&$j*Ik-IxC(?CE zKWrw`&NmRKuG3|0Y@prm?svK*gdGwAE$B_L*!EPj&6%krO+a`X$HH3?nP(3k+S=9fLG`izQnE|u#`jhYD8ItGNDqH+1Z6zzSQj$| zP{9^=`ed}k4uNk}_^x&Fo3&&N>f)C8OjKc9m!%(Y2O9^@W?KeF08&kh!&PMWp9aTE z*3l3RF9JOE?zwFQg_?rze>&)o%&30$w;lO^5+*CjQsWeK=ATK_1a>70jr z*oiLOKzCBBx3e9EdIyUL*`^{Tq6~N((jF6!mLeC_GUU6sg<%0;2c0H*)1I zV*_;warV)Mi3!`LSC9P=w;NmOJ{N9QqoR`(AmbO>ss50gUFg=@ejKWPL+mGdYTOkr z2u(K8e>K^$xE~z6DaiflG|2&459+_L?xuSEPG+ym+AU5|D1dGHvJuMYJs9iHTN>-F zHBXxE#UPV25`=ImIYWt1xDkoK-G)#PZW>c znx!=D`wqGz5YI>K@c62k*b)xo$LXo{;pI|CxE&N&4ApPHVn{q~Cq*GYO2x!1^d%ga zS%cCuev>$|Xtpw{G9EQ_i+jSP3x+d1(jLBe^mDx+x1Bj*SeBp$a{U-*bb|=bSyMjb z7F)SrEWX$lN=<^q&OA^p{iH^*Ttb|40X_!J`O-@?Ze- zU%L7$y1DXwy4<(@6hB)v-xd}|niPUxCNc$yWnuFA2@JHnqRAi?Qb0ECDBz`F03#h? zV7t&f(5v6`fqI_WUIt`Gmh@)Q&X5PcJW3$yUz%=~ga@iJ)(kykb6JeT3XcAc#h=(U z%Pt;xl6-w=*wG#8OSX3uLZADoH(?;<>wAo!;ucP->{Osd)|@ilzizZMO^t+Ae)HlU zT&tW{H)&`TEME@C-LzYG2{;rCF&MA+BCMap;u9;q2*5hR8<5(Jq)+DmTm=&JO&-Ll zC&AMESYW>y78!qmA%miQK2xp@Y{DCg_3a zgPuN8X*Xh(KDDUkO5F`dr~>gADI{ZBRxx277~{+l4ww?Rqo91Mzg_cnc?~t=qCaAg zy|HiF>~Z9yM~d{rjgc1Tg8KD@PiAJir*{)Q_tMg4NC`QxP&l8xC%&hk5{3he0P(WWn2h7*Rs_BhoorZ|p? z4DNNNTgCY~%1_55aPc{hHiG3T8D0GLqK}KaQ2@$?Tr&Oc@orfOzjWc>6Lp2gC7kI% zI)d;R;VG;_g|@$w|ZRkAo;jOAM5{3u$DmSZ)3%PKt*yoT#9z z!NG`N-g{D!ygfEnn(3w&JeNaL=VDxk)u37*^J_0+2Sa{?HA!>cw@(w4aRtV6s>vb| z`}~+zSSfwZOaz-^XiG6$I&6}z(jI0cDLxJIR3J- zBDmB8Vyh#2q@1+svuv|FbU@+uVORY`{c#%2_P>`v8Z`F_*UV~fbg>}EWqi6gh}L#{ zTa5y0-x95S8GDb^om+35Hj}aYA*^JND^LLn3IR{VzkYwp;CuA2xCdG1Hud$3I1zB9 z-rv@u{1zmi?Zb_W(QtTUN%u1^zwWzMYEokNO&gz&Xg)h#J_Yd1xJJtnFCM8_SD$}f z82H7&Ej3^cw0rZaRwIt`KH|-shoN$@1=XP5x@1zG_1=G*jL!klsjy<=Z7aW7YY}8G zk^0supercerigZ$9ULDjLBnTaqPUKF9|{;&4dHnBfZkdvH;1%LmlxZw7#%;)b_64S z)xH29FdSp^p&Tb+agffDA^1AP4I~-8dg65pRgF&S?8ZU&%~>QhI-XSyRv*ugaA}g`{7TlRF$4*Esc+n*KAwg!BsoJ=``$mFwX)VfO;k7E)E5*`>9rk)kHY>%Ex3 zF&iI7bx>ee7WhMOa9Jmbz+LH8Bu208fGD#91rO%n>Ez3r#!`oRC41E@9T}Wz?%=c= z7OX~ulgzPlyfmaovs<|`4&ycuv*$7sly{Usf2*FoJ`%!8v!p3Z*zZp6REs5AFDk~w zM=u~i>O(^Xj;~DX_AXPSLAqIJji}z7pdArRB{rcO6rZ{kNt74kF};+k4o__g4?~9Avbzo<+f34^|}w=|6>%n-vYH$yv3ZVzV?R0Yny$L%S#1 zxr$HN<>HCEW(5;t^g!^E?K+2eKce~|v-R@*AWgrt)-TKTLApEIwXt2s%ek$ZUU}pJ z%3Q=Edl!Fr5u9D8M0=q%w4GAmdW+6@srNOvD<|y%?H!}sV+ex>nDw}hCOTtpdtkUO z25{)ag|HU@$vt!)IT9>^Y0P7SM%Na(W{8QD+~X+xp{!p#=O8P@1C7gXNf(G*PCuiN zV<>rWFqOJXsp4TNVxH^5TK_myc^i2cPnK|jU;cdhN;STNNOerdj?HAAVE*ZmSGkb@ z=NkE0eOZV|Se?LIXFjO^9v{w)*4GnwbeAR*SIrZLW#}fy3_>!lG2VgRVN8VF>Dut< ze-Kv2p%mj*X5|--`UgfxU4x>cpnmzLt2EuUjZP|im@F`~(KPS@z|tEYP)nyd29UVwIKH+g1wZpg?gY^N*2&bG=es144Sy}L`F4(xs!tqMi% zAYd-ansZg(W1O7T%|3RHuMPdcyo%}M{Zk02jn+Y_ahUd1u*LV7lpcU}RXj&E(}H+^zOt{)1Dq9uM0<0J=?Bk> zxt8)v3(jF9p_lbNW`NEpQGd+@Z&Jtz^z?h2NH!T(`wf#tyNK7J?|s<;5$8P<|swZh5}d7-e->S?P%jy z$)Guv#~s*oozvm>=}xm5V!1O4j}AgmUjvG z=6OKM0<;9{?HE%&@69s%!Gt0Su_Rc>FbxkUZt}hPTa4}4r0Ax(+=U3mFW&#&#U1o$ z{MR%8JeTX?H|<}0U`?_dvkB=aWrK6r#RfdHwan!z4z6 znIir&2DQDE*QZw^gY)ZshY(u^*2zsV9xQdAYvA{&;MFSk39cd&<8X%-XeS@#*x1bi zeXv0da|Vlq@UCE#a}zjJQ@71&06$Ij)psY;wFmAlM;c`Lv&qF@peLbiK|O0rGVl~5 zL8EFECHt2@s0j((m?^F2ty&_{D=(4xg>20R67X0~c3oDML4yLtl!_oN9osVXqQn5$ zuu8Ui!2`f26mftX0UEPtUm}6&pN|~vK>stTP_nk&gK!Y9uBDnob@8D-*przT;bk(z zL%%Ce*pGXr~UpL;%rdkyIb z-Hv(bT%U-e*pW!lYC;g|a;y*|Hrxk*k%msHir!SIY`FxJ&;Ck2<|9RZLJIV|8Mi?H z>a529aOmzu5xi~Q2=#+_u1ri1UQZ+&Z23cT>txcE_~{+cjCo{?($D|e2KYwu-#I${ z9|)?~?32j|fU@awNyg9ctxRtYRTUNu+WZtxJC)Z0imiI;_Y?02_I~wRJrybIKvGT zUh2pXMOdt4YKu$ob7l>?*?Nsk*z3-#wWBRiYFLr*u}_)?!f^lRz9^3%ZY5t&b*883 z+!jXt#l@@g^f}744c{Pa`c)To_!h%o-~XU25H5PyXrIhW&B#S&_>te=A3o4}%0KKi zz>0PeFd-}HB4dIga^8z8l)!PH#4y6k{^?F%_E;FG7&;wt?;}hbifU|dPH*-wI5E4Y zZh-jQI_)|PbR`(rm^nVPn5(4xQ||j^vA{vm4k3`WY~RJ51sWB9&r~=sPun?%@OeBi z6cqZ+9(-we@4Re7bCRgswaYgZvCilCGm95AO?5?kUP%!4ME7?WvkT3Xin4LMCtm2H zE{+Z&5$U>T|K<}M-BmKw)o5%OIZjWsOWjUjomukk0&L z7UuZ9TvuM`G_~)yyz&zoZ_p46!wz=lJJjSPRbhJ$L`E3{)m-MDAau!}>z!b>3H+@W zeLshpdwY025I6NnmUj9=LWwg}B(SxgOojse@@Ve5({I5R+Uyd0o<>8wW!MW z017^-2_qM>Ymj*>vduJEYAy?Bp2FYifr<3oew}X-9?+ujGIc0#W$;sOQdgl1RkG&F)7^s3=z(!>&`n`!PivZ ziRL{|Y;Iz|ps9651kr>`WhvaKmZBW6om}}Ii6r8BO9`|EM=+;{(c%JR!y1wxf_f_YurSln$I<>jy8$d`Yya*RwHfBtY@%)rgk(JlxN(6I5xR-<=jwg`*~ z?8YBHIRtmt#W$x>Z*DV!T~?#i0*kwOU}+x)j;(PTNn*xrdck~QL&v)Ef)7!0m#sSA zovFkhEB0*ifSTj!BREa@8CGI3%o``?WG3rZRuLMB)}m*%`cc4xZC zOd1C3w%y{t&Jy&Y1&yKFqJ;v;7I!$W?J=boj6+mdoJ8G4kuU};W>HvV`QVp@PBny< zJoMt#zmGNLCm+)l)Yhkz2i%8?=W_>mwtn4yn06<&AB!Sc})i z3VvNn$g&KL!qk`i&ZR#2A(92f)S?h13JFJ;k3r?d?_sbKv~2 z8*I5PQ`{Pm5IK3P5*0GP$LKWv0mk%0?RT8?=+9=@1J!jok)VE$24P*~?;^KwOjj1Vf9LAmdJHcqAk|7ZIx?!Jz6F$!l*S9J1&!kryg19Q0TRS`fl zF2ZeHD^;6k4U0|!qHg?MY(&*GrQqLPn(k?SU)quwX1*VrdByJ=*tT$%^&kgjrvO#s zMY-gJTVP=F8tdPA=D)m=AgKHpR>FbhS9EoI8MH$T3QOIlE4|r)Yn{WJlXI^pG8P3F`h?I^-id1P+LwU7_QBc3`o4xr5EWtM#|bme5yrS^wa6UBQZ z7!#?ZHepC#Y6-ZOYj1g8zc)A_PYx#|dEN-uaTLaG_-$maN`2+#K{}LF9wcV!{-$hO z?tw_caDJ2-j?;Dc1sw5291FDK(S_bKZ<1g4^Uu0a)VwlHE`LyRE|K2cD~7*DBtybH zL(socRET*xI9&2Md|J!aKR{Tay_~aXlf$QWmw0hJ!;aD8v}8D|63S`w*7Um4~>MMMO8p&YLo zu$qpA08h(YR;$LiEgp?@!au%-=pBOh`Z-g5BsF&;Q}U=h;>s4B>uZ}kXH^Z?IZZsZ zXp%qD7A$&IKd#{_T&EE&S)O?>r^YAaK~4Fv(P)yW^$1jH8(V(gj_}FFWw5F7$Pi(T zRFOyF$Z<#DfK1-6mNl{OWY<=i#VRFs^Cn?v29o=@yM*TTWMk6=Xj|iZ;FY+=zMUER zdnBTSd4T<{r*m_(9>BO!cw{Z*$H>sv)d(r4q$P3?93TI}3v=Tgeau<|v%(U6hA2w1 z#+0o#t#O(dLxl+9(di?z7Ph8FQJatAuQjDf9S6SQAd=f_iJVN`zHA2M zU*u5@XDb_Ld4K~|gSF*eXgnqN+$B3a;?!Dh)(t04S%mlfR3lCW-#kSziz8HZE&fcL zulk?*hAi+1@wnV)VvpQ?I)=A0#cIY;C^=fKd+KvZRF|b7*P`W=kYeI=vSDE(C{3?0 zhdISv#8D^skff7YmEj}e1axjICd78}MwGE%4G@+O%g|!BujYJsm^!rva{t(kKB;2G zUwyU_S=$jWy8^2rr4j)j67V&_-Z7a_qyKZnKv`8p1e3z4`+(T_6S{rKZ3fzX_mRRj zXPrFq;Ex%#>9@efg;A0ZuUiin&0s^X@%veC!fM2Ck627u_(;jJ1d=fLD@i-kxkILe zR(9St#ao_VGik=;GO+sBeX%Vk&99%{ zOqr0xhuHYMp4%*bN$L#WOz70+u(dkQTp%7|wXXapY4YLh+;=++KdbS;)}-nS6b?<= zQZZJu_0-qmq-jLhp}-ffJ(6zpI9cDWhQF)bkEh!*_ms79ctZxueSFU$q(MXyh7_%z z5EW<=!p`E3K*)KM2K^mKf*5w#a^C#OrukFvNE#2D!D3kR!lg$wuj-AFy88#N?Tdqh zmUWq^)euX<;MMld?sUlyB_G3AzD)Dhbh)tg7QUNr-uFFylyc{vZY5n*7^R&rPaV?J z!gybTHv$-&^=U8BXPBy|3cS-IIMgNfChxDa*Sh(Gm-cYBYt+J*`mR0q_l>7ehs}6Wo~N-9ZXWzO#qbVdV&B3fYx~hKZm$`} zm3Ad?Tv;?B6Xm_Y7E(5)r5!OyDuT5siW$mLd34$zmcAL<&lx$5?Brl|Eb3X^={qd) zpc^+(LQK*ZC&-?$U9i)*L9bhJLsNv1Gjt@S)s+X3Wex>hD}l}N=gCT!H00^Xz-s{x zP4<-!aBuC9w0vQjw!JhB0$giJ5Fq5el!;BqKX-n1Rk7lJw-RtBwee8w6w=>V1 zKMb)Ke1aZ9Wvmn!MMS{~L{|$7h9XU6I*>KHNe_w?`=sXHhzRc&!HlLM3Jg6rDGW`j zlqM8UQEM`S(M9iB-zmxGgpMr(Xrd%$IwcZwOxRG%u}6+~VHFK^4uV{I;pVmF@ugdV zc8NFF?WCSTu1w#!Q&2SkP_m=FXgYckQmBK8OJ#_WKa;+pO?FJxPsn|EbusN9ke+^{ zW#RQgraMnpOYaMpDnX1LDW+ge(ouwzdtQ{&l0H=*+e^x%b10YYd|D6B!6SW#Z|#K( z0j!gVab0O(1eht)!nj)6wD4~3H%qFwQ8>R_ouWxa~?@!^{Yd*LG!<%U$qq^QB@^I(bf+ z;rx`C&X5g2$q8N^icX>vwxx`*llpXkd9caX&>-?0{_PB;q3ZHiTLh1J*Gx$MmCrJh zQ7odl7pmjf3j-{`PFw{_LY`HS;pZg{15UNRl^P5Ym4u-D{G5U=rwwash8N8~#qT_1!lASt){^ zFyZPhjQ$oz@zhb*JLP^Guf=cn8B{$}7Uap7a;6^}-GcJqmHK}LQ46m0Ha<1`qT=-s z2I5Ssbuwcg#xwB)dv*iG!MwP~FhV88dqgE)`H69|YA!$4%edKa{)r&R46wy36E-$lk8 z=aoCn&bor!ahhI7x{bWD^IHzb;K=J9I!T;QPAv=_rol0wI8G=4{zv~ntz=NLsNO}c zLBnV^q6R;z&6R~>5s;+@#n>z#|6vqR2LVa1Ryw3nML1Ajn@!gUJs#%Z#w>QfQlDsj z$7O@lJ*CFygY&gC_7&EMa8#dkyb_4+ifa}qOa!zeBa261zr)8n~2YU ztLrg+=CLkVGk@4793DJtVHr=rS>0Kg52X!!BaM&YW5hse{<@oRU+BcMz-CQE38Sx( zoPuEK(RHt@IM)e(QK#YoK%8x#0|7{oX=C=LbFT_#z-y4C=)14C8^X= z0~Jj^bzo`Q=`E>wilyddhIe}1MjIm$o$$LQvluwpapA4Cf>vVw5r}!V-?%;8SyGb9 zoF1Q}dq*k;2Krs5qI==y(AFK;Dt4$=aCc*8)M;NZ0lWdRYHyCPXrbP-xSMh*rD$_4 zWDVq}o)lMObfG%PPOw<3xmaV(@igwz(&}m+(>=Twe-{Y>D3=jxBc2{Oy zhj3f*j>2>}073N#9_W=Sw}5C}^^>%KippTyu$~KB9%lAWg;=7g2t0wOQ-fR>_ZzwT zM{3DQhUTjHsjI;>)XPk{@9M_bs=_HtLDHZ2VEx#-o%{gL%eXYOLg|s4G?=# zIEGkEXDIba8-LPCK|o8!u{@o}IbqP(sM)wBbBLJhsTTbWe7GkN*_`3YCh(DuzmJvIBf>hK*#l2vaqZ` z3&a0jj0^Gx%rpS5FfMv(cj;l`Hwx6}>IIoy0r&vo3R76vO~ha`{5VAogOd#kZ}qUI z&>+4?gCsZzXLr>f0Sr2q6{>;VTRO22e?l6#?w>UD*UAA0@wp-?IZnlctfqFhtzD6kE$B`6c(cMd3F7uRFK5 zwN~+tuww#_cQ@65C|>1KAo!}q8k;w!RvB_O$yk0IxN?^s)kBq&Sy^R0g# zMwDjBzO1Z#*jBIU1E1X{4cmKXD^FoUthqIT*lG_tP@O3A@+_nEly#&Dg$3cVh-2BU z7C(<%MoN06Hnj_6Vl#6%os#`f3xS-RGGc|FCcFfD8D}a(Xw% z-k})a3iwM2Q5My+(T04_PT0P_h+D_YEqAyykrwD6rehqtNAQ?1W}^U@Lr+9ik%Nhx zuRvL|A5X+PafB>p~#4gi=Mb!}&_rC7sNsf=Zt~UEj16&& zoJbg3{eD&?EhZVL&w!BZ+S#Bg%g~ zXIoQ`fu3ZT41S+WONpPp4=v~fIAOz&dL|Gp$y}=L2F2Nt3m_2W`=$Ub1hJFIyYpw8 zsy%)jr?}B+GJIusF%G-pAI9PdTq~xWub|*DK}6@1+SjZR^?&)KYPRa#f98W%reX3~Kv`Q-&cu+yQRUWzE#nF(-F8?T6 z|8+JC9R=hRQ;YtmMivp`{R$_Yb<&1etAX>Fy14);(IdG*G*CzEMW_F};$g>dVC=m2 z*KrnLcS?=gTb=vW{l1#IPgQ-$h6D@!Wurq$&mu|TmQ!#J6C0?Mj$pzjVi_;_ zfaqQnp^C=b4xaIz@Np>p0*HA z9Rc)Fq@!cJk4B5L9rI#Cl_aH^ic3fPorZFH_?%GTEUf>}ij)4AdQO5o*b3n*7kV(j zUy-c^U$0|uwrd8OoPinQ1a~D&)3KvjR+$9F&m|3w0=7=Wi7l&V0NA_b*r@q% z%RBb4?P&83YYj}~Wh(u@km8?F|K;;(b7DEGklj119YCv-C?( zH~w~jp4C#~p?|-Rb@a1?waCCFv6wU*GU=>ZU~Z#+C~|E+Ef&J?i_`1z-JJj z9a&zt5B{5Syu?N!$qE!4A_j=V9q9FNA8k1cR6ClsH18*a~IGQ84^=>jh?md+Ri` zHF(FPjHrT14Sv1&p3t{ZG&&p6mHH z8^jayry^oV%>a*{M|8Gzo*~mXjLBio6#Up6KEYOwdZToH~OWf%+DJ|8r}Uq|3gBk_tf5r0)U%9 z=~b^gUYWnuEjDI&+9=lV>G%2?6-U0N^hgv0+6PLldD`^N{;6rRGse+Iw|`H+(9o(q z^*5qGpeE2dRcp@Irf>C2O_`oHiZ#3Xeg1}pQSYg}5(NP^fzqp9cD*xys#d`!+g&fDEaCbg2juLxZ${C`h-!0D=M%!qDBN0-~fKjdUqFbcu9#4<+3( zG-vZX-`~65-+Rux*7@U{wa)nih8gx}_rCAzzV2(!M^)t)givZI1Og$HmwToTfnXGY zpO3e3z$b(7F*5Li;h_FP8dB6ty9R+ULgb%GX}TJ2CE-Vtx0a)Y9yRYq#<8-dx66_} z*ED2f{Pc;jiR2D>IO~1lMRGjkhYlrQ$lh~ij29P6&`*pBsrNsT@O=n=hCv9GqSSmS z_Kb{-fs&DEiEVDOwJ=IVd?tJ2FxNp@3*9hgFWjA*S9G=`a`e5<%EhU?C4`va5nL4l zeTu;f!KPJ9rEXmQklC*U-lRgvNT3k-x7+{z7Zr^OiPQKiZJq`p^Tkw!pqgoKUciId zWDsN`FNFyH%_F{;5Q@M5mViFVgfuQkSM}@uTN?z)jr>)yBuz5LkA1P_Oh&tErI2 zAmN)A@UZe@2twEt=6LVl4nbhu|CS&_yu|SBdhC!^`k%fcjEVl~a;mQe=%jKyep1lC zz466&xakzAH&GpevhReia{SZZo1tL+KR1-SBDkXkx>5aK-;9Gwe`3;!I3l!f`*!IL zEsxqIm%X2==qbE29E_>jD32{@Gc5b(L+7DH^G=Mz`}5oCW6}R2d!86$+*)Yji%3@ z^Q*rD)kw2{K9|Dq07W)`VY^2LfokrVq_L8HhzEal6C^~>L*Vki`2XNx=Ygh-wV&jI z#r!eRZ^8Qd8CZ%BVefwQgEa zg%Q|OiUcg=U_jhcR>gIXMABiq=jfemmK4uO?~@jWEY4ji?)m+pETxP>{mLwyOz8)t zyTcwl1kC(ZOpi5B{}uSK?SEmuE+hRXCz39=>QspDr?7%YhF_lfP4#l_aju=c3gx&=ck<%eP=7 zp;7&L1~r0vKDF=i=pV>z_xW7${X*lY6mTEhMX`nT3eN;Z>@Y}py1u62Gw)Tb`)+o2 zxHg=r5Lf0hVm(!%biv$dR1g^%sVO?^%;1W`aNldC$gQod?X3F~!N7Ho%+2n5?N7x7 z{*UWp#VK~4F)=axZu@WNoa@Lv(A!Fjf5H_uvW*rYI?5Vs`JGbqDp@}n=X@ns0=`@} zBTBGVl3Wa-x27s>DW0N8GN3%<(qcOgNlee*PnO-GC&PMsyg5<-M)YV{qxIr?H6vnB z`pbprn%&k^CD&2>CdJ$va1!@$;ruczvuMd_9vnETdk3$nD*;U+ozzErsi`zPi#M{mENR<(6&aa zxq9)sv-SLR<S&{~cVAC*{kw`I3}uHU_>ganhMnu2c*A@2@1Z*9Xb&qkSH$h8 zBvll*Kklu(`4c8TwF0$l!xs+j?oX3h`}%@j9-pDpTRuWzTgUFhcA`UXF6KA5bdc2g z*;XCXvscI})}31jazlqgQC`}=J>OXb{oUr#&5DA&uDh?Ph3$Wwuz0BEYZulXs{!ttrJ8Wk2UDqdTGD>&VTB1vK@+77ixPBH26tvJ`AvpwQMR#gt1gu8Mj9!wPumzrQ zrry=OOOzz)a?i*a=XS{6MbY4(Iz}v~e#MU@glDJllw>U1ao`h<53H{P;}`0 z6XWJv2~X_Ok%U-LQu)DTr|4ZIN77rInoM^MZJlc!Ks~#Cc<V*6b}- zd9ok8UV=dp0tE+|t=^nhl$Z1^pO=}cI)985FcHmbY3{59R5(~z(u54|;;<^C*4I_L z&G`9{M{%mgU!Hk;*v{7dv~G+Ee@w#G{k-rZZn@PHy**~+U0W`NubcHGe4VaYm^b~e*+h!v{tu>jF>$Z0X zLrHzYyT}fHh|U#Dkz_k{^(G?mbE1w^;a&xD##Mv1O0Ei|V|tT@nTMbwKwVHaN8aWkd41 z!D-J|xMuvYaDRK%rd3-vwx&rFVQo*8vXs00Rq9p6jauW={oMBZ{0_nRPl9I#X%Lt} zCR9Ti+y4+*?Xsh+5C{M&aAVTf_JE>TIk5irAIzqmR3g^bjMY5maQ>%khAV5#Zgq5`O2SaAbXA z{?F{k893|S%s#Ki*EG`Dgq;=P4sj8~#CsgCNb-=M$FLDWov(gk+DELXhr;gCha3)T zLV}#0EBUNMyG}gvu&pTzw*e|g6ub}0bsH7YRy6#Ds-A*0)Qy}i=(yo=tw^I|o zUEkBUkC3q(y3*f;Z0{*KXvaahaMEnnYsT0YRUd>r99gM7q4)HeB^whK2#V->XNoy!kaT^zBOLNC%QeG_i}UjS+79~!|& z*^k(-3f&+*#xzhlo`6^|;SHojXaP&4)s;4P1O5C00hD1L-lY`0DcGn55Q7e9h_}oQ zTD+SF#-ZBs51f07p#xw9yqxyzpE~4nK;NVZBaHtk2-gC5>i^-!5Ky?0*Ae-ikFidj z`_YH%1H&BROKzXt%cirhx_+L8DaO_22UQxV$)CnpTAG@HpNq@NqI<;-y4jXh8P1Ha zQ0Jr-zOOIa`_8DcJOoojv5Ty6f=h0OjKNBx0??l8BzcDa4M6^_4c4L}tF`eTxV;Ck z;iqf^9=018{?r-_+QdqafZ+clkM)TC1a9r2Y|Okak-hJONN08{=mD2k(e_>9*oUe2Q@E>3xQYA;9cf} zg$~liz>drZ3l5(W6}R{8vN%XPP-SER6P36mvupx_5OM{#X$mNr5r%L6iQrS>`>YUz zED51j>QhCq%}q?&e-L7!UM95i7_#o#f^F1|a|1jR`DsK5vBN-Fs;_s#$@DNZP;T`BpWzV_xUj7fp%m4s2%nKJOtrQB57u!G0hK6J}hf>(#Of>poX>jjjeI{b0 zv;-(w;V~sSe7fQ5gBOEE1~qw}!nIGAz1_}t(BTV$D}5M2&{_IyXAHX+ z%POiV06XVo!K3H9Py`4UVsR+9NsUSpt>tiYN;t%d631})A=m{s5f>%$!Z+1lL3-n} z=r&23_^Bbtl zL^QQggfKfb#q?C(^MFs}XeE}!H+Pi0SZC0T|0^$OytEK@&xKF}98(_TQosLk^2MVN z3Qg}>@=77|-b76&Jl<0eBJ$Bf{kUKvIyUJrDf+m>zie!5&3|*{?y5DlN=lwCMQKGU z4hQ=~HtMJv$=Mo?u!kjW-kM)FFI?z8LSQrFxjne!?Vsb(aUmQsWxCYtjzPD<*ciiz zD7T(a_l&=Is-~xx5IfL@k70h_toos1Z8BX5^pcn2@`^XMA*%84I6!hmHh`4lMDj|( z9Yg~B@A^~2CPF4Wi7|?-huI7#7)k6R{I8y9$PAUN03&bxZoLUjmdPSy*N0+OLU zW#ASt#{jVZ*pQmoI#%oEfI){7Iahy!HKMt{i^Bx;Ro(TKz6+8!>NR-R?H-@RbK_74 z$X;5xo$fF5zit0Ayo3Bqs}jBQppjg*ym@TzJVU{KX;x8}3j53Kw=-H3RZ+O$chxkZ z;hy;FMAwH8$ZwPXwfzj5#h~zjtvl%4Rg`+XczTR{GR)EvYtNtr6TCb}X9Hkeh+ z-*Ua)s6li0Y|AOo%L`J(9kh7@Ux|3Uotvm4mHaA8_-^bPo46|;XL7#%PD}t&lEz60QT@0)aG!9Q5P?qS342L=(LU3;Zr>m3|&y{w7l7@z?K zB9$xVwH4#jAgyLG3a1E#QQP61uP0VRJB~!<47AE>Xyi%pI5=WiTW-0k(OYxw8-<5! z*(sm6jVsm$;_KBRI`j7uJ?G?SijT13&S*2{7@kz4_NORtko)gvd@6|%%(vh{-_^Hg z@K7aFW+Jna5lfGA{7S@SJ24iU{k!JIs=tE!g(q!-dow}hM{$_an`Ko36eiOCo5WrY6w%u9 znRhFR^wD_MpvJ*e3nBF{7!p~Z(B4Ep6XA35#(h4B;)~WR-?du^3^?{TGv_%>&@YL2 z_mv(@O;XRLDU6eef}eam%Ndj+#XEL7g(ru!pz;O2?+-rTLVaST&6D!G4?GD5TsFb| zc={fyr0w1+9$CK{1e9|8D__tBf&UWZ!l94>{==6&cu1s%2?IW{7Br}uI(}osb$+e9 z9@rs9kk8y&g^;xa0t_G3Y1#by7k4B1i+mGBJMEjA7Q`CL|Cmw3^@L=ZfTy!pf4y~F zH=H&mAY9%Q!%*+w>-wl~B!6-q7|NPSw?olt%tuY+brd;xz~NVbW}ZQGDoP(cT2}I7 ze`NofQk78++PL^Kx@wW{l`p$Q#c#QPi%qZv?`s3crfZDjhzTW=1%8pWWHGA>0IKCY z#&sDC7hwCQ%MH>!h!D(^7nGjO;Qd_*3^<)c<8t0|zb)&R8i1mcr_d&)%C z$W(K!P4z;8+~kk0E3t2vbVjOzDq{fPD;r}@xNd`Ak0BVez|vwOX}_2Jt-(jckCAao z`+0&PV!%%mjg-p>5yHlg9PdJZfKEb+e*4gYUn&^4QQvMVSssrTm^1M0s?BA)=Mz)GL$AZ+U;isy&aOS7K&n)|%A6Aw>)tyf4*L z?C5N42Y<+Gf;Wv4iYXFF)sAb@VsW)&2?AE{0HtEFm7v3>%ZUlLR*6|{-#ZMLOSUhA z_3`$M!ha~3tfog$p z?mWeD``Ppcqe@6!qE^0krWg+_AHT@3&V691BMPun(>mW~>pbv3{GwQ0AryQ+8NFSz zBY`1yd;x(S9N>eA?(So2)4PS-6nsU3;0?ky9p@(XMH0y5*v{2Eo^MrVKU`hCDezJs#*Bbdl(p{#YKQ@_xV7ap@GvSUDCFvvnpyNmF8epf?joV0A>iES z|K#g!uEBucqh{`c{i-Z`(?;}_V;&cVgKu;pd|eBGm1ZoXjEcL;Vp{|See9X2txj_% zqwoWTMOjuNuo8E&lxp)JaOX)4p=VDV{wjX8*~Wzh-wwGrQ6HMPRG$uz{wUR;Qzy1& zSN?uVTMXU0E2C|E^_RR68?kKbEIh~y`2I9r$DtZY=J~y)D3!C|3*=+2FN*#OUJzZd#-Fl4EU)>}ZZVoH%|9gjZ2(>=` zD}!|LIZK*vKD72|RKLvm+;F{rBOx(lcQ*;^xBTNG!x19D)Q*qzX3PzcbK@2DNw^BR zzfdIwVaR*z&*G;9zM&r=u&u|W#r|21bDOqfDNvKGnzY zRnTW6E0YHS>LaUEF4feZz#IpS(75pZ0A^86jvot~_J+Iff49o}k#FVsg)_#FoA}lb z6vN|prwt}Rv^IUB#3K8U1!*K#5*lVJ0T%Wvq3@K8-+f@T$kjZjMXJF^j$5E_Adqfc zuEr*v^uZY;uT)Z8UFET6{bG%MPm&*l#r&ZM6e)bxBonMRvxjw#4!zoIf;p{^mRA?A z(vl(6{Deyx?0!w9_dOAA(H&~7S@cY0ZIE&S$AI7D_Xk(vdl>{vb^9oIi`c&@5 z^Mv(F6zH?^ewuwfFthua9Fswq+kjE1vTb?w8Q|bdT^`o|k%RbM2C`BXvIF#FA0oh0 zp4L?PUx8cYWx>J14g(ca#gK-_1CoI^^Wmf)=H3mCPEmTpbKjT!p4o54)Tf3wP26Xv z?g2CbyAHM*1N8R|Sy5l%?VnId}Jf+pTj85c?FT2c11Y6&LPR8zYnG7E@Uk%Ml&{Ad|3 zmqYz>OD%FlllS&G@tkCVWE#E`5q2V`4$tv0f!IsH5VX*H^;zF@Vn$!IDEbs(t0 zF(`A|(?QVZNHaTywy7Ey+q1Kc;#|FQ%k&Ts9j6E2603((3*@*TtuMHM)LxcylE{Fq z_W_N<3qKnv3|7_KVEUzV!aM@m8o3t&34%5HTUEb{II%hH%!-$Z|3jbDg9i7?3pE~0 zA?+SquD*~=$HFDLbzB3oGKZU`ed-{WkVH*$lkM}>1<0k;J-wWk47u=$eHA2vh^#@T zM>(}Mlwt*es|iwaj6|S1yPqbCf4Du4t=*TtzW6;Gi(aTJN|Om0{K6z-!mCGs1(=x} zP)sdGMtl+Kc`_ezlT5(z-^k5QPLEO+={WHkU<7@$0sE@Tw0InS3`d}f^C%Q+BYy!6ZXEb__F`_y#qfQ6Ke6c9*v3js&)?qEvTRXNnC&fO`` z0nqAMRD5RR6S{{`&~1OPG0EZf)7*-FAXhUu<1)wD98_*Ol5TXdKrx)JQ~cBNg!YDb z0+@&ScR!3UswL2%#+kfLV$V*6U8m?F5yiqkyv$Fqd!<*JfTu2?KP}KLUF{K?BQqx@ zq7m#~uXkSUPg}k0`jhN^DeHB)BSU+{es-(31Ph`Q>))FB#OJj4$p(Q{h{NkSj5EYk zh@*OFRF3n^1B%HDs;%DCtAI0|$CZd#X?K^)njD7o zr|`pk{Y6Sk?UmoE9#eXNA{^m$+olNENMm+p5#!KDhPfZ_#G5nQTU}MuOO{~5MbSRb z7`Dz?E1OcCO~{`7+WrX9F)5(S@T-QvD3&U);X*UK-)zoD-oxaACGWR4 zj=7J0yTZyL|0(dgt2SK3(w9BY{__|XvYWC$78`QR|EBLpnF;t$o=_vXRF!P#y^nf; zXTUG$c|0iUvR$D5JliG^W@>(+Rh4liq)?MJE?nRU zy-ajA?8epk)i29kt3s}S4>CR!CLT5TiSs}(Zx*8S!Tx`OYP_-5lVy*U0N~Oqd&kt7 zi&~iLizltUBjWJ4+h2wN-(a{{(Ovw1ts@Qx`L{1cCF4azR%GN|wS zxQDOj_6h1(XIV%lC7iirhl&i0v=pbAn@K3yUY+)^p~W+u@T=c9zt>#(CgSI{AShI? z^>sat$|{XK*Y{>VWFbYjQ6|KBAiq$LiUh=c;_78(Wq1xwm>x0eQy#oltVpX%`y+9SQxBb9)4{NaU|Ht?(V)+ZAj6@y8hv) zux5@s+&OCZ29P+a z<@dUfP92U+98Q`OBDUMl;3ucTeQHymGl%oawDb%l5pOXmuzuPN{YfW0jy{8<#W7&@ zB91$(ljgSD9cwD8m9rF@RVkH=khu6$JjAlj3oCuJf2cS!&K)Cs3fK=od|_}=vAsp? zU5(~x<*P3v3%Z{!^&#nHA~19c5KN7xW}Oq5MT+L+I2bG%ufB|YP2Iys%O5@AIBEGR z)Sq(i@?)>Wwe=)=L}Iekir8s$R?TAJX7v@Kgx%Xo2_02Y0wjgcH7-W> zk&gk_tw_{D9d9s|hq33Wn~Qg>>P}3@8~!(~OXd5^v0S+>LM}$VP=tlA!Xy0?(`%ku zL-#f2flrFP<0Yn{kZ+!!6@n>5oi|xi&1GJSfr;<7>r^g zw2en2tF)SE*bX=9TmU=K?sx(`5?-!}2U_B^ zk_a9#`h*jb_j6^OF;AJMj=JErTXEO&6SlMUX zM$9BJv_upK#03Joz5|q{P^!_QoY~p5sAe*kSwd zk2^`$&-q3bmDz#b;(ZbxJP$owpE3TJrm|-$CGJ zkBT@^`yLE&Aj>G@KtL>sI1M^|C2Zp{G_|9JceyojL?vM3&f!#@QwpIlCxhwlM!Ft{ zauu)q?hA6N{Ta3Uqca6Yfk*GOJ|eHsh6@gwRyp}UA?q&)aKL8IfZ?v1)FAAUj|k<% zye?ZMxAYoavoMU*R34r8IiRP;Qr&)UH!~Jjm`>+2Cp80WVAL|Ii#V*9Oqzm`2iF=8 zGY-@SvR)G);^Xrnac*=2CVu4YPHF52m%=&&+QwzC`j_sOYU=%NT$_8j4r?Bgn}Dx( z!3?2R_q1qIc(~Kju;RsEKO7~ns~C5>7J;_AiNWsyWlL9Bdy#+F&bLRiMhh8fOTUyE ztgjfiSXm@npG3D?9O%zYC3M1f*$ zsHMLr!iQ7#fAT6?y?eky|6lp2|9cJb)QPP`+S597}UhYBj5>d+G;=4 zBjtM_%WkLMcw-+l{<}*=@!q&Zf3zKQ>mUyeOQY$ZRA3zG23{JI12W`)L>kZK!0dj0 zm-*kqkN?ew`i;wI3E0kUAzRnE?$m)#N6<2ngR(HMTgU<-CXrPD_f5?V$1!1a9?I7f znt940Jp{Ccukst2@}58N5ywH5j7qP*dW-FNuev>)B`4hqMJ=#<*V1T`SL)u&QPnYR z>$H;7;y{1=oyC`cCtl==(xy5~-3$5XazzcrM_Fn{5TsP^ay$ld|- z)brj38&xtPvZ~(;4vetEJ7(mtTFfw8TBnb;`gfxDIUA-k?^+|5Z<8nR)RY5wGn#-y z$Cn1?MY{C?ecW8@+23<_kS<;YqNdSmrJhNFHPl$laIOLSy4Sq$VO^-G<%kSZC4l(T zB^2)8GXX4&^~WY7#*B*}?Ws^N)Du{^?zI^5H=Fj#pZGJ7Kn{AL{S70jJq`pyRHjsL zpdR8MdKFuEqXlx_o{s;eU7nFQFz|vw>4t89!@u&O-;uq9;4vI--?$e`xD%n%-W16hD(EoE~ zAc!g{^N^5S5w@KA6|EOjaremC2l;)puKu&T(UAVd#Y$^e?lq^*9m(^DT(&^d6=B|2 zB(8O)WgW2rc29Pc3SfMXH|0In*w5b0y`VbkU_r7GUxjXa?^5ZVta_y z+_7gok6Qh_#?K?L_|5jMt1q*5)XoPryt+Dg$#-^0QiQ~dFk+M_UvA8u`+5spTFi-D zJD?*lrxu~pe{Ib<(?vs$6v8DLx}so6M7xc|@Qd$mI*j@eUA9uhdKWE?%*Lo7{nMdk z$Kz9Tji{)EAK`Pvj;-6*i`op zBK;f5mqrA{ikN2j&A7$U;|>;vF}I-jMg$R|Ovi)%ae3ngN1LCj+DsoHFP{FsB-oF< z5c|rAaBFHwR*fUd9xC-!cW2>ff^>B@t&$44gb0D^kBb#7^-m!o;qJgk~VZL{QT zjS<&-j@;j}Gu@B&Z}uO&cj2Z=`MIkdK^H=#bvth>FHM@944%>wzKFtsR^iISKYPMSB|E*gePAM+gguIYu<~LWg6+lk2sLyA_LHcJ4a9w0P zl8~;Exa3`&){$}bPJY$z{mnYM$!Vj{{5DsZOpy6Sn>?nc6g>q?05>bWkF(pVi@H0! z!Dn9Jj`otmBhMVKmz zLy-)suEud6Ni6TBTPqyRj+eT-&GXLN@w8J?wJ*()_4OEHl!RJppB!a9?^_wmUNJs7>s^#QDbG0YHLb0Vh5cw7{rR-c)T^W7OkN?K=s*~iq2ya z0yAvvGULj^lcZ-IY_0=BLgtF?liw6X6Twi1#B&oiC8OprQchQu@Rn!-saKHngk26|;qqXu_P8+DWQgb&%OgH!s_t4ALDW9XaVN~&V)zSt;ESwdVGb+x zsZ|MVj3W53jKK!EDuqZANg6?J-CpNLGyA@OC~kMWPj=EoUT3N1MLaqlCP3A4P8(z7 z_Lwywj8w>SH1}~T>v=l#s_8Fr&U1Ofh5EccJ3Ff2jnIZ`EQ84g^O%*s6NlU&+T!Vu z#q-@z>SaFTeeY`2WSIqNOkCX1LThNQcA?&3Gd>q((u3ApH{iT3ww}U;;8vW`N%)kR z9QW#5IT#Hhw@)c|ZJnUNIXi_z`a9vqNZR&FthO7-nmbI});~l(3H{lU*WFZCjXjI(q~{ISmpkFTG+-$g>~i}YKv>Y~da%`F zF&j$OUb7Ozi5$mTxa3@W-=WG_X_W1ZceLqT;hL%|q4G$oJLJxROs^$_k?yG=okq5H zUW(=1lWtZ%lG6#UdX}AdgSleSo{O2m=r|B!OB`VoO$#`dU?&CjigHs4F5ZF?Ez0i% zlClp6hAR5!aH}$$EH8aT3t8VI#@xBKY#3)*G_N^XAQi7g?>4JSa}Blp=8c{xJH;!r zYHO>{_HM8DIPYjco3ZWh)S@OEe0-qVKqZ%T!|(y&%g{r4UgYpfvZe~>_hAGV(tmKZ z&I4|#j?1FOZ!yid%#$>EaneXCFs>gtdyi(&V=#oj$U5bkv+V(TV|XvfROxR?Dx>DN zQ1=|GI?QU6dL1Kuw4$(1%ISXW9*r1QP+fvRah(>nJpohwFd{uca7SD|pevZ!Jje9y z^Rxu1oUCyVjkC*C=C+6PEa>frNJF<}a`&s#6$_wq)U0uSZ|D8GKjt9i9)%J?uhflpPy}Jw4M{x# zz@uq)oYC63k6<_@iGbxW$+=F25hQNwUT2-pKS^KgP{90kQ-;U{0VXy?u5rB7oETi0 z_)vd&JQWr$WHlNFWbC$CSl45n$;FZxeOFa1Z@%N3OUOvlOj6LtsV#3=5}-=D=vTvy z0q4x;pVvWU*?Hb@b;PysjIe>7!2@2ALS*~=ec9bCUl?*%qKKP$uPWvyr0u>NO>_<5 z|0nZ`Cl)O=*Vu^o0s2Bst)~=lR|X2Rh^T)GWgS3Dgcg5-jNSdRcl-Wrrs_&AQy`aD zPUl_(zS$$ue4 zc25s9#8}U^#q@cnZY{8@3SN^_pKkC_i2}d9w-pFPjvx`im;i{y#>_V=68m^Myf>u2 z@yhRTb@McZMAkbvn|G1-35c~!@*uNqs}p1&wF$-N1kTEJcbQ)>p+p?md%HV067^1h+e zZ;a_VM^467@bkHrnIwUNH@Z7TkXedX8@h*B8;_c63tA%JbFRGMQM9{Kjh>VB>#l}P zv;GZ-zP;hYif|cxzTN<&R70f* ztEZ-Z(&@BpWU*Zvuz)>7e zhOYJ+U*=r#lxxTuxt=QRmAYUMV^NG5e~9ieR3!Bl;~sf5w%CQ=qWEQLf4bp6aN3;u zOD*~~W}M4rg!lo=x}Eo9uU{@cjEP$Pd5uxNwu4ANuEdO9iyIClXC)9Ub$vWgQUWZm zXiARb#~PKrup*4@7mC3L0<}C>pODzE;~U1soOx}nixYc$$z-?d7FFx+P0c4 z0^P(!UVkOo*9$Q&xj2GWpRR{u5mGq&NJfPE2<=^+@>{l(=OTVk!1F>_&h2K*?Xp>9DX*kiDHW z^`klcE}Yi?ZC}Y^{I!(r`xB2Caoz^1l@X@)?;}Zt)u`rkq$9bka6ULfhdmgGOMUhk z?&O3Y^SIHem)w&1tHCoRTQoadc9Cso=!$dy(DRb(r>1(yh^D~;`3EB9r+c}VB_Is? zHn|vSn7J;D}sti?bw^ zW;vb}aY+u<(LvLFR(k;}LkimJXoso6Y4hu7%(%cTA_7ynU;5dCm~dLQx5Pzd zP7dyGwzA3;<${HGJ`y5p@h=psq9KE8vrwt%{;_7vQT%vxi zEt4jA5&K%wc2fr3>na$tn=0MH75!+c9*_i?ZjVLI;k*yCHMGl-(+*LyOgjqV+~#;Q z_U~1|SUa?m7u8#0XsMT0DvGKvJO_T{G=#~F?#b{=9jn8GVoe3anIlpi&W z5fOS@H|T6br{E;For2G2n#O z@CguWRzZ=iz|XK1T_HV^0YEy$#z7vIOLzQ_%bk7DB~tkRlxf6#!2|B1sRb8zcD_sy zd{2FImlEV0zk+mLf~1c`IK6nn%aC&ZivC9(PM&k(CNG8RTpAJIZNxfU2<1|Db8~w+m8JCk)4992 z2;H6mXd7(k4607D7eG4rqvvIRyoZjhyeU6?JTU6?7g)vsVD*4oudk_m8I~ zBSB3RJ3PPINS!+#hW~S6FKp^Qz{;)wtXndN4y_=`kEx(9IslS(P8-@4;@tw!sW@+p+?ZjBX~8@ZTqP>G&3 z#PGk_@7KKJ%T90B%8E($69ax1mUwRm7dHESAyjfD#bsyKz|=Oht^g?1`l-wlrNGTi zc~iE|IR`HymcNk(-)OC+z>~ z!*&);B7i#8S(~gutWnF8CKVT3o6!i>X-2tz6?2F@nJBusg?ppSJx+{+KDg4A!1^=a zytmj!+Ol1To>ka#+lzAta-wu<7`!=v+B@0b4I>W?v|OEnHG2y@ejYwH-02#`Lt5{@OBl^j*Fe^FR1{W`^M zUfMz~mx`}`aIqrKYvVit6fG36Nx~mi>_<$%MWi}NQFko9`zUPP?dv{qqBVtmdE6-g z8B~cS#I+$bhc6;?7uzAQ&7I$H@Z|xnH{T1OqeFHm3Y1-O^^TCZ25l*P;S{0M-=r#< z!f%B)f0NeZbFK4s(=5BfCWbuy*oXZ3_Gj1SIlT?DDd1HQ2}^AzvH#{b*S0#$&GzkgE@Y2U(nA!!b_lTdrNK9OCv!05@xQ16V#vX zPA8?^4K|h-tU)5z+y!P(tyx>;Tb$FsPz{N@a-n2?lMXCWX3gm~Asu#*)`@O$z1R7t zmAYP$jRLklyNyTDvMZD>L2(en{}CP;v2eC^}5MF%%HDKGDI9kmv0UWSPHu# z9fCXqx;%asqPwMLT;7?A6vPcpk1VG8H)~YE$SML9l6*(3G_!3RE@PF&mVZAWsY%5e zDbUg&;~|6{$-yRYdrxY2>d${0#>|CE1=MpCxx%~RxUzw0>O;owVF&Jpi+zQKzF-2X zNOz6^;7d5tdhdVT%6CE8t>t{;{q>I(^bo)c&2@(p@Se7&hDg4!F*N;YeN#+f8#4vhRRa`iNXy{1bZ4^CmZ1<{rT*># z8gt3GmcjdkX~uOie8Dw4RUA9X{DXhs%2n9a(Yv{b>`QCvUmAY5c-1;xm@83v{vAL6o zxQ4E%x(5&TADQPc*-WYQLXgd7BodYCe&8U#dm|xttLpV$oZ&W2&b;)FN44v~yV=^R zldj~bCjb=4dK_=wS=;BWdXS4UdF6G`FxM|^(3f@q6Hd`f84*!(9?}icR^Ghch_*mX zJQ)RN8Used`AH&(?F$#e&9SR&2^QXJ%S$<*{PXV=%4aZ%sC8KTooqV8K zE_2?}+Z*%DDqXS{NB90g+Znhm#~hxnq=X)Bia2cyI#n96q>=-m`67D0^40;(m%aNP zq%vc8`uL2(E!tg6RYs>cafh|8l%}Sphj9%TvSD%(Q+K1OIpp8VI=B{XclQ|yd|49{34sYypzN02{A3SK z)qr1$L|FikFzHcV>%n;UIj?4W#$j?8QUzYW*t4H~+ZSt^gv8dKVc zTra}X>|OW)VoAf>%zV4>zUBwC-LgbzzrPbSkkc_sC89SvvFYa`$FttZe4~bXx$wW6 zPe<4_NCk)I6cFIXG{+*9o3(FFACSXb4UL_1(3#upmpTI3{hn8>^2()NmYC@fDzfd9 zJhLm&%}w;$AJHnddxj#q1;%&^#-Yz)9`LM&Ib$z@OJV7tEOMtGbcc3*5N5Wo-Jmujhpjos(K98wNsl ztamK|IF(aK(d)vcJz2OvntI~E6tU+tVH)?*>*X3JaIccP%b)g%1OpW4Wgiif#iH^w zvX!C3BH4lMzd)ogCuJ+|%U+5FMQCyD{-@j7 zdpw55#aMWqw@2`}&W?H=b5z9}<4MDxbjV4xwf;1$b^Yag3_zb(R8TGb_ql+5k$G8L z(I@$;*B)V!Jb)@26awP&;usg66?G7Svc3KGi{cLj!`B}Uj#eknz_vjkI_BYAzDR9bb!i+*PGT>J?ahs{j*+W!K#Zw7^!Z)&Ul))n`l;{B9h zc^h-Y(!wOrwUTnxbsrW9x;e&r);2vtoYvL(Mo>n9i|)^rhgZjcy;&a!OAXmGeN=cx z#OJGUecsM|hpk(=csv@wb|pN-sns&Y>|&f-J^0Fs$Hf6DRf|zq%*eM2@v}*POrH#| z_6Uwtj(2M`1=rea>wa*6L4JMYfYPaS7yF?Jo%b@Ysr7Q;O3S;4Nb0kB@O!XbV zZ{J0h#NT11!N2jpeASSmeR73dsV*EU+?bseq$`sRxK0QKbF$R>Es6qUB%<6!d0o`a z{_Yumt$v*!>PE)(4EmZHp7(Q~l2=*cD{4WNslhCGj%Gzk`YRqsEiUWaTaeoIo_S$2v`hAmU8fjjwMu3Ej$ zt1>c6sp*%@l$wU=^K&10*KVKX3+#|i`2O+7f1wR?Rv4DP3Yp|E`K0=)>l%C4f_9(X zvpin)(QclNPE^%`I``?RvzG3x01ov||My*9RTsDv{E|5vaEb8Ln!1PW7Ju92 zS-Wd)&DzdYlYu+e{GRrmK5E9ze7z(3|K*<#-D(*m<)(jL=KqZ0&;irG&-qn?XEX%% z@A=Jl-#^E4&6j1-Y?pKQ@6ug-)+kkU-Q6FKZ{FA@e+sy7*je&p`LyF{dtO;o=A?d( zUpR69xf&~*1ESKO)w2~6A9ziZy6B$Gy`$ufB+I%Ni`{*11BY^QHgo(+4q*gN%)H=D zz9n%0I2dhZr4w6V`=z@*0!HzJQyDg^RCQRLlmrd-wly6GF1J7a z8o0=ZC*w5Jf@J@1I|@{t8#k*5fHgBz&)S$C6txjF(ku-bY2I*Qrr`ozZiY9R$L4uvg4b8(L)K3{ zZq56$zJE^~ipyBw_2MaA|`e$ikGRB~+wlWZf?Nnr2ZYu($$Ju>efcP)~71>BCG5CWVvSutm0 zK2Q6bWA~0oD2QwTZZbbzCw=pZGjt;e@C1W`qGRd*?<_X`6_!3PZ0Vh6yzjHGK6P60 zeqSBK^lv5Zj=-IMpOzl@B6&FzIF7sU?EJg`a(2C(Hha~S*}q@JT=Ab#cRa*itz_Sg zD2A6Gwnef11+Ou8V1%5J1lsHmJQ-n-ov1Z<4hQ=$zxIBjn(-|@MFt@7boFyt=akR{ E03iOkng9R* literal 0 HcmV?d00001 diff --git "a/zh-cn/device-dev/driver/figures/Vibrator\351\251\261\345\212\250\350\277\220\350\241\214\345\233\276.png" "b/zh-cn/device-dev/driver/figures/Vibrator\351\251\261\345\212\250\350\277\220\350\241\214\345\233\276.png" new file mode 100755 index 0000000000000000000000000000000000000000..db30b9b7223918b0796f5abaa76c62999886fa42 GIT binary patch literal 20385 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 literal 0 HcmV?d00001 -- GitLab