当前位置: 首页 > news >正文

OpenHarmony外设驱动使用 (五),Fingerprint_auth

OpenHarmony外设驱动使用 (五)


Fingerprint_auth

概述

功能简介

指纹认证是端侧设备不可或缺的功能,为设备提供用户认证能力,可应用于设备解锁、支付、应用登录等身份认证场景。用户注册指纹后,指纹认证模块就可为设备提供指纹认证的功能。指纹认证功能整体框架如图1。

基于HDF(Hardware Driver Foundation)驱动框架开发的Fingerprint_auth驱动,能够屏蔽硬件器件差异,为上层用户认证框架和Fingerprint_auth服务提供稳定的指纹认证基础能力接口,包括指纹认证执行器列表查询、执行器信息查询、指定指纹模板ID查询模板信息、用户认证框架和执行器间的指纹模板信息对账、指纹的录入、删除、认证和识别等。

图1 指纹认证功能整体框架

image

基本概念

用户认证框架与各基础认证服务组成的身份认证系统支持用户认证凭据设置、删除、认证等基础功能。系统支持用户身份认证,需要提供数据采集、处理、存储及比对能力。

  • 执行器

    执行器是能够提供以上能力的处理模块,各基础认证服务提供执行器能力,被身份认证框架调度完成各项基础能力。

  • 执行器安全等级

    执行器提供能力时所在运行环境达到的安全级别。

  • 执行器角色

    • 全功能执行器:执行器可独立处理一次凭据注册和身份认证请求,即可提供用户认证数据采集、处理、储存及比对能力。

    • 采集器:执行器提供用户认证时的数据采集能力,需要和认证器配合完成用户认证。

    • 认证器:认证器提供用户认证时数据处理能力,读取存储的凭据模板与当前认证信息完成比对。

  • 执行器类型

    同一种身份认证类型的不同认证方式会产生认证算法差异,设备器件差异也会导致算法差异,执行器根据支持的算法类型差异或对接的器件差异,会定义不同的执行器类型。

  • 用户认证框架公钥 & 执行器公钥

    用户身份认证处理需要保证用户数据安全以及认证结果的准确性,用户认证框架与基础认证服务间的关键交互信息需要做数据完整性保护,各基础认证服务将提供的执行器能力对接到用户认证框架时,需要交换各自的公钥,其中:

    1)执行器通过用户认证框架公钥校验调度指令的准确性。

    2)执行器公钥可被用户认证框架用于校验认证结果的准确性,同时用于执行器交互认证时的校验交互信息的完整性。

  • 认证凭据

    在用户设置认证凭据时认证服务会产生并存储认证凭据,每个模板有一个ID,用于索引模板信息文件,在认证时读取模板信息并用于与当次认证过程中产生的认证数据做对比,完成身份认证。

  • 执行器对账

    用户认证框架统一管理用户身份和凭据ID的映射关系,执行器对接到用户认证框架时,会读取用户身份认证框架内保存的该执行器的模板ID列表,执行器需要与自己维护的模板ID列表进行比对,并删除冗余信息。

  • HAPs

    HAPs(Harmony Ability Packages),广义上指可以安装在OpenHarmony上的应用包,本章节中仅代表Fingerprint_auth驱动的上层应用。

  • IDL接口

    接口定义语言(Interface Definition Language)通过IDL编译器编译后,能够生成与编程语言相关的文件:客户端桩文件,服务器框架文件。本文主要是通过IDL接口生成的客户端和服务端来实现Fingerprint_auth服务和驱动的通信,详细使用方法可参考IDL简介。

  • IPC通信

    IPC(Inter Process Communication),进程间通信是指两个进程的数据之间产生交互,详细原理可参考IPC通信简介。

  • HDI

    HDI(Hardware Device Interface),硬件设备接口,位于基础系统服务层和设备驱动层之间,是提供给硬件系统服务开发者使用的、统一的硬件设备功能抽象接口,其目的是为系统服务屏蔽底层硬件设备差异,具体可参考HDI规范。

运作机制

Fingerprint_auth驱动的主要工作是为上层用户认证框架和Fingerprint_auth服务提供稳定的指纹认证基础能力,保证设备上指纹认证功能可以正常运行。如图2,Fingerprint_auth服务通过执行器列表接口获取执行器信息后,向用户认证框架注册执行器。Fingerprint_auth服务通过执行器功能接口实现和Fingerprint_auth驱动的认证、识别、查询等功能的信息交互。 开发者可基于HDF框架对不同芯片进行各自驱动的开发及HDI层接口的调用。

图2 Fingerprint_auth服务和Fingerprint_auth驱动交互

image

约束与限制

要求设备上具有可信执行环境TEE(Trusted Execution Environment),指纹特征信息高强度加密保存在可信执行环境中。

开发指导

场景介绍

Fingerprint_auth驱动的主要工作是为上层用户认证框架和Fingerprint_auth服务提供稳定的指纹认证基础能力,保证设备上指纹认证功能可以正常运行。为实现上述场景的功能,开发者首先需要基于HDF驱动框架,完成Fingerprint_auth驱动开发,其次实现获取执行器列表接口和认证、识别查询等功能接口。

接口说明

注:以下接口列举的为IDL接口描述生成的对应C++语言函数接口,接口声明见idl文件(/drivers/interface/fingerprint_auth)。 在本文中,指纹凭据的录入、认证、识别和删除相关的HDI接口如表1所示,表2中的回调函数分别用于指纹执行器返回操作结果给框架和返回操作过程中的提示信息给上层应用。

表1 接口功能介绍

