3.2 自定义协议驱动开发指导

更新时间:

概述

本文档指导自定义协议驱动开发人员,了解物联网设备接入相关知识,并且可以根据设备接入SDK开发驱动,实现设备物模型定义的功能。

1.了解物联网设备接入

1.1 设备接入概念

设备接入是Link IoT Edge提供的基础能力,设备接入模块在Link IoT Edge中称为驱动(driver)或设备接入驱动。所有连接到Link IoT Edge的设备都需要通过驱动实现接入。

设备接入驱动在Link IoT Edge框架位置如图所示。

设备接入框架图

1.2 设备接入驱动

1.2.1 设备接入驱动的功能组成

一个完整的驱动(设备接入模块)由设备的连接管理、设备的数据(协议)转换和设备的数据与命令处理三个模块组成。

1.2.1.1连接管理

指设备与网关建立通信连接。Link IoT Edge不限制建立通信连接的协议,可根据业务需求灵活选择。

1.2.1.2数据转换

指设备接入驱动将获取到的终端设备数据转换为符合阿里云IoT物模型规范的数据格式,并上报到阿里云IoT Cloud。阿里云物联网平台物模型规范请参考物模型

1.2.1.3数据与命令处理

指驱动可以处理云端对于设备的操作请求,并完成对设备的服务调用和处理调用结果,最终将结果返回到阿里云物联网平台。

1.2.2 设备接入驱动的开发工作

设备接入驱动是Link IoT Edge中独立的服务模块,您可以根据业务协议需求开发自定义设备接入驱动。下图展示了自定义驱动的功能和数据流向,并指出了开发一个自定义驱动需要做的开发工作。

IoT框架

2.驱动开发

Link IoT Edge提供设备接入SDK(Link Edge Device Access,简称LEDA)方便您开发自己的驱动,SDK目前支持C、Node.js、Java和Python四种版本的语言。

2.1 LEDA接口介绍

LEDA支持的四种开发语言详细内容,请参见CNode.jsJavaPython

虽然SDK版本不同,但提供的功能是一样的,LEDA接口调用关系如下图所示:

image

2.2 驱动编码示例

在驱动开发过程中进行驱动编码时,需遵循物联网边缘计算的驱动编码规范和步骤。

2.2.1 驱动和设备配置

进行驱动编码前,需要了解Link IoT Edge的设备信息配置和驱动信息配置相关内容。

2.2.1.1 驱动配置

驱动信息配置在阿里云物联网平台进行配置。部署边缘实例时,驱动配置信息会被部署到边缘网关,其内容以JSON格式存储在Link IoT Edge配置中心,可以通过leda_get_driver_info接口获取。

驱动信息配置为JSON格式:

{
    "json":{
        "ip":"127.0.0.1",
        "port":54321
    }
}

格式参数说明如下:

参数名称

说明

json

驱动配置的格式为JSON格式配置。配置内容为自定义内容。

驱动配置示例:

image

2.2.1.2 设备配置

设备信息配置在阿里云物联网平台控制台配置。部署边缘实例时,设备信息配置会被部署到边缘网关,其内容以JSON格式存储,可以通过leda_get_device_info接口获取。

设备信息配置格式定义:

{
    "deviceList": [{
        "custom": {
             "ip":"127.0.0.1",
             "port":22322
        }, // 设备自定义配置
        "productKey": "xxxxxxxxxxx", // 产品ProductKey,在创建产品时生成
        "deviceName": "demo_led",    // 设备DeviceName,在创建设备时设置
    }]
}

设备信息配置参数说明:

配置名称

配置解释

deviceList

当前驱动下所有已进行设备配置的设备列表。

custom

设备自定义配置。

productKey

设备所在产品唯一标识符。

deviceName

设备名称。

实际设备配置示例:

image

2.2.2 驱动代码示例

完成驱动编码,可参考以下4个示例。本示例的完整工程源码请参见Github源码库LED设备驱动

2.2.2.1 初始化驱动资源

