全部产品

人脸&二维码对接说明

更新时间:2020-08-25 10:03:26

1. 整体架构

image.png

2. 业务流程

  1. 人脸业务链路

    image.png

  2. 二维码业务链路

    1二维码

3. 对接LinkKit-SDK

  1. C语言SDK

    1. 完整SDK手册:https://code.aliyun.com/edward.yangx/public-docs/wikis/user-guide/linkkit/Archived/V230_Snapshot_Index

    2. 交叉编译手册:https://code.aliyun.com/edward.yangx/public-docs/wikis/user-guide/linkkit/Archived/V230_Snapshot/porting/Make_Usage

  2. Android版SDK手册

    1. https://help.aliyun.com/document_detail/96607.html

  3. 创建门禁产品

    1. 访问地址:https://iot.console.aliyun.com/product,创建产品(创建多功能门禁产品)

      image.png

    2. 所属品类选择多功能门禁

      品类

    3. 前往定义物模型

      前往定义物模型

    4. 添加设备的多功能门禁功能,点击编辑草稿

      编辑草稿

    5. 点击添加标准功能

      添加标准功能

    6. 进入功能页,勾选所有功能项,点击确认

      勾选确认

  4. 创建门禁设备

    1. 进入创建设备页面,https://iot.console.aliyun.com/devices

      image.png

    2. 选择多功能门禁产品

      选择多功能门禁产品

    3. 复制多功能门禁证书

      复制门禁证书

  5. 下载的证书,请参照一下文档,将设备三元组设置到linkkitsdk中,正常运行后,可以查看到该设备的状态变更为上线,本案例采用一机一密模式进行

    1. C语言设备认证地址:https://code.aliyun.com/edward.yangx/public-docs/wikis/user-guide/linkkit/Archived/V230_Snapshot/programme/Auth_Connect#%E4%B8%80%E6%9C%BA%E4%B8%80%E5%AF%86%E7%BC%96%E7%A8%8B

    2. Android设备认证地址:https://help.aliyun.com/document_detail/96608.html

  6. 设备开发,设备物模型开发手册,您可以通过这个文档内容学习怎么通过sdk完成物模型中的服务和事件的实现,针对门禁对接中您需要实现syncPermissions、remoteOpen两个服务、passEvent一个事件、RSAPublicKey一个属性。

    1. C语言SDK设备开发说明:https://code.aliyun.com/edward.yangx/public-docs/wikis/user-guide/linkkit/Archived/V230_Snapshot/programme/DeviceModel_Prog

    2. Android版SDK设备开发说明:https://help.aliyun.com/document_detail/96609.html

    3. RSA公钥(RSAPublicKey):二维码加密公钥,云端会在设备上电并设备绑定后触发下发的公钥信息,用于后续通过摄像头获取用户二维码之后的解密校验

    4. 同步门禁权限(syncPermissions):云端下发使用二维码的用户权限配置信息到设备端。参数:权限url(permissionUrl)为权限配置文件下载地址,通过http下载。

      字段

      类型

      备注

      qrCodePermissionList

      JSONArray

      二维码的权限配置内容

      tradCardPermissionList

      JSONArray

      传统卡的权限配置内容

      bleCardPermissionList

      JSONArray

      蓝牙卡的权限配置内容

      passwordPermissionList

      JSONArray

      密码的权限配置内容

      二维码的权限配置定义

      字段

      类型

      备注

      type

      Integer

      操作类型。

      取值范围:1-增加,2-删除,3-修改。

      qrCode

      String

      用户信息内容。样例:ALI24DD9END8CD3F,表示一个人的唯一id

      startTime

      String

      生效时间(UTC)单位精确到毫秒。

      endTime

      String

      生效时间(UTC)单位精确到毫秒。

      maxScanTimes

      String

      最大刷码限制次数。-1 表示无限制。

      maxScanScope

      Integer

      表明二维码刷码次数限制是多个设备共享还是单个设备维度,默认设备维度。如果不限制刷码次数,无需指定该值。

      取值范围:SHARE 或 DEVICE,默认DEVICE。

      二维码校验机制,用户在设备前展示二维码,设备获取二维码后进行RSA解密,解密后的数据格式为:code:timestamp(样例:ALI24DD9END8CD3:1589369696)

      字段

      语义

      备注

      code

      下发二维码权限中的qrCode

      需要和本地存储的qrCode列表做对比,对比成功表示当前人是有效的

      timestamp

      秒级时间戳,表示当前码的有效期

      需要和本地设备时间进行对比,如果这个时间戳<当前时间则提示“二维码已过期”

    5. 远程开门(remoteOpen):云端下发对设备的开门命令,设备端执行开门动作

    6. 通行事件(passEvent):使用二维码、传统卡、蓝牙卡、密码进行通行时,将通行结果及认证信息发到云端

      字段

      类型

      备注

      code

      Int

      错误编码,成功为0,其他值为失败。格式定义见下面code列表。

      message

      String

      错误信息,成功(success),或者失败的原因。格式定义见下面code列表

      type

      String

      认证类型,二维码(qrCode)、传统卡(tradCard)、蓝牙卡(bleCard)、密码(password)、人脸(face)

      content

      String

      认证内容(content):二维码字符串、卡号、蓝牙卡号、密码字符串。

      extInfo

      String

      扩展信息(extInfo):内容为string格式,先构造JSON对象,再将JSON对象转成string格式放入其中,详见下面示例。

      {
      "code": 0, //
      "message": "success", //
      "type": "qrCode", //
      "content": "ALI7af81cfad5df1", //
      "extInfo": "{\"type\":\"qrCode\",\"qrCode\":\"ALI7af81cfad5df1
      \",\"userID\":\"508072501fea93ae39fc8737a2fb2a2424f88787\",\"event
      Time\":\"1597821579\"}"
      7}

      通行结果(code)

      结果说明(message)

      0

      成功

      success

      101

      二维码未生效

      QR code does not take effect

      102

      二维码已过期

      QR code has expired

      103

      二维码不存在

      QR code does not exist

      104

      二维码刷码次数用尽

      QR code have be used up

    7. 设备SDK下载

      1. C语言SDK下载地址:https://product-info.oss-cn-shanghai.aliyuncs.com/linkkit-sdk-c.tar%2830000%29.gz

      2. AndroidSDK下载地址:http://gaic.alicdn.com/ztms/android-linkkit-demo/IoTDeviceSDKDemo.zip?spm=a2c4g.11186623.2.14.46642af38h0ZAL&amp;amp;amp;amp;amp;amp;file=IoTDeviceSDKDemo.zip

    8. 4. 对接LinkFaceSDK

      本章将以C版本SDK为例着重介绍,如过您使用的是Android版本,请查看LinkFace Android设备端开发指南

      1. 文件目录介绍

        tree -L 3
        .
        ├── CMakeLists.txt
        ├── LinkFace
        │   ├── samples
        │   │   ├── 1.jpg
        │   │   ├── database.c
        │   │   ├── database.h
        │   │   ├── linkface_demo.c
        │   │   ├── linkface_demo_v2.c              
        │   │   ├── linkkit_adapter.c
        │   │   ├── link_visual_face_demo
        │   │   ├── link_visual_face_demo_v2  //对接Demo可执行文件 
        │   │   └── list.h                                      //链表头文件
        │   └── sdk
        │       ├── liblinkface.a
        │       ├── liblinkface.so
        │       ├── liblinkface_v2.a                    //linkface静态库
        │       ├── liblinkface_v2.so                   //linkface动态库
        │       ├── linkface_sdk.h
        │       └── linkface_sdk_v2.h                   //linkface头文件
        └── third_party                                             //以静态库方式对接时,在此目录下获取需要的三方.a文件
            ├── cJSON
            │   ├── cJSON.h
            │   ├── libcjson.a
            │   ├── libcjson.so
            │   ├── libcjson.so.1
            │   └── libcjson.so.1.7.7
            ├── cJSON-1.7.7.tar.gz
            ├── linkkit-sdk-c
            │   ├── exports
            │   ├── imports
            │   ├── iot_export.h
            │   ├── iot_import.h
            │   ├── libiot_hal.a
            │   ├── libiot_sdk.a
            │   └── libiot_tls.a
            ├── linkkit-sdk-c.tar.gz
            ├── release                                             //以静态库方式对接时,在此目录下获取需要的三方.a文件
            │   ├── apr
            │   ├── apr_util
            │   ├── curl
            │   ├── expat
            │   ├── iconv
            │   ├── mxml
            │   ├── openssl
            │   ├── oss_c_sdk
            │   └── zlib
            ├── sqlite
            │   ├── libsqlite3.a
            │   ├── libsqlite3.so
            │   └── sqlite3.h
            └── sqlite-amalgamation-3310100.zip
            
            sdk集成方式:
            1.集成动态库:链接liblinkface_v2.so libiot_sdk.a libiot_tls.a libiot_hal.a
            2.集成静态库:链接liblinkface_v2.a libiot_sdk.a libiot_tls.a libiot_hal.a libcjson.a liboss_c_sdk_static.a libaprutil-1.a libapr-1.a libmxml.a libcurl.a libcrypto.a libssl.a     2.集成静态库:链接liblinkface_v2.a libcjson.a liboss_c_sdk_static.a libaprutil-1.a libapr-1.a libmxml.a libcurl.a libiconv.a libssl.a libcrypto.a m
      2. 头文件说明

        #ifndef __LINKFACE_SDK_V2_H__
        #define __LINKFACE_SDK_V2_H__
        
        #include "utils/list.h"
        
        #ifdef __cplusplus
        extern "C"
        {
        #endif
        
        #define LENGTH_PERSON_ID            65
        #define LENGTH_PERSON_NAME          128 
        #define LENGTH_FACE_MD5             33
        #define LENGTH_GROUP_ID             64
        #define LENGTH_EVENTLIST            1024  //人脸事件队列长度
        #define MAX_COUNT_GROUP_ID          16
        #define MAX_LENGTH_EXTRA_INFO       2048
        
        /* 错误码枚举值 */
        typedef enum {
            LF_ERR_SUCCESS = 0,                   //成功
        
            // 添加人脸底库错误码表 
            LF_ERR_IMAGE_PARSEPIC_FAIL   = 16001, //图片解析失败
            LF_ERR_IMAGE_PIC_TOOBIG      = 16002, //图片过大
            LF_ERR_IMAGE_PIC_TOOSMALL    = 16003, //图片过小
            LF_ERR_IMAGE_NO_FACE         = 16004, //图片中未检测到人脸
            LF_ERR_IMAGE_MULTI_FACES     = 16005, //图片中检测到多人脸
            LF_ERR_IMAGE_FACE_EXIST      = 16006, //人脸已存在 
            LF_ERR_IMAGE_PIC_FORMATE_ERR = 16007, //不支持的图片格式
            LF_ERR_IMAGE_PIX_ERR         = 16008, //不支持的像素
            LF_ERR_IMAGE_UNKNOWN_ERR     = 16009, //其他未知错误
            LF_ERR_IMAGE_ALG_ERR         = 16010, //算法异常,无法提特征
            LF_ERR_IMAGE_FACE_DB_FULL    = 16011, //人脸底库达到最大值
        
            // 文件下载错误码列表
            LF_ERR_URL_DOWNLOAD_FAILED   = 17001  //下载失败
        } LINKFACE_ERROR_CODE_E;
        
        /*新增人脸底库需要的字段*/
        typedef struct {
          /*! 人员ID: 可能是身份证号码,也可能是其他身份唯一标示。 */
          char person_id[LENGTH_PERSON_ID];
        
          /*! 人脸数据md5值。*/
          char faceimg_md5[LENGTH_FACE_MD5];
          
          /*! 人脸图片数据大小。单位:字节。仅在注册人脸时有效。 */
          int faceimg_size;
        
          /*! 人脸图像原始数据。*/
          char* faceimg;
        
          /*! 新增本次人脸数据时的时间戳, 单位为ms。*/
          long long timestamp_ms;
        } linkface_image_info;
        
        /* 
         * 人脸照片添加队列
         */
        typedef struct __tag_add_face_image_list{
            struct  list_head   face_list;
            linkface_image_info face_image_info;
        }LINKFACE_ADD_FACE_IMAGE_LIST_ST;
        
        typedef struct {
            /*! 人员ID: 可能是身份证号码,也可能是其他身份唯一标示。 */
            char person_id[LENGTH_PERSON_ID];
           
            /*! 人员姓名,可能为空。*/
            char name[LENGTH_PERSON_NAME];
        
            /*! 该人员所在的组ID,一个人可能存在于多个组中*/
            char group_ids[MAX_COUNT_GROUP_ID][LENGTH_GROUP_ID];
        
            int group_id_count;
        
            /*! 新增该人员信息时的时间戳,单位为ms。*/
            long long timestamp_ms;
          
            /*! 注册人脸时的其他信息。 */
            char *extra_info;
        } linkface_user_info;
        
        /// 人脸比对状态。
        typedef enum {
            /*! 比对失败。 */
            LINKFACE_MATCH_STATUS_FAILED  = -1,
        
            /*! 未进行比对。 */
            LINKFACE_MATCH_STATUS_NULL    = 0,
        
            /*! 比对成功。 */
            LINKFACE_MATCH_STATUS_SUCCESS    = 1,
        }linkface_match_status_t;
        
        // 增量事件类型
        typedef enum {
            //用户信息
            LINKFACE_SYNC_EVENT_USERINFO    = 1,
            //用户图片
            LINKFACE_SYNC_EVENT_USERPIC     = 2,
            //用户特征
            LINKFACE_SYNC_EVENT_USERFEATURE = 3,
            //用户快速删除
            LINKFACE_SYNC_EVENT_USERFASTDEL = 4,
            //删除用户组
            LINKFACE_SYNC_EVENT_GROUPDEL    = 5,
            //清空所有数据
            LINKFACE_SYNC_EVENT_DELTOTAL    = 21    
        }LINKFACE_SYNC_EVENT_E;
        
        /*人脸对比上报*/
        /*注册人脸需要的字段*/
        typedef struct {
            /*! 比对状态。 */
          linkface_match_status_t match_status; //对比成功才会有人脸(即person_id,match_score)的值才有意义
        
          /*! 人脸比对到的人员ID。 抓拍成功时,必要字段。*/
          char person_id[64];
        
          /*! 对比相似度。 抓拍成功时,必要字段。范围为[1, 100]*/
          int match_score;
        
          /*! 人脸全景图大小。单位为Byte,不能超过10MByte. */
          int panorama_img_size; //大小大于0时全景图的数据才有意义,否则全景图所有数据全部置空
        
          /*! 人脸全景图原始数据。必要字段。 */
          char *panorama_img;
        
          /*! 人脸全景图名称。 必要字段。 */
          char *panorama_img_name;
        
          /*! 事件产生utc时间。 必要字段。 */
          long long timestamp_ms;
        }linkface_match_result;
        
        /* 
         * 事件上报队列
         */
        typedef struct __tag_event_list{
            struct  list_head       event_list;
            linkface_match_result   match_result;
        }LINKFACE_EVENT_LIST_ST;
        
        /** 云端下发底库同步请求时,sdk先缓存人脸图片,再调用该接口将图片批量入库。
         * @brief       批量注册人脸数据接收回调函数。
         * @param[IN]   pImageInfoList,图片信息列表。
         * @param[IN]   num,该值为pImageInfoList、pErrorCode的个数。
         * @param[OUT]  pErrorCode,每张图片的入库结果
         * @ret         void
         *
         * 注意:
         * 1. 该接口为同步调用,接口不宜耗时过久。
         * 2. 该接口不会在多线程中并发调用。
         * 3. 每张图片的添加结果,都要返回到pErrorCode中
         * 4. 错误码请严格按照 @LINKFACE_ERROR_CODE_IMAGE_E 中的定义. 
         */
        typedef int (*linkface_add_image_batch_cb)(const struct list_head *pImageInfoList, int num, int *pErrorCode);
        
        /* 
         * 添加用户信息,成功返回0,失败返回其他值.
         */
        typedef int (*linkface_add_user_cb)(const linkface_user_info *info, void *user);
        
        /** 云端需要删除某个人脸底库时,sdk会调用该接口将人脸信息逐个传递给厂商,厂商需要将该人脸从算法数据库中删除.
         * @brief   删除人脸数据接收回调函数格式定义。
         * @param   人脸id。
         * @param   user 用户参数,用户设置回调时指定的参数通过该参数再次回传给用户。
         * @ret     如果删除人脸成功,则返回0,否者返回其他值。
         *
         * 注意:
         * 1. 只有人脸数据真实存在时(person_id/faceimg_md5组合校验),才返回成功。
         * 2. person_id 不存在返回-1。 
         * 3. person_id 存在而 faceimg_md5 不存在,返回-2。 
         * */
        typedef int (*linkface_del_image_cb)(const linkface_image_info *info, void *user);
        
        /*
         * 删除用户信息, 成功返回0,失败返回其他值.
         * */
        typedef int (*linkface_del_user_cb)(const linkface_user_info *info, void *user);
        
        /*
         * 基于入库的时间戳,轮询人脸底库信息。
         * */
        typedef void (*linkface_polling_image_cb)(long long timestamp_ms, void *user, void (*cb_sdk)(const linkface_image_info *info, void *user), void *user_sdk); 
        
        /*
         * 基于入库的时间戳,轮询人员库信息。
         * */
        typedef void (*linkface_polling_user_info_cb)(long long timestamp_ms, void *user, void (*cb_sdk)(const linkface_user_info *info, void *user), void *user_sdk); 
        
        /*
         * 基于人员id,查询人脸底库信息。
         * 厂商实现,结构体内存由sdk释放。
         * */ 
        typedef linkface_image_info* (*linkface_query_image_info_cb)(char *person_id, void *user);
        
        /*
         * 基于人员id,查询人员信息.
         * */
        typedef linkface_user_info* (*linkface_query_user_info_cb)(char *person_id, void *user);
        
        /*
         * 查询端侧人脸图片总数
         * */
        typedef int (*linkface_querey_image_count_cb)(void *user);
        
        /*
         * @brief       清除用户分组下的所有用户信息   
         * @param[IN]   group_id, 用户组ID
         * @ret         如果删除人脸成功,则返回0,否者返回具体错误码
         */
        typedef int (*linkface_clear_usergroup_cb)(const char *group_id);
        
        /*
         * 清空人脸库回调函数,由设备厂商实现
         *
         * */
        typedef int (*linkface_clearall_cb)();
        
        
        /**  当人脸门禁机检测到人脸后,无论是否能正确识别出该人脸,均需要调用本接口将识别结果上报到云端。
         * @param   result 识别结果,如果识别成功,需要包含已识别的人的基本信息。
         *
         * @return 成功返回0,失败返回其他值。
         *
         * 注意:
         * 1. 该接口为同步接口,请勿并发调用。
         * 2. 若接口返回失败,建议最多重试3次。
         */
        int linkface_upload_match_result(const linkface_match_result *result);
        
        /* 产品三元组长度限制 */
        #define LINKFACE_PRODUCT_KEY_LEN     (20)
        #define LINKFACE_DEVICE_NAME_LEN     (32)
        #define LINKFACE_DEVICE_SECRET_LEN   (64)
        
        typedef struct {
            char product_key[LINKFACE_PRODUCT_KEY_LEN + 1]; /* 三元组之一,由平台颁发的产品唯一标识,11位长度的英文数字随机组合 */
            char device_name[LINKFACE_DEVICE_NAME_LEN + 1]; /* 三元组之一,在注册设备时,自定义的或自动生成的设备名称,具备产品维度内的唯一性 */
            char device_secret[LINKFACE_DEVICE_SECRET_LEN + 1];  /* 三元组之一,物联网平台为设备颁发的设备密钥*/
        } linkface_device_triple_t; /* 物联网平台上分发的三元组信息。 */
        
        /* SDK日志等级 */
        typedef enum {
            LF_LOG_DEBUG = 0,
            LF_LOG_INFO = 1,
            LF_LOG_WARN = 2,
            LF_LOG_ERROR = 3,
        } linkface_log_level_e;
        
        typedef enum {
            LF_STDOUT = 0,
            LF_FILE   = 1
        } linkface_log_dest_e;
        
        typedef struct {
            linkface_log_level_e lvl;
            linkface_log_dest_e  dst;
        } linkface_log_cfg_t;
        
        typedef struct {
            char        *logpath;           //日志文件保存路径
            char        *picpath;           //人脸照片保存路径
            char        *eventpath;         //告警事件文件保存路径
            int         max_size_log_mb;    //日志存储目录大小Mb
            int         max_size_cache_mb;  //人脸照片存储目录大小Mb
            int         max_size_event_mb;  //告警事件存储目录大小Mb
        } linkface_cache_cfg_t;
        
        typedef struct {
            int                       addFaceBatchSize; //每次批量添加人脸的人数,范围为1~15
            linkface_device_triple_t  triple; 
            linkface_log_cfg_t        cfg_log;
            linkface_cache_cfg_t      cfg_cache;
        
            /* 批量增加底库图片*/
            struct {
                linkface_add_image_batch_cb cb;
                void                        *user;
            } config_batch_add_image;
        
            /* 删除一个底库图片*/
            struct {
                linkface_del_image_cb     cb;
                void                      *user;
            } config_del_image;
        
            /* 基于id查询底库图片信息*/
            struct {
                linkface_query_image_info_cb cb;
                void                     *user;
            } config_query_image_info;
        
            /* 遍历所有底库图片信息*/
            struct {
                linkface_polling_image_cb cb;
                void                         *user;
            } config_polling_image_info;
        
            /* 增加人员信息. */
            struct {
                linkface_add_user_cb   cb;
                void                    *user;
            } config_add_user;
        
            /* 删除人员信息. */
            struct {
                linkface_del_user_cb     cb;
                void                      *user;
            } config_del_user;
        
            /* 查询人员信息. */
            struct {
                linkface_query_user_info_cb cb;
                void                     *user;
            } config_query_user_info;
        
            /* 轮询所有人员信息. */
            struct {
                linkface_polling_user_info_cb cb;
                void                         *user;
            } config_polling_user_info;
        
            /* 聚合接口: 查询端侧人脸总数. */
            struct { 
                linkface_querey_image_count_cb cb; 
                void                                *user; 
            } config_query_image_count; 
        
            /* 聚合接口:  清空所有人脸和底库图片信息. */
            struct {
                linkface_clearall_cb cb;
            } config_clearall;
        
            /* 清空指定人脸分组. */
            struct {
                linkface_clear_usergroup_cb cb;
            } config_clear_usergroup;
        
        } linkface_config_t;
        
        /*
         * SDK 初始化接口.
         */
        int linkface_init(linkface_config_t *cfg);
        
        /*
         * 消息路由接口,源码开放.
         */
        int linkface_message_router(const char *service_name, const int service_name_len, const char *request, const int request_len, char **response, int *response_len);
        
        /** 进程退出前,释放sdk资源。 
         */
        void linkface_destroy();
        
        #ifdef __cplusplus
        }
        #endif
        
        #endif
        
      3. 对接DEMO源码

        #include <unistd.h>
        #include <signal.h>
        #include <stdlib.h>
        #include <stdio.h>
        #include <string.h>
        #include <time.h>
        //填写.h文件的实际路径
        #include "../src/v2/utils/list.h"   
        #include "linkface_sdk_v2.h"
        
        // 固定失败一些入库图片
        #define  FAILED_SOME_PIC
        
        extern int linkkit_destroy();
        extern int linkkit_init(const char *product_key, const char *device_name, const char *device_secret);
        int g_flag_is_running = 1;
        
        void sig_handler(int signo)
        {
            printf("catch the signal SIGINT %d\n",signo);
            g_flag_is_running = 0;
            exit(0);
        }
        
        /* 批量人脸入库回调函数 */
        static int _add_user_batch(const struct list_head *pImageInfoList, int num, int *pErrorCode, void *arg)
        {
            if (!list_empty(pImageInfoList)) 
            {
                LINKFACE_ADD_FACE_IMAGE_LIST_ST  *node = NULL;
                LINKFACE_ADD_FACE_IMAGE_LIST_ST  *next = NULL;
                int cur_index = 0;
                list_for_each_entry_safe(node, next, pImageInfoList, face_list, LINKFACE_ADD_FACE_IMAGE_LIST_ST) 
                {
                #ifdef FAILED_SOME_PIC
                    // 固定第二个错误
                    if (++cur_index == 1){
                        *(pErrorCode + cur_index) = -1;
                        continue;
                    }
                #endif
                    add_image_info((const char *)(&(node->face_image_info))); //add_image_info由设备厂商实现
                    printf("add_image person_id:{%s}, md5:{%s}\n", node->face_image_info.person_id, node->face_image_info.faceimg_md5);         
                }
            }
            return 0;
        }
        
        /*
         * 用户照片删除
         */
        static int _del_image(const linkface_image_info *person, void *arg)
        {
            printf("del face from local database: %s\n", person->person_id);
             
            return del_image_info((const char *)person->person_id); //del_image_info由设备厂商实现
        }
        
        /*
         * 用户信息添加
         */
        static int _add_user(const linkface_user_info *person, void *arg)
        {
            printf("add user to local database: %s, name: %s\n", person->person_id, person->name);
         
            return add_user_info((const linkface_user_info*)person); //add_user_info由设备厂商实现
        }
        
        /*
         * 用户信息删除
         */
        static int _del_user(const linkface_user_info *person, void *arg)
        {
            printf("del user from local database: %s\n", person->person_id);
             
            return del_user_info((const char *)person->person_id); //del_user_info由设备厂商实现
        }
        
        /*
         * 清空人脸库
         */
        static int _clear_all()
        {
            clear_data(); //clear_data由设备厂商实现
            return 0;
        }
        
        /*
         * 清除指定用户分组
         */
        static int _clear_gourp(char * groupid)
        {
            printf("del group face from local database: %s\n", groupid);
            return clear_gourp_face(groupid);   //clear_gourp_face由设备厂商实现
        
        }
        
        /*
         * 底库人脸照片总数查询回调函数
         */
        static int _query_image_base_count(void *arg)
        {
            int count = 0;
            
            count = query_image_count(); //clear_gourp_face由设备厂商实现
        
            printf("query facebase count : %d\n", count);
            return count;
        }
        
        /*
         * 用户信息查询回调函数
         */
        static linkface_user_info *_query_user_info(char *person_id, void *arg)
        {
            linkface_user_info *tmp = NULL;
            int len = sizeof(linkface_user_info);
        
            tmp = (linkface_user_info*)malloc(len);
            if (tmp == NULL) {
                return NULL;
            }
            memset(tmp, 0, len);
        
            query_user_info((const char *)person_id, &tmp); //query_user_info由设备厂商实现
            printf("query user info: %s, name: %s\n", person_id, tmp->name);
        
            if(tmp->timestamp_ms == 0) {
                free(tmp);
                tmp = NULL;
            }
        
            return tmp;
        }
        
        /*
         * 用户照片查询回调函数
         */
        static linkface_image_info *_query_image_info(char *person_id, void *arg)
        {
            linkface_image_info *tmp = NULL;
            int len = sizeof(linkface_image_info);
        
            tmp = (linkface_image_info *)malloc(len);
            if (tmp == NULL) {
                return NULL;
            }
            memset(tmp, 0, len);
        
            query_image_info((const char *)person_id, &tmp);    //query_image_info由设备厂商实现
        
            printf("query face info: %s, md5: %s\n", person_id, tmp->faceimg_md5);
            if(strlen(tmp->faceimg_md5) == 0) {
                free(tmp);
                tmp = NULL;
            }
            return tmp;
        }
        
        /*
         * 用户照片遍历回调函数
         */
        static void _polling_image_base (long long timestamp, void *arg, void (*cb_sdk)(const linkface_image_info *person, void *user), void *user_sdk)
        {
            printf("polling_image_base\n");
            polling_image_info(timestamp, cb_sdk, user_sdk);    //polling_image_info由设备厂商实现
        }
        
        /*
         * 用户信息遍历回调函数
         */
        static void _polling_userinfo_base (long long timestamp, void *arg, void (*cb_sdk)(const linkface_user_info *person, void *user), void *user_sdk)
        {
            printf("polling_userinfo_base\n");
            polling_user_info(timestamp, cb_sdk, user_sdk);     //polling_user_info由设备厂商实现
        }
        
        #define PRODUCT_KEY ""
        #define DEVICE_NAME ""
        #define DEVICE_SECRET ""
        
        int main(int argc, char **argv)
        {
            int ret = 0;
            const char *pk = NULL;
            const char *dn = NULL;
            const char *ds = NULL;
            const char *dir = NULL;
            /*
              alarm_interval < 0 直接退出
              alarm_interval = 0 不上报事件
              alarm_interval > 0 代表上报事件间隔
             */
            int alarm_interval = 0;
            if(argc < 5) {
                printf("use default triples, you can modify it by: ./xxx_demo $pk $dn $ds $path \n");
                pk = PRODUCT_KEY; 
                dn = DEVICE_NAME; 
                ds = DEVICE_SECRET; 
                dir = "./"; 
            } else {
                pk = argv[1]; 
                dn = argv[2]; 
                ds = argv[3]; 
                dir = argv[4]; 
                alarm_interval = atoi(argv[5]);
            }
        
            if (signal(SIGINT,sig_handler) == SIG_ERR) {
                perror("signal errror");
                exit(EXIT_FAILURE);
            }
        
            /* 初始化linkface配置参数。 */
            linkface_config_t cfg;
            memset(&cfg, 0, sizeof(linkface_config_t));
        
            /* 物联网平台三元组信息. */
            strncpy(cfg.triple.product_key, pk, LINKFACE_PRODUCT_KEY_LEN);
            strncpy(cfg.triple.device_name, dn, LINKFACE_DEVICE_NAME_LEN);
            strncpy(cfg.triple.device_secret, ds, LINKFACE_DEVICE_SECRET_LEN);
        
            /* 日志输出配置信息. */
            cfg.cfg_log.lvl = LF_LOG_DEBUG;
            cfg.cfg_log.dst = LF_FILE;
            
            /* 缓存配置信息. */
            cfg.addFaceBatchSize = 10;
            cfg.cfg_cache.logpath = dir;
            cfg.cfg_cache.picpath = dir;
            cfg.cfg_cache.eventpath = dir;
            cfg.cfg_cache.max_size_log_mb = 30;    //日志文件目录100M
            cfg.cfg_cache.max_size_cache_mb = 50;  //缓存图片 500M
            cfg.cfg_cache.max_size_event_mb = 50;
        
            /* 用户需要实现的接口: 增加底库信息. */
            cfg.config_batch_add_image.cb = _add_user_batch;
            /* 用户需要实现的接口: 删除底库信息. */
            cfg.config_del_image.cb = _del_image;
            /* 用户需要实现的接口: 通过id查询某个底库信息*/
            cfg.config_query_image_info.cb = _query_image_info; 
            /* 用户需要实现的接口: 轮询底库信息*/
            cfg.config_polling_image_info.cb = _polling_image_base; 
            /* 用户需要实现的接口: 查询底库总数*/
            cfg.config_query_image_count.cb = _query_image_base_count; 
        
            /* 用户需要实现的接口: 增加用户信息 */
            cfg.config_add_user.cb = _add_user;
            /* 用户需要实现的接口: 删除用户信息 */
            cfg.config_del_user.cb = _del_user;
            /* 用户需要实现的接口: 通过id查询用户信息*/
            cfg.config_query_user_info.cb = _query_user_info; 
            /* 用户需要实现的接口: 轮询用户信息*/
            cfg.config_polling_user_info.cb = _polling_userinfo_base;
          
            /* 用户需要实现的接口: 清空底库. */
            cfg.config_clearall.cb = _clear_all;
        
            cfg.config_clear_usergroup.cb = _clear_gourp;
            /* 确保linkface + linkkit 初始化完成.*/
            while (1) 
            {
                ret = linkkit_init(pk, dn, ds);
                if (ret < 0) 
                {
                    printf("linkkit sdk init failed, retry 1s later.\n");
                    sleep(1);
                    if(g_flag_is_running == 0) 
                    {
                        return 0;
                    }
                    continue;
                }    
                
                ret = linkface_init(&cfg);
                if (ret < 0) 
                {
                    printf("linkface sdk init failed, retry 1s later.\n");
                    linkkit_destroy(); 
                    sleep(1);
                    if (g_flag_is_running == 0) 
                    {
                        return 0;
                    }
                    continue;
                }
            }
        
            return 0;
        }

      5. 单元测试方法

      1. 设备自测,进入设备在线调试界面

        调试页面

      2. 选择同步人脸数据服务

        同步人脸数据

        1. 新增人脸

          {"FaceDataUrl":"https://linkface-doc.oss-cn-shanghai.aliyuncs.com/logdpx-add-2.json"}
        2. 删除人脸

          {"FaceDataUrl":"https://linkface-doc.oss-cn-shanghai.aliyuncs.com/logdpx-del.json"}
        3. 清空人脸

          {"FaceDataUrl":"https://face-biz-2-online.oss-cn-shanghai.aliyuncs.com/05A19AEFC200431B9E1F7C371B754B48/a103B6PNtuKdx2Fr/a15qAuXknvo_lfv_4_MAIN_CURSOR_0_1597213340164.json?Expires=1599805340&OSSAccessKeyId=LTAI2I4clvU02QPN&Signature=hgFXz%2FMr9lOr6CtfoNzVth8Jmko%3D"}
      3. 选择RSA公钥服务

        RSA公钥

        {  "RSAPublicKey": "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAI6zQY57YYUh1xDOk9ZOXQapFpSGmZIukTtJkvM5rf43b4H6tmzH27anYW8csBKmFAdJir9ZaJY1GhZO6FY/8d0CAwEAAQ=="}

        以下二维码是私钥加密的测试二维码

        1. 有效期至:2022年的二维码(明文:ALIF24DD9END8CD3:1661011200, 密文:Pd3fZ3VDviUjZ6dREW1wIeITxKQBAmmxJ1ZRL6kmAfq2RmXGiZBiv8wh39ySPODojwPCpYwsF4Ken5znf/sGtQ==)kkkk4

        2. 已过期的二维码(明文:ALIF24DD9END8CD3:1597939200,密文:XW5qDO45+wJe31/q5mMkx6EZDKf754yg2pg3QhUoSL6VBtwIzUlkKW+NilPC5tXpdlRlI92PNLhjtFlU707l2w==)kkkkk1

      4. 选择同步门禁权限服务

        下载url

        {"permissionUrl":"https://product-info.oss-cn-shanghai.aliyuncs.com/test.json"}

        url为您上传文件在互联网上可直接下载的url地址,文件demo文件内容见下方

        {
            "qrCodePermissionList":[
                {
                    "type":1,
                    "qrCode":"ALIF24DD9END8CD3",
                    "startTime":"1568270392000",
                    "endTime":"1599892792000",
                    "maxScanTimes":600,
                    "maxScanScope":"SHARE"
                },
                {
                    "type":2,
                    "qrCode":"ALIF24DD9END8CD3"
                },
                {
                    "type":3,
                    "qrCode":"ALIF24DD9END8CD3",
                    "startTime":"1568270392000",
                    "endTime":"1599892792000",
                    "maxScanTimes":600,
                    "maxScanScope":"SHARE"
                }
            ]
        }
      5. 二维码通行事件

        数据上报查看

        在物联网平台中,查看设备的物模型数据,可以通过passEvent来进行过滤检索,检查上报的内容和下面的json数据一致

        {"code":0,"message":"success","type":"qrCode","content":"ALIF24DD9END8CD3"}