接口名称功能介绍
GetExecutorList(std::vector<sptr<IAllInOneExecutor>>& allInOneExecutors)获取V2_0版本执行器列表。
GetExecutorInfo(ExecutorInfo &executorInfo)获取执行器信息,包括执行器类型、执行器角色、认证类型、安全等级、执行器公钥等信息,用于向用户认证框架注册执行器。
OnRegisterFinish(const std::vector<uint64_t>& templateIdList,
const std::vector<uint8_t>& frameworkPublicKey, const std::vector<uint8_t>& extraInfo)
执行器注册成功后,获取用户认证框架的公钥信息;获取用户认证框架的模板列表用于对账。
Enroll(uint64_t scheduleId, const std::vector<uint8_t>& extraInfo,
const sptr<IExecutorCallback>& callbackObj)
录入指纹模板。
Authenticate(uint64_t scheduleId, const std::vector<uint64_t>& templateIdList, bool endAfterFirstFail,
const std::vector<uint8_t>& extraInfo, const sptr<IExecutorCallback>& callbackObj)
认证指纹模板(V2_0版本)。
Identify(uint64_t scheduleId, const std::vector<uint8_t>& extraInfo,
const sptr<IExecutorCallback>& callbackObj)
识别指纹模板。
Delete(const std::vector<uint64_t>& templateIdList)删除指纹模板。
Cancel(uint64_t scheduleId)通过scheduleId取消指定录入、认证、识别操作。
SendCommand(int32_t commandId, const std::vector<uint8_t>& extraInfo,
const sptr<IExecutorCallback>& callbackObj)
指纹认证服务向Fingerprint_auth驱动传递参数的通用接口。
GetProperty(const std::vector<uint64_t>& templateIdList,
const std::vector<int32_t>& propertyTypes, Property& property)
获取执行器属性信息。
SetCachedTemplates(const std::vector<uint64_t> &templateIdList)设置需缓存模板列表。
RegisterSaCommandCallback(const sptr<ISaCommandCallback> &callbackObj)注册SA命令回调。

表2 回调函数介绍

接口名称功能介绍
IExecutorCallback::OnResult(int32_t result, const std::vector<uint8_t>& extraInfo)返回操作的最终结果。
IExecutorCallback::OnTip(int32_t tip, const std::vector<uint8_t>& extraInfo)返回操作的过程交互信息。
ISaCommandCallback::OnSaCommands(const std::vector<SaCommand>& commands)发送命令列表。

开发步骤

以Hi3516DV300平台为例,我们提供了Fingerprint_auth驱动DEMO实例,以下是目录结构及各部分功能简介。

// drivers/peripheral/fingerprint_auth
├── BUILD.gn     # 编译脚本
├── bundle.json  # 组件描述文件
└── hdi_service  # Fingerprint_auth驱动实现├── BUILD.gn    # 编译脚本├── include     # 头文件└── src         # 源文件├── executor_impl.cpp                       # 认证、录入等功能接口实现├── fingerprint_auth_interface_driver.cpp   # Fingerprint_auth驱动入口└── fingerprint_auth_interface_service.cpp  # 获取执行器列表接口实现