调用leda_init接口完成资源初始化。

int main(int argc, char** argv)
{
    ...
 
    /* init driver */
    if (LE_SUCCESS != (ret = leda_init(WORKER_THREAD_NUMS)))
    {
        log_e(TAG_NAME_LED_DRIVER, "leda_init failed\n");
        return ret;
    }
 
    ...
 
  return LE_SUCCESS;
}

2.2.2.2 解析驱动配置,完成设备上线

通过调用leda_get_driver_info接口获取驱动配置,解析设备的连接信息,并根据解析结果连接设备。设备连接成功后,调用leda_get_device_info接口获取设备配置并解析,根据解析结果验证设备功能。功能验证通过后,调用leda_register_and_online_by_device_name接口完成设备注册并上线到阿里云物联网平台

static int online_devices()
{
  ...
 
    /* 获取驱动和设备配置 */
    size = leda_get_device_info_size();
    if (size >0)
    {
        device_config = (char*)malloc(size);
        if (NULL == device_config)
        {
            log_e(TAG_DEMO_LED, "allocate memory failed\n");
            return LE_ERROR_INVAILD_PARAM;
        }
 
        if (LE_SUCCESS != (ret = leda_get_device_info(device_config, size)))
        {
            log_e(TAG_DEMO_LED, "get device config failed\n");
            return ret;
        }
    }
 
    /* 解析驱动和设备配置 */
    devices = cJSON_Parse(device_config);
    if (NULL == devices)
    {
        log_e(TAG_DEMO_LED, "device config parser failed\n");
        return LE_ERROR_INVAILD_PARAM;
    }
 
    cJSON_ArrayForEach(item, devices)
    {
        if (cJSON_Object == item->type)
        {
            /* 解析配置内容 */
            result      = cJSON_GetObjectItem(item, "productKey");
            productKey  = result->valuestring;
 
            result      = cJSON_GetObjectItem(item, "deviceName");
            deviceName  = result->valuestring;
 
            result      = cJSON_GetObjectItem(item, "custom");
            if (NULL != result)
            {
                log_i(TAG_DEMO_LED, "custom content: %s\n", cJSON_Print(result));
            }
 
            /* 注册并上线设备 */
            device_cb.get_properties_cb            = get_properties_callback_cb;
            device_cb.set_properties_cb            = set_properties_callback_cb;
            device_cb.call_service_cb              = call_service_callback_cb;
            device_cb.service_output_max_count     = 5;
 
            dev_handle = leda_register_and_online_by_device_name(productKey, deviceName, &device_cb, NULL);
            if (dev_handle < 0)
            {
                log_e(TAG_DEMO_LED, "product:%s device:%s register failed\n", productKey, deviceName);
                continue;
            }
 
            g_dev_handle = dev_handle;
            log_i(TAG_DEMO_LED, "product:%s device:%s register success\n", productKey, deviceName);
        }
    }
 
  ...
 
    return LE_SUCCESS;
}

2.2.2.3 上报数据

将收到的设备数据转换为阿里云IoT物模型格式并上报到物联网平台,调用leda_report_properties接口上报设备属性数据,调用leda_report_event接口上报设备事件。

/* 上报数据 */
while (1)
{
    /* 上报属性 */
    leda_device_data_t dev_proper_data[1] =
    {
        {
            .type  = LEDA_TYPE_INT,
            .key   = {"temperature"},
            .value = {0}
        }
    };
    sprintf(dev_proper_data[0].value, "%d", g_dev_temperature);
    leda_report_properties(g_dev_handle, dev_proper_data, 1);
 
    /* 上报事件 */
    if (g_dev_temperature > 50)
    {
        leda_device_data_t dev_event_data[1] =
        {
            {
                .type  = LEDA_TYPE_INT,
                .key   = {"temperature"},
                .value = {0}
            }
        };
        sprintf(dev_event_data[0].value, "%d", g_dev_temperature);
        leda_report_event(g_dev_handle, "high_temperature", dev_event_data, 1);
    }
 
    sleep(5);
}

