文档管理中心
我的

开发步骤

DV Engine整体上开发步骤包含三部分,其中UX要求包含基于安全考虑的隐私声明,连接鉴权和可信设备管理,以及对于设备名称的展示,北向接口是由DV Engine实现,三方厂商来进行调用,进行初始化,南向接口是由DV Engine进行定义,三方厂商来实现,最终由DV Engine SDK在解析接收到手机侧发送过来的命令后进行调用,完成整个虚拟化过程。

基本业务交互流程

Camera/Mic业务:

1. 当发现连接完设备后,应用程序可以调用手机侧DV Engine接口使能虚拟Camera/Mic服务。

2. 应用程序通过标准android接口,打开虚拟Camera/Mic,建立数据通道。

3. 设备侧DV Engine SDK接收到打开虚拟Camera的消息,通知设备厂商打开Camera,获取视频流。

4. 设备侧厂商推送视频流,DV Engine SDK将视频流发送到手机侧。

5. 应用程序通过标准android接口,获取到虚拟Camera/Mic数据流进行播放。

虚拟Speaker业务和上述业务流程类似,只不过是手机侧的数据流发送到设备侧在设备侧进行播放,不再进行详述。

UX要求

  1. 隐私声明
    • 权限使用可知(必选)

      手机连接设备后必须给用户明确的提示,设备有显示屏的需要展示已经连接的手机名称,包括但不限于声音、指示灯或者文字等。

    • 权限使用可控(必选)

      手机连接设备后必须提供用户通过相机操作主动断开DV Engine的能力,包括但不限于通过开关、菜单或者按键的选择。

      设备必须提供可以开启或者关闭DV Engine功能的入口,包括但不限于开关、菜单或者按键选择,对于DV Engine默认开关状态,需要在产品资料中说明。

      设备应具备随时中断正在使用中的DV Engine通信能力,包括但不限于通过开关、菜单或者按键关闭,用户一旦选择中断,设备应立即响应生效。

  2. 连接鉴权
    • 鉴权规则(必选)
      1. 连接码鉴权(必选)

        首次连接时,输入连接码鉴权。

        非首次连接时,无需输入连接码鉴权,使用已经鉴权过的设备信息进行鉴权。

        连接码是随机值,没有设置项。

      2. 连接码通知界面(必选)

        首次连接设备,通过连接码进行鉴权。

        1. 屏幕显示连接码(可选)

          如果设备带有显示屏,则在设备上显示连接码。

        2. 语音播报连接码(可选)

          如果设备不带有显示屏,且必须在私密空间内(例如:车内),可以用设备语音播报连接码。

  3. 连接码通知规范
    • 屏幕显示连接码(可选)
      1. 描述区(必选)

        内容包含标题和设备连接码,在设备上全屏显示。

      2. 连接显示界面(必选)

        展示六位数字连接码,为界面的最核心元素。

      3. 引导(可选)

        连接此设备的手机名称。

    • 语音播报连接码(可选)
      1. 播报内容(必选)

        如果设备不带有显示屏,且必须在私密空间内(例如:车内),播报内容必须设备连接码,清晰的提示用户连接码内容。例如:连接码:xxxxxx。

      2. 播报引导语(可选)

        如果设备不带有显示屏,且必须在私密空间内(例如:车内),播报内容应包含引导语提示用户此时将要播报连接码。例如:请在XX设备上输入。

  4. 可信设备管理

    1. 信任列表(必选)

    DV Engine信任列表采用白名单方式。

    2. 可信设备管理

    1. 查询(可选)

      设备提供展示可信设备信息的能力,包括但不限于设备名称信息。

    2. 删除(必选)

    设备提供可以删除可信设备的入口,包括但不限于开关、菜单等。

    删除可信设备时需弹窗提醒用户确认是否删除。

  5. 设备名称展示

    上文中涉及的设备名称展示,建议显示完整手机名称,至少显示手机名称的前8个字节。