下面结合DEMO实例介绍驱动开发的具体步骤。

  1. Fingerprint_auth驱动是基于HDF驱动框架设计,所以开发者需要按照驱动Driver Entry程序,完成Fingerprint_auth驱动框架开发,主要由Bind、Init、Release、Dispatch函数接口实现。关键代码如下,详细代码请参见fingerprint_auth_interface_driver.cpp文件。

    // 通过自定义的HdfFingerprintAuthInterfaceHost对象包含ioService对象和真正的HDI Service实现IRemoteObject对象
    struct HdfFingerprintAuthInterfaceHost {struct IDeviceIoService ioService;OHOS::sptr<OHOS::IRemoteObject> stub;
    };// 服务接口调用响应接口
    static int32_t FingerprintAuthInterfaceDriverDispatch(struct HdfDeviceIoClient *client, int cmdId, struct HdfSBuf *data,struct HdfSBuf *reply)
    {auto *hdfFingerprintAuthInterfaceHost = CONTAINER_OF(client->device->service, struct HdfFingerprintAuthInterfaceHost, ioService);OHOS::MessageParcel *dataParcel = nullptr;OHOS::MessageParcel *replyParcel = nullptr;OHOS::MessageOption option;if (SbufToParcel(data, &dataParcel) != HDF_SUCCESS) {HDF_LOGE("%{public}s: invalid data sbuf object to dispatch", __func__);return HDF_ERR_INVALID_PARAM;}if (SbufToParcel(reply, &replyParcel) != HDF_SUCCESS) {HDF_LOGE("%{public}s: invalid reply sbuf object to dispatch", __func__);return HDF_ERR_INVALID_PARAM;}return hdfFingerprintAuthInterfaceHost->stub->SendRequest(cmdId, *dataParcel, *replyParcel, option);
    }// 初始化接口
    static int HdfFingerprintAuthInterfaceDriverInit(struct HdfDeviceObject *deviceObject)
    {HDF_LOGI("%{public}s: driver init start", __func__);return HDF_SUCCESS;
    }// Fingerprint_auth驱动对外提供的服务绑定到HDF框架
    static int HdfFingerprintAuthInterfaceDriverBind(struct HdfDeviceObject *deviceObject)
    {HDF_LOGI("%{public}s: driver bind start", __func__);auto *hdfFingerprintAuthInterfaceHost = new (std::nothrow) HdfFingerprintAuthInterfaceHost;if (hdfFingerprintAuthInterfaceHost == nullptr) {HDF_LOGE("%{public}s: failed to create create HdfFingerprintAuthInterfaceHost object", __func__);return HDF_FAILURE;}hdfFingerprintAuthInterfaceHost->ioService.Dispatch = FingerprintAuthInterfaceDriverDispatch;hdfFingerprintAuthInterfaceHost->ioService.Open = NULL;hdfFingerprintAuthInterfaceHost->ioService.Release = NULL;auto serviceImpl = OHOS::HDI::FingerprintAuth::V2_0::IFingerprintAuthInterface::Get(true);if (serviceImpl == nullptr) {HDF_LOGE("%{public}s: failed to get of implement service", __func__);delete hdfFingerprintAuthInterfaceHost;return HDF_FAILURE;}hdfFingerprintAuthInterfaceHost->stub = OHOS::HDI::ObjectCollector::GetInstance().GetOrNewObject(serviceImpl,OHOS::HDI::FingerprintAuth::V2_0::IFingerprintAuthInterface::GetDescriptor());if (hdfFingerprintAuthInterfaceHost->stub == nullptr) {HDF_LOGE("%{public}s: failed to get stub object", __func__);delete hdfFingerprintAuthInterfaceHost;return HDF_FAILURE;}deviceObject->service = &hdfFingerprintAuthInterfaceHost->ioService;return HDF_SUCCESS;
    }// 释放Fingerprint_auth驱动中的资源
    static void HdfFingerprintAuthInterfaceDriverRelease(struct HdfDeviceObject *deviceObject)
    {HDF_LOGI("%{public}s: driver release start", __func__);if (deviceObject->service == nullptr) {return;}auto *hdfFingerprintAuthInterfaceHost = CONTAINER_OF(deviceObject->service, struct HdfFingerprintAuthInterfaceHost, ioService);if (hdfFingerprintAuthInterfaceHost != nullptr) {delete hdfFingerprintAuthInterfaceHost;}
    }// 注册Fingerprint_auth驱动入口数据结构体对象
    struct HdfDriverEntry g_fingerprintAuthInterfaceDriverEntry = {.moduleVersion = 1,.moduleName = "drivers_peripheral_fingerprint_auth",.Bind = HdfFingerprintAuthInterfaceDriverBind,.Init = HdfFingerprintAuthInterfaceDriverInit,.Release = HdfFingerprintAuthInterfaceDriverRelease,
    };// 调用HDF_INIT将驱动入口注册到HDF框架中。在加载驱动时HDF框架会先调用Bind函数,再调用Init函数加载该驱动。当Init调用异常时,HDF框架会调用Release释放驱动资源并退出
    HDF_INIT(g_fingerprintAuthInterfaceDriverEntry);
    
  2. Fingerprint_auth驱动框架开发完成后,Fingerprint_auth驱动需要向Fingerprint_auth服务和统一身份认证注册执行器,所以需要实现获取执行器列表接口。关键代码如下,详细代码请参见fingerprint_auth_interface_service.cpp文件。

    // 执行器实现类
    class AllInOneExecutorImpl : public IAllInOneExecutor {
    public:AllInOneExecutorImpl(struct ExecutorInfo executorInfo);virtual ~AllInOneExecutorImpl() {}private:struct ExecutorInfo executorInfo_; // 执行器信息
    };static constexpr uint16_t SENSOR_ID = 123; // 执行器sensorID
    static constexpr uint32_t EXECUTOR_TYPE = 123; // 执行器类型
    static constexpr size_t PUBLIC_KEY_LEN = 32; // 执行器32字节公钥// 创建HDI服务对象
    extern "C" IFingerprintAuthInterface *FingerprintAuthInterfaceImplGetInstance(void)
    {auto fingerprintAuthInterfaceService = new (std::nothrow) FingerprintAuthInterfaceService();if (fingerprintAuthInterfaceService == nullptr) {IAM_LOGE("fingerprintAuthInterfaceService is nullptr");return nullptr;}return fingerprintAuthInterfaceService;
    }// 获取V2_0执行器列表实现
    int32_t FingerprintAuthInterfaceService::GetExecutorList(std::vector<sptr<IAllInOneExecutor>> &executorList)
    {IAM_LOGI("interface mock start");for (auto executor : executorList_) {executorList.push_back(executor);}IAM_LOGI("interface mock success");return HDF_SUCCESS;
    }
    
  3. 步骤1、2完成后基本实现了Fingerprint_auth驱动和Fingerprint_auth服务对接。接下来需实现执行器每个功能接口,来完成指纹认证基础能力。关键代码如下,详细代码请参见all_in_one_executor_impl.cpp文件。

    // 实现获取执行器信息接口
    int32_t AllInOneExecutorImpl::GetExecutorInfo(ExecutorInfo &executorInfo)
    {IAM_LOGI("interface mock start");executorInfo = executorInfo_;IAM_LOGI("get executor information success");return HDF_SUCCESS;
    }// 实现执行器注册成功后,获取用户认证框架的公钥信息、获取用户认证框架的模板列表接口。将公钥信息保持,模板列表用于和本地的模板做对账
    int32_t AllInOneExecutorImpl::OnRegisterFinish(const std::vector<uint64_t> &templateIdList,const std::vector<uint8_t> &frameworkPublicKey, const std::vector<uint8_t> &extraInfo)
    {IAM_LOGI("interface mock start");static_cast<void>(templateIdList);static_cast<void>(extraInfo);static_cast<void>(frameworkPublicKey);IAM_LOGI("register finish");return HDF_SUCCESS;
    }// 实现指纹录入接口
    int32_t AllInOneExecutorImpl::Enroll(uint64_t scheduleId, const std::vector<uint8_t> &extraInfo, const sptr<IExecutorCallback> &callbackObj)
    {IAM_LOGI("interface mock start");static_cast<void>(scheduleId);static_cast<void>(extraInfo);if (callbackObj == nullptr) {IAM_LOGE("callbackObj is nullptr");return HDF_ERR_INVALID_PARAM;}IAM_LOGI("enroll, result is %{public}d", ResultCode::OPERATION_NOT_SUPPORT);int32_t ret = callbackObj->OnResult(ResultCode::OPERATION_NOT_SUPPORT, {});if (ret != HDF_SUCCESS) {IAM_LOGE("callback result is %{public}d", ret);return HDF_FAILURE;}return HDF_SUCCESS;
    }// 实现指纹认证接口
    int32_t AllInOneExecutorService::Authenticate(uint64_t scheduleId, const std::vector<uint64_t> &templateIdList,const std::vector<uint8_t> &extraInfo, const sptr<IExecutorCallback> &callbackObj)
    {IAM_LOGI("interface mock start");static_cast<void>(scheduleId);static_cast<void>(templateIdList);static_cast<void>(extraInfo);if (callbackObj == nullptr) {IAM_LOGE("callbackObj is nullptr");return HDF_ERR_INVALID_PARAM;}IAM_LOGI("authenticate, result is %{public}d", ResultCode::NOT_ENROLLED);int32_t ret = callbackObj->OnResult(ResultCode::NOT_ENROLLED, {});if (ret != HDF_SUCCESS) {IAM_LOGE("callback result is %{public}d", ret);return HDF_FAILURE;}return HDF_SUCCESS;
    }// 实现指纹识别接口
    int32_t AllInOneExecutorService::Identify(uint64_t scheduleId, const std::vector<uint8_t> &extraInfo, const sptr<IExecutorCallback> &callbackObj)
    {IAM_LOGI("interface mock start");static_cast<void>(scheduleId);static_cast<void>(extraInfo);if (callbackObj == nullptr) {IAM_LOGE("callbackObj is nullptr");return HDF_ERR_INVALID_PARAM;}IAM_LOGI("identify, result is %{public}d", ResultCode::OPERATION_NOT_SUPPORT);int32_t ret = callbackObj->OnResult(ResultCode::OPERATION_NOT_SUPPORT, {});if (ret != HDF_SUCCESS) {IAM_LOGE("callback result is %{public}d", ret);return HDF_FAILURE;}return HDF_SUCCESS;
    }// 实现删除指纹模板接口
    int32_t AllInOneExecutorService::Delete(const std::vector<uint64_t> &templateIdList)
    {IAM_LOGI("interface mock start");static_cast<void>(templateIdList);IAM_LOGI("delete success");return HDF_SUCCESS;
    }// 实现通过scheduleId取消指定操作接口
    int32_t AllInOneExecutorService::Cancel(uint64_t scheduleId)
    {IAM_LOGI("interface mock start");static_cast<void>(scheduleId);IAM_LOGI("cancel success");return HDF_SUCCESS;
    }// 实现指纹认证服务向Fingerprint_auth驱动传递参数的通用接口,当前需要实现冻结与解锁模板命令
    int32_t AllInOneExecutorService::SendCommand(int32_t commandId, const std::vector<uint8_t> &extraInfo, const sptr<IExecutorCallback> &callbackObj)
    {IAM_LOGI("interface mock start");static_cast<void>(extraInfo);if (callbackObj == nullptr) {IAM_LOGE("callbackObj is nullptr");return HDF_ERR_INVALID_PARAM;}int32_t ret;switch (commandId) {case DriverCommandId::LOCK_TEMPLATE:IAM_LOGI("lock template, result is %{public}d", ResultCode::SUCCESS);ret = callbackObj->OnResult(ResultCode::SUCCESS, {});if (ret != HDF_SUCCESS) {IAM_LOGE("callback result is %{public}d", ret);return HDF_FAILURE;}break;case DriverCommandId::UNLOCK_TEMPLATE:IAM_LOGI("unlock template, result is %{public}d", ResultCode::SUCCESS);ret = callbackObj->OnResult(ResultCode::SUCCESS, {});if (ret != HDF_SUCCESS) {IAM_LOGE("callback result is %{public}d", ret);return HDF_FAILURE;}break;case DriverCommandId::INIT_ALGORITHM:IAM_LOGI("init algorithm, result is %{public}d", ResultCode::SUCCESS);ret = callbackObj->OnResult(ResultCode::SUCCESS, {});if (ret != HDF_SUCCESS) {IAM_LOGE("callback result is %{public}d", ret);return HDF_FAILURE;}break;default:IAM_LOGD("not support DriverCommandId : %{public}d", commandId);ret = callbackObj->OnResult(ResultCode::OPERATION_NOT_SUPPORT, {});if (ret != HDF_SUCCESS) {IAM_LOGE("callback result is %{public}d", ret);return HDF_FAILURE;}}return HDF_SUCCESS;
    }// 实现获取执行器属性接口
    int32_t AllInOneExecutorService::GetProperty(const std::vector<uint64_t> &templateIdList, const std::vector<int32_t> &propertyTypes, Property &property)
    {IAM_LOGI("interface mock start");property = {};IAM_LOGI("get property success");return HDF_SUCCESS;
    }// 实现设置需缓存模板列表接口
    int32_t AllInOneExecutorService::SetCachedTemplates(const std::vector<uint64_t> &templateIdList)
    {IAM_LOGI("interface mock start");IAM_LOGI("set cached templates success");return HDF_SUCCESS;
    }// 实现注册SA命令回调接口
    int32_t AllInOneExecutorService::RegisterSaCommandCallback(const sptr<ISaCommandCallback> &callbackObj)
    {IAM_LOGI("interface mock start");IAM_LOGI("register sa command callback success");return HDF_SUCCESS;
    }
    
  4. 用户身份认证框架支持多driver,当增加driver或者修改driver信息,需要修改如下文件中serviceName2Config。

    // base/user_iam/fingerprint_auth/services/src/fingerprint_auth_service.cpp
    void FingerprintAuthService::StartDriverManager()
    {IAM_LOGI("start");int32_t ret = UserAuth::IDriverManager::Start(HDI_NAME_2_CONFIG);if (ret != UserAuth::ResultCode::SUCCESS) {IAM_LOGE("start driver manager failed");}
    }
    