2.2.2.4 处理云端服务请求

实现服务请求的三个回调函数如下所示。

get接口:处理获取设备属性的请求。

set接口:处理设置设备属性的请求。

service接口:处理调用设备自定义方法的请求。

static int get_properties_callback_cb(device_handle_t device_handle,
                               leda_device_data_t properties[],
                               int properties_count,
                               void *usr_data)
{
    ...
 
    return ret;
}
 
static int set_properties_callback_cb(device_handle_t device_handle,
                               const leda_device_data_t properties[],
                               int properties_count,
                               void *usr_data)
{
    ...
 
    return ret;
}
 
static int call_service_callback_cb(device_handle_t device_handle,
                               const char *service_name,
                               const leda_device_data_t data[],
                               int data_count,
                               leda_device_data_t output_data[],
                               void *usr_data)
{
    ...
 
    return ret;
}

2.3 驱动产出方式

2.3.1 驱动依赖注意事项

设备接入驱动根据协议和业务场景的不同,可能会涉及第三方库依赖。Link IoT Edge针对开发设备接入驱动所用不同开发语言,分别制定了第三方库依赖规则。

  • C版本SDK:C语言属于编译型语言,如果编译目标环境和运行环境不一致,则很可能导致不可运行。所以对于使用设备接入C版本SDK开发驱动,需要保证开发编译目标环境和运行环境相同。 驱动包中包含驱动程序和依赖动态库。如果该驱动依赖于第三方库,则需要将动态库和驱动程序一起打包生成最终驱动程序包。

  • Node.js版本SDK:使用设备接入SDK Node.js版本开发驱动时,若依赖第三方库,需要到Link IoT Edge运行环境上开发驱动,并在驱动目录中使用如下命令安装依赖。

npm install 第三方库名
  • Python版本SDK:使用设备接入SDK Python版本开发驱动时,若依赖第三方库,需要到Link IoT Edge运行环境上开发驱动,并在驱动目录中使用如下命令安装依赖。

pip3 install -t . 第三方库名

2.3.2 驱动打包方式

基于Link IoT Edge提供的SDK开发驱动并完成调试后,需将产物打包为.zip包,并确保驱动Binary或index源文件在.zip包第一级目录。

每个版本SDK开发的驱动在打包时,有不同的打包规则。

  • 基于C SDK开发的驱动对于C语言开发的驱动,驱动包中包含驱动程序和驱动依赖的动态库。如果驱动程序包含依赖库,则需要将依赖库放置指定的位置,即在驱动程序当前路径下的lib文件夹下。具体操作步骤如下:

    1. 规定驱动程序需命名为main。

    2. 在main当前路径下创建lib文件夹。

    3. 将main依赖的动态库全部拷贝到lib文件夹下。

    4. 使用zip命令对当前路径下的main和lib进行压缩处理生成zip包。

zip -r your_driver_name.zip main lib
  • 基于Python SDK开发的驱动包文件中须包含index.py,并且在该文件中定义handler函数。驱动是一个在函数计算应用引擎中持续运行的函数,所以在驱动包中须包含index.py文件,并且在该文件中定义handler函数。 驱动运行时,会加载index.py文件。而该文件中,函数计算应用定义的handler函数是不会被调用,因此驱动代码须放在handler函数外,保证加载index.py文件时能直接执行。详情请参考Python版本SDK

  • 基于Node.js SDK开发的驱动包文件中须包含index.js,并且在该文件中定义handler函数。 驱动运行时,会加载index.js文件。而该文件中,函数计算应用定义的handler函数是不会被调用,因此驱动代码须放在handler函数外,保证加载index.js文件时能直接执行。详情请参考Nodejs版本SDK

2.4 驱动使用示例

2.4.1 上传及发布驱动

  • 在SI集成工作台si.iot.aliyun.com上,选择边缘接入->驱动管理,选择新增驱动

    根据提示设置参数,需要注意的是通信协议类型要选择【自定义】