北向接口适配

  1. 设备连接和事件处理回调实现
    DV_AGENT_HANDLE g_dvagentHandle = NULL;
    bool g_disconnectState = false;
    
    /* DVAgent设备状态上报 */ 
    void DeviceChange(DV_AGENT_HANDLE handle, const char *deviceId, uint32_t idLen, int state) 
    { 
        /* Asynchronously process 106 state. */ 
        switch (state) {
            case DVAGENT_DEVICE_DETECT_DISCONNECT:
                /* when receive this state, DVAgentDestoryServiceHandle and recreate the handle in another thread */
                g_disconnectState = true;
                break;
            case DVAGENT_DEVICE_CONNECTED:
                /* you can do something when recv the connected status */
                break;
            default:
                break;
        }
        return; 
    } 
    /* DVAgent服务状态上报 */ 
    void DeviceServiceChange(DV_AGENT_HANDLE handle, const char *serviceId, uint32_t idLen, int state) 
    { 
        return; 
    } 
    /* 接收来自DVAgent的数据 */ 
    void DataReceive(DV_AGENT_HANDLE handle, int dataType, char *data, uint32_t dataLen) 
    { 
        return; 
    } 
    /* 接收来自DVAgent鉴权认证过程中的PIN码并显示 */ 
    void PinCode(const char *pinCode, uint32_t codeLen) 
    { 
        return; 
    }
  2. 鉴权文件管理回调实现
    int32_t GetSecureFileSize(const char *filename) 
    { 
        FILE *fp; 
        int32_t size = 0; 
        if (filename == NULL) { 
            printf("GetSecureFileSize param null.\n"); 
            return -1; 
        } 
        printf("Enter GetSecureFileSize, fileName:%s\n", filename); 
        char filenameInBox[256] = {0}; 
        sprintf(filenameInBox, "%s/%s", HICHAIN_FILE_PATH, filename); 
        fp = fopen(filenameInBox, "rb"); 
        if (fp == NULL) { 
            printf("fopen null.\n"); 
            return 0; 
        } 
        if (fseek(fp, 0L, SEEK_END) != 0) { 
            printf("fseek error.\n"); 
            fclose(fp); 
            return -1; 
        } 
        size = ftell(fp); 
        fclose(fp); 
        printf("Leave GetSecureFileSize, size:%d\n", size); 
        return size; 
    } 
    int32_t ReadSecureFile(const char *filename, uint32_t offset, uint8_t *buf, uint32_t len) 
    { 
        FILE *fp; 
        int32_t size = 0; 
        (void)offset; 
        if (filename == NULL || buf == NULL) { 
            printf("ReadSecureFile param null.\n"); 
            return -1; 
        } 
        printf("ReadSecureFile, fileName:%s, len:%d\n", filename, len); 
        char filenameInBox[256] = {0}; 
        sprintf(filenameInBox, "%s/%s", HICHAIN_FILE_PATH, filename); 
        fp = fopen(filenameInBox, "rb"); 
        if (fp == NULL) { 
            printf("fopen null.\n"); 
            return 0; 
        } 
        size = fread(buf, sizeof(uint8_t), (size_t)len, fp); 
        fclose(fp); 
        printf("ReadSecureFile, size:%d\n", size); 
        return size; 
    } 
    int32_t WriteSecureFile(const char *filename, uint32_t offset, uint8_t *buf, uint32_t len) 
    { 
        (void)offset; 
        FILE *fp; 
        size_t size; 
        if (filename == NULL || buf == NULL) { 
            printf("WriteSecureFile param null.\n"); 
            return -1; 
        } 
        printf("WriteSecureFile, filename:%s, len:%d\n", filename, len); 
        char filenameInBox[256] = {0}; 
        sprintf(filenameInBox, "%s/%s", HICHAIN_FILE_PATH, filename); 
        fp = fopen(filenameInBox, "wb"); 
        if (fp == NULL) { 
            printf("fopen null.\n"); 
            return 0; 
        } 
        size = fwrite(buf, sizeof(uint8_t), (uint32_t)len, fp); 
        fflush(fp); 
        fclose(fp); 
        printf("WriteSecureFile, size:%d\n", size); 
        if (size != len) { 
            printf("WriteSecureFile error.\n"); 
            return -1; 
        } 
        return 0; 
    } 
    int32_t RemoveSecureFile(const char *filename) 
    { 
        printf("RemoveSecureFile\n"); 
        return 0; 
    }
  3. 日志函数回调实现
    int32_t DeviceLogHeaderFormat(char *logInfo, size_t length)
    {
        struct timeval tv;
        struct timezone tz;
        struct tm p;
        int result;
        gettimeofday(&tv, &tz);
        localtime_r(&tv.tv_sec, &p);
        result = sprintf_s(logInfo, length - 1, "%02d-%02d %02d:%02d:%02d.%03ld", 1 + p.tm_mon,
            p.tm_mday, p.tm_hour, p.tm_min, p.tm_sec, tv.tv_usec / 1000L);
        return result;
    }
    
    void DeviceLogDebug(const char *tag, const char *funcName, const char *format, ...)
    {
        char logInfo[LOG_BUFFER_LEN + 1] = "";
        uint32_t len = 0;
        va_list stVaList;
        va_start(stVaList, format);
        int32_t ret = memset_s(logInfo, LOG_BUFFER_LEN, 0, LOG_BUFFER_LEN);
        if (ret != 0) {
            return;
        }
    
        uint32_t result = DeviceLogHeaderFormat(logInfo, LOG_BUFFER_LEN);
        if (result < 0) {
            va_end(stVaList);
            return;
        }
    
        len += result;
        result = sprintf_s(logInfo + len, LOG_BUFFER_LEN - len, "[%s-%s-%s]", tag, funcName, "Debug");
        if (result < 0) {
            va_end(stVaList);
            return;
        }
    
        len += result;
        result = vsprintf_s(logInfo + len, LOG_BUFFER_LEN - len, (const char *)format, stVaList);
        if (result < 0) {
            va_end(stVaList);
            return;
        }
        printf("%s\n", logInfo);
        va_end(stVaList);
        return;
    }
    
    void DeviceLogInfo(const char *tag, const char *funcName, const char *format, ...)
    {
        char logInfo[LOG_BUFFER_LEN + 1] = "";
        uint32_t len = 0;
        va_list stVaList;
        va_start(stVaList, format);
        int32_t ret = memset_s(logInfo, LOG_BUFFER_LEN, 0, LOG_BUFFER_LEN);
        if (ret != 0) {
            return;
        }
    
        uint32_t result = DeviceLogHeaderFormat(logInfo, LOG_BUFFER_LEN);
        if (result < 0) {
            va_end(stVaList);
            return;
        }
    
        len += result;
        result = sprintf_s(logInfo + len, LOG_BUFFER_LEN - len, "[%s-%s-%s]", tag, funcName, "Info");
        if (result < 0) {
            va_end(stVaList);
            return;
        }
    
        len += result;
        result = vsprintf_s(logInfo + len, LOG_BUFFER_LEN - len, (const char *)format, stVaList);
        if (result < 0) {
            va_end(stVaList);
            return;
        }
        printf("%s\n", logInfo);
        va_end(stVaList);
        return;
    }
    
    void DeviceLogWarning(const char *tag, const char *funcName, const char *format, ...)
    {
        char logInfo[LOG_BUFFER_LEN + 1] = "";
        uint32_t len = 0;
        va_list stVaList;
        va_start(stVaList, format);
        int32_t ret = memset_s(logInfo, LOG_BUFFER_LEN, 0, LOG_BUFFER_LEN);
        if (ret != 0) {
            return;
        }
    
        uint32_t result = DeviceLogHeaderFormat(logInfo, LOG_BUFFER_LEN);
        if (result < 0) {
            va_end(stVaList);
            return;
        }
    
        len += result;
        result = sprintf_s(logInfo + len, LOG_BUFFER_LEN - len, "[%s-%s-%s]", tag, funcName, "Warn");
        if (result < 0) {
            va_end(stVaList);
            return;
        }
    
        len += result;
        result = vsprintf_s(logInfo + len, LOG_BUFFER_LEN - len, (const char *)format, stVaList);
        if (result < 0) {
            va_end(stVaList);
            return;
        }
        printf("%s\n", logInfo);
        va_end(stVaList);
        return;
    }
    
    void DeviceLogError(const char *tag, const char *funcName, const char *format, ...)
    {
        char logInfo[LOG_BUFFER_LEN + 1] = "";
        uint32_t len = 0;
        va_list stVaList;
        va_start(stVaList, format);
        int32_t ret = memset_s(logInfo, LOG_BUFFER_LEN, 0, LOG_BUFFER_LEN);
        if (ret != 0) {
            return;
        }
    
        uint32_t result = DeviceLogHeaderFormat(logInfo, LOG_BUFFER_LEN);
        if (result < 0) {
            va_end(stVaList);
            return;
        }
    
        len += result;
        result = sprintf_s(logInfo + len, LOG_BUFFER_LEN - len, "[%s-%s-%s]", tag, funcName, "Error");
        if (result < 0) {
            va_end(stVaList);
            return;
        }
    
        len += result;
        result = vsprintf_s(logInfo + len, LOG_BUFFER_LEN - len, (const char *)format, stVaList);
        if (result < 0) {
            va_end(stVaList);
            return;
        }
        printf("%s\n", logInfo);
        va_end(stVaList);
        return;
    }
  4. 注册回调函数并初始化
    #define MAINLOOP_SLEEP 5000
    void InitDvAgentMac(uint8_t *mac, uint32_t len)
    {
        if (mac == NULL) {
            return;
        }
    
        uint32_t i = 0;
        for (; i < len; i++) {
            mac[i] = TEST_MACBYTE;
        }
    }
    
    void DoSomethingWhenChanged()
    {
        if (g_disconnectState) {
            printf("disconnect the device change\n");
            DVAgentDestoryServiceHandle(g_dvagentHandle);
            g_dvagentHandle = DVAgentCreateServiceHandle();
            g_disconnectState = false;
        }
    }
    
    int main()
    {
        DVAgentConfig dvAgentConfig = { { 0 } };
        dvAgentConfig.deviceInfo.vendorID = TEST_VENDOR_ID;
        dvAgentConfig.deviceInfo.connectAllowType = 0x3;
        /* BLE P2P init brMac, CoAP init wifiMac. Pick one of two. */
        InitDvAgentMac(dvAgentConfig.deviceInfo.brMac, DVAGENT_BR_MAC_LEN);
    
        char *deviceID = "BB000DDD92A0A2A346F0B531F278AF06E370F86932CCAFCCC892D68D350F80F8";
        char *deviceName = "SampleDevice";
        dvAgentConfig.deviceInfo.deviceId = deviceID;
        dvAgentConfig.deviceInfo.idLen = strlen(deviceID);
        dvAgentConfig.deviceInfo.deviceName = deviceName;
        dvAgentConfig.deviceInfo.nameLen = strlen(deviceName);
        DVAgentInit(&dvAgentConfig);
    
        DVAgentListener listener = { 0 };
        listener.DeviceChange = DeviceChange;
        listener.DeviceServiceChange = DeviceServiceChange;
        listener.DataReceive = DataReceive;
        listener.PinCode = PinCode;
        listener.GetSecureFileSize = GetSecureFileSize;
        listener.ReadSecureFile = ReadSecureFile;
        listener.WriteSecureFile = WriteSecureFile;
        listener.RemoveSecureFile = RemoveSecureFile;
        listener.StartScanQRCode = StartScanQRCode;
        listener.StopScanQRCode = StopScanQRCode;
        listener.logGroup.logD = DeviceLogDebug;
        listener.logGroup.logE = DeviceLogError;
        listener.logGroup.logI = DeviceLogInfo;
        listener.logGroup.logW = DeviceLogWarning;
        DVAgentRegisterListener(&listener);
        g_dvagentHandle = DVAgentCreateServiceHandle();
    
        while (1) {
            usleep(MAINLOOP_SLEEP);
            DoSomethingWhenChanged();
        }
        return 0;
    }