调测验证

驱动开发完成后,开发者可以通过用户认证API接口开发HAP应用,基于RK3568平台验证。认证和取消功能验证的测试代码如下:

1.发起认证并获取认证结果的测试代码如下:

  // API version 10import type {BusinessError} from '@ohos.base';import userIAM_userAuth from '@ohos.userIAM.userAuth';// 设置认证参数const authParam: userIAM_userAuth.AuthParam = {challenge: new Uint8Array([49, 49, 49, 49, 49, 49]),authType: [userIAM_userAuth.UserAuthType.PIN, userIAM_userAuth.UserAuthType.FINGERPRINT],authTrustLevel: userIAM_userAuth.AuthTrustLevel.ATL3,};// 配置认证界面const widgetParam: userIAM_userAuth.WidgetParam = {title: '请进行身份认证',};try {// 获取认证对象let userAuthInstance = userIAM_userAuth.getUserAuthInstance(authParam, widgetParam);console.info('get userAuth instance success');// 订阅认证结果userAuthInstance.on('result', {onResult(result) {console.info(`userAuthInstance callback result: ${JSON.stringify(result)}`);// 可在认证结束或其他业务需要场景,取消订阅认证结果userAuthInstance.off('result');}});console.info('auth on success');userAuthInstance.start();console.info('auth start success');} catch (error) {const err: BusinessError = error as BusinessError;console.error(`auth catch error. Code is ${err?.code}, message is ${err?.message}`);}