imageimageimage

  • 上传已完成编译并打包的led_driver.zip文件。

  • 完成参数的设置并上传成功驱动文件后,单击确定。您可以在自研驱动列表中看到刚刚创建的驱动

2.4.2分配驱动和设备到边缘实例

2.4.2.1 添加驱动到边缘实例

  • 登录SI集成工作台si.iot.aliyun.com,在网关管理设备集成页面,点击网关右侧的设置图标,首先,在弹出菜单中选择添加协议

image
  • 然后,在下拉选择的协议列表中选择“DemoDriver”,选择“确定”。

image

2.4.2.2 新建设备

  • 如下图所示,通过点击通道右侧的“设置”图标,选择“新建设备”

image
  • 在弹出的新建设备页面中按照如下方式选择刷卡门禁

image
  • 点击确定按钮即可完成设备创建,创建好的设备如下如所示:

image

2.4.3 部署边缘实例

  • 实例详情页面,右上角单击部署,部署边缘实例。部署成功后边缘实例名称后显示部署成功

image

2.4.4 查看驱动日志

在边缘网关宿主机命令行中通过docker命令进入LE网关运行环境,命令如下:

sudo docker exec -it $(sudo docker ps | grep k8s_linkedge  | awk '{print $1}') bash

进入网关容器内之后可以在本地查看驱动日志。

a. 通过./fctl show命令,可以查看到部署驱动的具体位置。

cd /linkedge/gateway/build/bin/
./fctl show

系统显示类似如下图所示:图1

参数说明:

字段名称

字段解释

DriverName

驱动名称,该名称为在上传及发布驱动步骤中设置的驱动名称。

CodePath

驱动部署到边缘网关中的位置路径。

Process PID

驱动启动后的进程ID。

b. 驱动在运行过程中会产生运行日志,通过查看运行日志可以了解驱动运行状态,每个自定义驱动生成的日志文件统一放在/linkedge/run/logger/fc-base路径下。每个驱动日志文件路径为/linkedge/run/logger/fc-base/xxxx/log_xxxx.txt。

说明驱动日志文件路径中的xxxx为在上传驱动时填写的名称。

cd /linkedge/run/logger/fc-base/demo_driver && ls -l

详情如下图所示:详情

驱动运行日志可以查看大部分驱动的运行状态,但是有时还需要查看其它日志配合了解当前驱动的运行情况,例如设备上线异常时可以查看dimu日志,日志文件路径为/linkedge/run/logger/dimu/log_xxxxx.txt。

cd /linkedge/run/logger/dimu
ls -l

详情如下图所示:详情2

设备上线成功后,如果设备数据上报有异常,则可以查看cloud_proxy日志。cloud_proxy产生的运行日志文件路径格式为/linkedge/run/logger/cloud-proxy/log_xxxxx.txt。

cd /linkedge/run/logger/cloud-proxy
ls -l

详情如下图所示:详情3

2.5 驱动调试方式

2.5.1 云端在线调试

如果设备上线成功,则可以使用阿里云物联网平台在线调试功能调试驱动和设备,该功能页面还可以实时查看设备上报的数据信息,也可以触发对设备服务的调用请求。

a.物联网平台控制台,选择监控运维 > 在线调试,在在线调试页面,选择调试产品和调试设备。

b.选择调试设备待调功能和服务,进行调试,并查看设备实时运行日志在线调试

2.5.2 边缘端替换驱动

如果在驱动调试过程中发现问题,需要修改驱动代码重新生成新的驱动进行调试,这时只需要在本地替换修改编译后的驱动即可。

a. 找到驱动的位置。可以通过fctl命令进行查找,具体字段解释查看调试信息。

cd /linkedge/gateway/build/bin/
./fctl show
详情3

b. 根据CodePath找到驱动所在位置路径,完成替换。

c. 根据Process PID得到驱动进程ID,使用kill -9 Pid命令重新启动驱动。

kill -9 Pid #Pid为驱动进程ID,即通过fctl命令查看得到的Process PID