发现连接南向接口适配

BLE适配

  1. 实现原理

    靠近发现连接采用BLE发现。DV Engine SDK通过BLE扫描手机广播,解析后回复反向广播,手机通过解析反向广播内容显示设备,用户根据设备名字选择设备,点击连接建立BLE连接通道。

  2. 交互流程

    DV SDK与BLE driver(Linux南向)交互时序图

    说明
    1. DV Engine SDK调用设备南向适配层Init函数开启蓝牙,保证扫描和广播接口能顺利调用,并启动GATT服务。
    2. DV Engine SDK调用设备南向适配层RegisterChannelCallback函数注册ChannelStateChange、ScanCallback和ReceiveData。
    3. DV Engine SDK调用设备南向适配层GetParam函数获取BLE设备名称。
    4. DV Engine SDK调用DV Engine SDK BLE发现,DV Engine SDK调用设备南向适配层StartScan函数开启BLE扫描。
    5. 设备南向适配层收到手机侧广播后调用ScanCallback上报扫描信息。
    6. DV Engine SDK处理扫描信息后调用设备南向适配层StartAdv函数发反向广播。
    7. 手机侧收到设备侧反向广播后发送连接请求,设备南向适配层BLE连接成功后调用ChannelStateChange上报EVENT_CONNECT,DV Engine SDK调用设备南向适配层StopScan和StopAdv停止扫描和停止广播。
    8. BLE通道数据交互时,设备南向适配层收到手机侧发送的数据调用ReceiveData,将手机侧数据上报给DV Engine SDK。
    9. BLE通道数据交互时,DV Engine SDK调用设备南向适配层NotifyData函数将数据发给手机侧。
    10. 在退出DV Engine SDK服务时,DV Engine SDK调用设备南向适配层Deinit关闭GATT服务。