2.取消认证的测试代码如下:

  // API version 10import type {BusinessError} from '@ohos.base';import userIAM_userAuth from '@ohos.userIAM.userAuth';const authParam: userIAM_userAuth.AuthParam = {challenge: new Uint8Array([49, 49, 49, 49, 49, 49]),authType: [userIAM_userAuth.UserAuthType.PIN, userIAM_userAuth.UserAuthType.FINGERPRINT],authTrustLevel: userIAM_userAuth.AuthTrustLevel.ATL3,};const widgetParam: userIAM_userAuth.WidgetParam = {title: '请进行身份认证',};try {// 获取认证对象let userAuthInstance = userIAM_userAuth.getUserAuthInstance(authParam, widgetParam);console.info('get userAuth instance success');// 开始认证userAuthInstance.start();console.info('auth start success');// 取消认证userAuthInstance.cancel();console.info('auth cancel success');} catch (error) {const err: BusinessError = error as BusinessError;console.error(`auth catch error. Code is ${err?.code}, message is ${err?.message}`);}

相关文章:

OpenHarmony外设驱动使用 (五),Fingerprint_auth

OpenHarmony外设驱动使用 &#xff08;五&#xff09; Fingerprint_auth 概述 功能简介 指纹认证是端侧设备不可或缺的功能&#xff0c;为设备提供用户认证能力&#xff0c;可应用于设备解锁、支付、应用登录等身份认证场景。用户注册指纹后&#xff0c;指纹认证模块就可为设…...

.NET外挂系列:2. 了解强大的 harmony 注解特性

一&#xff1a;背景 1. 讲故事 上一篇我们简单的聊了下harmony外挂的基本玩法&#xff0c;让大家宏观上感受到了外挂在 .NET高级调试 领域的威力&#xff0c;这一篇我们从 注解特性 这个角度继续展开。 二&#xff1a;harmony 注解特性 1. HarmonyPatch 解读 在harmony支持…...

【Arm】应用ArmDS移植最小FreeRTOS系统

一、文档背景 FreeRTOS&#xff08;Free Real-Time Operating System&#xff09;是一个开源的实时操作系统内核&#xff0c;广泛应用于嵌入式系统。它具有小巧、灵活、低功耗等特点&#xff0c;支持多任务调度、信号量、队列等实时操作系统基本功能。 将FreeRTOS移植到特定硬…...

Python----目标检测(labelimg和labelme的安装与使用,Pycharm配置教程)

一、labelimg labelimg是一款开源的图像标注工具&#xff0c;标签可用于分类和目标检测&#xff0c;它是用python写的&#xff0c;并使用Qt作为其图形界面&#xff0c;简单好用&#xff08;虽然是英文版的&#xff09;。其注释以 PASCAL VOC格式保存为XML文件&#xff0c;这是I…...

Axure跨页面交互:利用IFrame和JS实现父子页面菜单联动

在 Axure 中使用内联框架&#xff08;IFrame&#xff09;进行父子页面间的通信是一个非常有效的技巧&#xff0c;尤其是在需要实现复杂交互逻辑时。通过 JavaScript 直接操作 DOM 元素&#xff0c;可以突破 Axure 基础元件的限制&#xff0c;实现更灵活的页面联动。以下是对你描…...

百度飞桨OCR(PP-OCRv4_server_det|PP-OCRv4_server_rec_doc)文本识别-Java项目实践

什么是OCR? OCR&#xff08;Optical Character Recognition&#xff0c;光学字符识别&#xff09;是一种通过技术手段将图像或扫描件中的文字内容转换为可编辑、可搜索的文本格式&#xff08;如TXT、Word、PDF等&#xff09;的技术。它广泛应用于文档数字化、信息提取、自动化…...

救生衣穿戴检测数据集VOC+YOLO格式2171张2类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;2171 标注数量(xml文件个数)&#xff1a;2171 标注数量(txt文件个数)&#xff1a;2171 …...

JAVA请求vllm的api服务报错Unsupported upgrade request、 Invalid HTTP request received.

环境&#xff1a; vllm 0.8.5 java 17 Qwen3-32B-FP8 问题描述&#xff1a; JAVA请求vllm的api服务报错Unsupported upgrade request、 Invalid HTTP request received. WARNING: Unsupported upgrade request. INFO: - "POST /v1/chat/completions HTTP/1.1&…...

信号量基础入门:并发控制的核心概念

问题的复杂性产生的根本原因在于&#xff0c;如 2.2 节所述&#xff0c;共享变量的访问始终是“单向信息流”。也就是说&#xff0c;一个进程可以分配新值或检查当前值&#xff0c;但这种检查不会为其他进程留下任何痕迹。结果是&#xff0c;当一个进程想要对共享变量的当前值作…...

物联网之使用Vertx实现HTTP/WebSocket最佳实践

小伙伴们&#xff0c;你们好呀&#xff0c;我是老寇&#xff0c;跟我一起学习使用Vertx实现HTTP-Server和WebSocket-Server 实现Http/WebSocket【响应式】 Vertx-Web地址 实现过程 查看源码 代码比较简单&#xff0c;懒得讲解啦 代码比较简单&#xff0c;懒得讲解啦 代码…...

【神经网络与深度学习】GAN 生成对抗训练模型在实际训练中很容易判别器收敛,生成器发散

引言部分 在深度学习领域&#xff0c;生成对抗网络&#xff08;GAN&#xff09;是一种强大的数据生成方法&#xff0c;它通过生成器&#xff08;G&#xff09;和判别器&#xff08;D&#xff09;之间的博弈来不断优化模型。然而&#xff0c;在实际训练过程中&#xff0c;GAN 往…...

使用 NGINX 的 `ngx_http_secure_link_module` 模块保护资源链接

一、模块简介 版本&#xff1a;自 NGINX 0.7.18 起引入 功能&#xff1a; 签名校验&#xff1a;对请求 URI 中的签名进行校验&#xff0c;保证链接未经篡改。时效控制&#xff1a;根据请求中携带的过期时间&#xff0c;判断链接是否仍在有效期。 启用方式&#xff1a;编译 NG…...

5月19日day30打卡

模块和库的导入 知识点回顾&#xff1a; 导入官方库的三种手段导入自定义库/模块的方式导入库/模块的核心逻辑&#xff1a;找到根目录&#xff08;python解释器的目录和终端的目录不一致&#xff09; 作业&#xff1a;自己新建几个不同路径文件尝试下如何导入 一、导入官方库 …...

NW860NW894美光闪存颗粒NX770NX789

在数字化浪潮席卷全球的当下&#xff0c;数据存储技术的革新正以惊人的速度推动着硬件性能的边界。美光科技作为半导体存储领域的领军者&#xff0c;其NW860、NW894、NX770、NX789系列闪存颗粒凭借前沿架构与精密工艺&#xff0c;成为高性能存储解决方案的核心载体。本文将深入…...

高性能锁机制 CAS:Java 并发编程中的深度剖析

引言 在并发编程领域&#xff0c;i操作的非线程安全性是开发者们熟知的问题。这一现象根源在于i并非原子操作&#xff0c;其内部执行过程包含读取、修改和写入三个步骤&#xff0c;在多线程环境下极易因线程切换导致数据竞争与不一致&#xff0c;这与我们此前探讨的多线程常见问…...

leetcode 每日一题 1931. 用三种不同颜色为网格涂色

题目 1931. 用三种不同颜色为网格涂色 思路 先获取列表&#xff0c;上下左右的所有情况。解决一维的问题 然后所有一维的问题暴力循环。已知一个一维的解&#xff0c;可以对应其他一维解的列表&#xff08;用于记忆化搜索&#xff09; 然后使用递归&#xff0c;进行累加 代…...

解决Windows磁盘管理中因夹卷导致的无法分区问题

解决Windows磁盘管理中因夹卷导致的无法分区问题 在现代计算机管理中&#xff0c;磁盘分区是一个常见且重要的操作。无论是为了优化存储空间&#xff0c;还是为了实现多系统安装&#xff0c;合理的磁盘分区都是必不可少的。然而&#xff0c;许多用户在使用Windows磁盘管理工具…...

龙虎榜——20250519

上证指数缩量收十字星&#xff0c;个股涨多跌少&#xff0c;这周反弹的概率比较大。 深证指数缩量调整&#xff0c;临近反弹&#xff0c;个股表现更好。 2025年5月19日龙虎榜行业方向分析 化工&#xff08;新能源材料国产替代&#xff09; • 代表个股&#xff1a;红宝丽、…...

Python在自动驾驶数据清洗中的应用

Python在自动驾驶数据清洗中的应用 在自动驾驶领域,数据是算法的燃料。高质量的数据意味着更精准的模型,更稳定的驾驶体验。然而,原始数据通常充满噪声、缺失值、不一致格式,甚至有异常点,这些都会严重影响自动驾驶系统的可靠性。因此,数据清洗是一道绕不开的关卡。 一…...

腾讯云Mysql实现远程链接

1.SQL语句&#xff1a;CREATE USER remote_user% IDENTIFIED BY YourPassword; GRANT ALL PRIVILEGES ON *.* TO remote_user%; FLUSH PRIVILEGES; 2.设置入站规则 3.设置安全组 4.效果...

大模型(2)——提示工程(Prompt Engineering)

文章目录 一、提示工程的核心概念为什么需要提示工程&#xff1f; 二、提示设计的基本原则三、实用提示工程技巧1. 角色设定法2. 示例引导法&#xff08;Few-Shot Learning&#xff09;3. 分阶段提问4. 负面约束5. 温度&#xff08;Temperature&#xff09;控制 四、不同任务类…...

深入Java G1 GC调优:如何解决高延迟与吞吐量瓶颈

引言 Java的垃圾回收&#xff08;GC&#xff09;机制是JVM性能的核心&#xff0c;但即使是最先进的G1&#xff08;Garbage-First&#xff09;收集器&#xff0c;在复杂场景下仍可能引发长时间停顿&#xff08;Stop-The-World, STW&#xff09;​或吞吐量骤降。许多开发者虽然熟…...