WLAN适配

  1. 实现原理

    DV Engine SDK通过BLE通道进行信道协商。在信道协商完成后创建GO,将GC信息经由BLE通道发送到手机。手机在收到设备的GC信息后连接设备,当手机连接GO成功后,WLAN Mac层链路打通。

  2. 交互流程

    DV Engine SDK与WLAN driver(Linux南向实现)交互时序图

    说明
    1. DV Engine SDK调用设备南向适配层Init函数初始化WLAN信息。
    2. DV Engine SDK调用设备南向适配层RegisterChannelCallback函数注册ChannelStateChange。
    3. DV Engine SDK调用设备南向适配层GetParam函数获取设备的WLAN能力。
    4. 设备南向适配层通过BLE通道收到手机侧数据后调用BLE通道的ReceiveData回调,将手机侧数据上报给DV Engine SDK。
    5. DV Engine SDK调用设备南向适配层CreateWifiChannel创建GO,南向适配层创建GO成功后调用ChannelStateChange上报EVENT_CREATE_WIFI_SUCCESS。
    6. DV Engine SDK调用设备南向适配层SetParam设置南向IP,南向适配层成功后调用ChannelStateChange上报EVENT_CONFIG_IP_SUCCESS。
    7. DV Engine SDK创建P2P控制通道并发送P2P信息给手机侧。
    8. 手机侧收到P2P信息发起连接。
    9. 在退出Nearby服务时,DV Engine SDK调用设备南向适配层Deinit移除GO。

DV Engine南向接口适配

  1. DV Engine南向接口适配

    DV Engine需要采集智能设备的设备类型、设备支持的服务能力及设备能力的状态(当前是否占用)。DV Engine采集智能设备的设备类型、设备支持的服务能力及设备能力的状态,智能设备厂商需实现对应libdmsdpdevice.so,完成获取设备类型、设备服务能力及设备能力状态等接口,供DV Engine调用。

  2. 设备Camera虚拟化

    将智能设备上的Camera硬件虚拟化,通过DV Engine的分布式能力,使用Camera硬件。此类接口用于定义DV Engine对智能设备Camera硬件的调用接口,智能设备厂商需实现对应的libdmsdpcamerahandler.so。如果智能设备厂商不具备Camera硬件或者不需要将Camera硬件虚拟化,不需要关注该接口,默认不实现此so即可。

  3. 设备Audio虚拟化

    将智能设备上的Speaker和Mic硬件虚拟化,通过DV Engine的分布式能力,使用Speaker和Mic硬件。此类接口用于定义DV Engine对智能设备Audio(Speaker/Mic)硬件的调用接口,智能设备厂商需实现对应的libdmsdpaudiohandler.so。如果智能设备厂商不具备Audio(Speaker/Mic)硬件或者不需要将Audio(Speaker/Mic)硬件虚拟化,不需要关注该接口,默认不实现此so即可。

  4. 音视频编解码接口(可选)

    本章节提供了编解码接口定义,智能设备厂商根据业务选择性集成实现。对于音视频硬件编解码的调用接口,接口需要智能设备厂商在对应的so中实现。智能设备厂商不提供功能实现,不需要实现对应so即可。

    • 视频编码器:libdmsdpvideoencoder.so
    • 音频编码器:libdmsdpaudioencoder.so
    • 音频解码器:libdmsdpaudiodecoder.so