DAPO:用于指令微调的直接偏好优化解读

一、背景与动机:从RLHF到DPO,再到DAPO 大型语言模型(LLM)经过海量无监督预训练后,往往需要对齐人类偏好或遵循指令的微调,使模型的回答更符合人类期望。这一过程通常通过人类反馈强化学习(RLHF)来实现。例如OpenAI的ChatGPT就使用了RLHF:先让人工标注对模型输出进行偏…...

vue2、vue3项目打包生成txt文件-自动记录打包日期:git版本、当前分支、提交人姓名、提交日期、提交描述等信息 和 前端项目的版本号json文件

vue2 打包生成text文件 和 前端项目的版本号json文件 项目打包生成txt文件-自动记录git版本、当前分支、提交人姓名、提交日期、提交描述等信息生成版本号json文件-自动记录当前版本号、打包时间等信息新建branch-version-webpack-plugin.js文件 // 同步子进程 const execSyn…...

iOS解码实现

import Foundation import VideoToolboxclass KFVideoDecoderInputPacket {var sampleBuffer: CMSampleBuffer? }class KFVideoDecoder {// MARK: - 常量private let kDecoderRetrySessionMaxCount 5private let kDecoderDecodeFrameFailedMaxCount 20// MARK: - 回调var pi…...

Windows中PDF TXT Excel Word PPT等Office文件在预览窗格无法预览的终级解决方法大全

Windows中PDF TXT Excel Word PPT等Office文件在预览窗格无法预览的终级解决方法大全 参考链接&#xff1a; https://zhuanlan.zhihu.com/p/454259765...

在Excel中使用函数公式时,常见错误对应不同的典型问题

在Excel中使用函数公式时&#xff0c;常见错误对应不同的典型问题 1. #DIV/0!&#xff08;除以零错误&#xff09;2. #N/A&#xff08;值不可用&#xff09;3. #NAME?&#xff08;名称错误&#xff09;4. #NULL!&#xff08;空交集错误&#xff09;5. #NUM!&#xff08;数值错…...

Excel

1.快捷键 CtrlE 快速填充 CtrlQ 快速分析 CtrlEnter 原位填充 Tab 横向移动到下一个单元格 Enter 移动到下一行起始位置对应单元格 Shift 返回上一个单元格 0空格分数 显示分数 1.if if(condition,true,false)if(A1>10,"true","fa…...

Rust 学习笔记:错误处理

Rust 学习笔记&#xff1a;错误处理 Rust 学习笔记&#xff1a;错误处理不可恢复的错误带有结果的可恢复错误匹配不同的错误出现错误时 panic 的快捷方式&#xff1a;unwrap 和 expect传播错误传播错误的快捷方式&#xff1a;? 操作符哪里可以使用 ? 操作符 panic or not pan…...

【Linux】系统指令与开发全栈(vim、ssh、gcc)

【Linux】系统指令与开发全栈&#xff08;vim、ssh、gcc&#xff09; 一、Linux 系统指令大全 1、文件与目录管理 基础操作 指令参数说明典型用例注意事项cd~ 家目录&#xff0c;- 返回上级&#xff0c;.. 上级目录cd ~/Documents 进入文档目录无目录权限时会报错ls-l 详情&am…...

用 CodeBuddy 搭建「MiniGoal 小目标打卡器」:一次流畅的 UniApp 开发体验

我正在参加CodeBuddy「首席试玩官」内容创作大赛&#xff0c;本文所使用的 CodeBuddy 免费下载链接&#xff1a;腾讯云代码助手 CodeBuddy - AI 时代的智能编程伙伴 在日常生活中&#xff0c;我们总是希望能够坚持一些小习惯&#xff0c;比如每天锻炼十分钟、读一页书、早睡十分…...

前端(vue)学习笔记(CLASS 6):路由进阶

1、路由的封装抽离 将之前写在main.js文件中的路由配置与规则抽离出来&#xff0c;放置在router/index.js文件中&#xff0c;再将其导入回main.js文件中&#xff0c;即可实现路由的封装抽离 例如 //index.js import { createMemoryHistory, createRouter } from vue-routerim…...

ubuntu 安装 Redis新版Redis 7.x

以下是在Ubuntu系统中安装Redis的详细指南&#xff0c; 一、官方APT源安装 sudo apt install redis-server -y 默认安装最新APT源版本&#xff08;Ubuntu 22.04通常为Redis 6.x&#xff09; 服务自动启动&#xff0c;配置文件路径&#xff1a;/etc/redis/redis.conf验证安装 …...

Httphelper: Http请求webapi小记

文章目录 1、HttpHelper.cs Framework4.812、HttpHelper.cs NET83、JsonHelper.cs Framework4.814、JsonHelper.cs NET85、uniapp request.js 访问WEBAPI 每次查找、测试都比较费事&#xff0c;记录一下把 1、HttpHelper.cs Framework4.81 using System; using System.IO; usi…...

【Linux】进程控制(进程创建、进程终止、进程等待、进程替换)

目录 一、进程创建 1、fork函数 2、页表权限 二、进程终止 1、main函数返回值&#xff08;退出码&#xff09; 2、常见错误码及其对应的错误描述&#xff1a; 将错误退出码转化为错误描述的方法&#xff1a; 3、进程退出的三种场景 4、由上我们可以知道&#xff1a; 5…...

java+selenium专题->启动浏览器下篇

1.简介 上一篇文章&#xff0c;我们已经在搭建的java项目环境中实践了&#xff0c;今天就在基于maven项目的环境中演示一下。 2.eclipse中新建maven项目 1.依次点击eclipse的file - new - other &#xff0c;如下图所示&#xff1a; 2.在搜索框输入关键字“maven”&#xff…...

sqlserver 循环删除1000行

在SQL Server中&#xff0c;如果你想循环删除1000行数据&#xff0c;有几种方法可以实现&#xff0c;但值得注意的是&#xff0c;频繁使用循环删除操作可能会对数据库性能造成影响&#xff0c;尤其是在处理大量数据时。下面介绍几种方法&#xff0c;并讨论它们的优缺点。 方法…...

亚信电子与联发科技携手打造AIoT新未来

[台湾新竹讯, 2025年5月19日] 智能物联网&#xff08;AIoT&#xff09;融合人工智能与物联网技术&#xff0c;通过边缘AI的实时数据分析及设备智能联网能力&#xff0c;加速智能物联网创新应用的蓬勃发展。为满足AIoT产业对多网络端口的应用需求&#xff0c;全球半导体公司【联…...

【成品设计】基于STM32的人体健康监测系统

《基于STM32的人体健康监测系统》 Ps:有4个版本。 V1硬件设计&#xff1a; 主控&#xff1a;STM32F103C8T6&#xff1a;作为系统主控芯片。 血氧心率传感器&#xff1a;用于采集当前心率、血氧值。 温湿度传感器&#xff1a;用于采集当前环境温湿度。 有源低电平触发蜂鸣器&…...

【MySQL进阶】了解linux操作系统下mysql的配置文件和常用选项

前言 &#x1f31f;&#x1f31f;本期讲解关于linux下mysql配置选项的详细介绍~~~ &#x1f308;感兴趣的小伙伴看一看小编主页&#xff1a;GGBondlctrl-CSDN博客 &#x1f525; 你的点赞就是小编不断更新的最大动力 &#x1f386;那么…...

LeetCode 219.存在重复元素 II

目录 题目&#xff1a; 题目描述&#xff1a; 题目链接&#xff1a; 思路&#xff1a; 核心思路&#xff1a; 思路详解&#xff1a; 代码&#xff1a; C代码&#xff1a; Java代码&#xff1a; 题目&#xff1a; 题目描述&#xff1a; 题目链接&#xff1a; 219. 存…...

解释:神经网络

在过去的10年里&#xff0c;表现最好的artificial-intelligence系统——比如智能手机上的语音识别器或谷歌最新的自动翻译——都是由一种叫做“深度学习”的技术产生的 深度学习实际上是一种被称为神经网络的人工智能方法的新名称&#xff0c;这种方法已经流行了70多年。1944年…...

Java 泛型详解

在 Java 的类型系统中&#xff0c;泛型&#xff08;Generics&#xff09; 是一个非常重要的特性。它让我们能够编写更通用、更安全的代码&#xff0c;尤其是在处理集合类&#xff08;如 List、Map 等&#xff09;时&#xff0c;泛型的使用可以大大减少类型转换的麻烦&#xff0…...

React集成百度【JSAPI Three】教程(001):快速入门

文章目录 1、快速入门1.1 创建react项目1.2 安装与配置1.3 静态资源配置1.4 配置百度地图AK1.5 第一个DEMO1、快速入门 JSAPI Three版本是一套基于Three.js的三维数字孪生版本地图服务引擎,一套引擎即可支持2D、2.5D、3D全能力的地理投影与数据源加载,帮助开发者轻松搞定平面…...

WPF中资源(Resource)与嵌入的资源(Embedded Resource)的区别及使用场景详解

🌟 开发WPF项目时图片、SVG、配置文件等到底该设置为哪种资源?如何正确读取、跨程序集访问?一篇文章全解答。 在使用 WPF 进行项目开发时,很多开发者在设置文件“生成操作(Build Action)”时,常常会在“资源(Resource)”和“嵌入的资源(Embedded Resource)”之间感…...

如何在 Windows 11 或 10 上安装 Fliqlo 时钟屏保

了解如何在 Windows 11 或 10 上安装 Fliqlo,为您的 PC 或笔记本电脑屏幕添加一个翻转时钟屏保以显示时间。 Fliqlo 是一款适用于 Windows 和 macOS 平台的免费时钟屏保。它也适用于移动设备,但仅限于 iPhone 和 iPad。Fliqlo 的主要功能是在用户不活动时在 PC 或笔记本电脑…...

【STM32】ST-Link V2.1制作

一、下载烧写工具及程序 下载器制作&#xff08;ST-Link V2.1&#xff09; 链接: 提取码&#xff1a;6666https://pan.baidu.com/s/1n0RYNDEw5mBT_CsTFoqrIg?pwd6666 二、安装STM32 CubeProgrammer 双击安装包&#xff0c;点击Next 继续点击Next 选择安装路径&#xff0c;再…...

day30python打卡

知识点回顾&#xff1a; 导入官方库的三种手段导入自定义库/模块的方式导入库/模块的核心逻辑&#xff1a;找到根目录&#xff08;python解释器的目录和终端的目录不一致&#xff09; 作业&#xff1a;自己新建几个不同路径文件尝试下如何导入 一、导入官方库 我们复盘下学习py…...

AI大语言模型评测体系演进与未来展望

随着人工智能技术的飞速发展,大语言模型(LLMs)已成为自然语言处理领域的核心研究方向。2025年最新行业报告显示,当前主流模型的评测体系已从单一任务评估转向多维度、全链路的能力剖析。例如,《全球首个大语言模型意识水平”识商”白盒DIKWP测评报告》通过数据、信息、知识…...

用Python将 PDF 中的表格提取为 Excel/CSV

*用Python将 PDF 中的表格提取为 Excel/CSV&#xff0c;*支持文本型 PDF 和 扫描件/图片型 PDF&#xff08;需 OCR 识别&#xff09;。程序包含以下功能&#xff1a; 1.自动检测 PDF 类型&#xff08;文本 or 扫描件&#xff09; 2.提取表格数据并保存为 Excel/CSV 3.处理多页…...