分类 边缘计算网关 下的文章

我们都知道蓝牙BLE设备只能与蓝牙相关设备通信,如果需要将蓝牙BLE设备接入物联网平台,那需要通过一些技术手段来实现协议和数据的转换。本文章,就是为这个解决方案提供一个指引。

准备工作

设备和软件相关

  • 蓝牙BLE广播设备,可以是一个iBeacon,这里用 YiTHE 人体温度计为例;
  • 蓝牙BLE网关,可以是一个通用的蓝牙网关或可以二次开发的网关;
  • ThingsBoard Gateway,需要二次开发;

蓝牙网关需要跟蓝牙设备之间已经建立连接和配合,具体配置要看蓝牙设备的协议是否被网关支持。

数据走向

  1. 蓝牙设备持续广播,将数据发送出来:02010613ff11aae6000055444757543035000000000000;
  2. 网关设备持续扫描广播,并解析广播数据,比如 YiTHE ;
  3. 蓝牙网关将数据发送到 ThingsBoard Gateway;
  4. ThingsBoard 端需要针对 iBeacon 数据解析,并入库。

协议转换

蓝牙网关端

为了区分自有设备,过滤掉其他厂家设备,需要在网关处开发过滤规则:

  • 根据广播数据长度过滤不相关数据;
  • 根据数据内容关键字过滤;

ThingsBoard 端

为了保证数据完整性,我们将原始数据直接发送到 TB ,然后,在规则链 中过滤并且分析数据,写入:

adv_data = msg.adv_data;

// 电池
battery_str = adv_data.substr(20, 2);
hex = "0x" + battery_str;
charValueBattery = String.fromCharCode(hex);
battery = charValueBattery.charCodeAt(0);
msg.battery = battery;

// 温度
temp_symbol = adv_data.substr(10, 2);
if (temp_symbol == "00") {
    symbol = -1;
} else if (temp_symbol == "11") {
    symbol = 1;
}
temp1_str = adv_data.substr(16, 2);
temp2_str = adv_data.substr(14, 2);
hex = "0x" + temp1_str + temp2_str;
charValueTemp = String.fromCharCode(hex);
temp = charValueTemp.charCodeAt(0);
msg.temp = symbol * temp / 10.0;

// 温度单位 摄氏度或华氏度
temp_unit_str = adv_data.substr(12, 2);
if (temp_unit_str == "aa") {
    unitCode = "C";
} else if (temp_unit_str == "bb") {
    unitCode = "F";
}
msg.temp_unit = unitCode;

// 按键标志
pressed_str = adv_data.substr(18, 2);
if (pressed_str == "01") {
    pressed = true;
} else if (pressed_str == "00") {
    pressed = false;
}
msg.pressed = pressed;

// 设备id
devcie_id_str = adv_data.substr(34, 12);
hex = "0x" + devcie_id_str;
charValueDeviceId = String.fromCharCode(hex);
device_id = charValueDeviceId.charCodeAt(0);
msg.device_id = device_id;

// 设备标志位
var result = [];
device_flag_str = adv_data.substr(22, 12);
for (var i = 0; i < device_flag_str.length / 2; i++) {
    var aa = device_flag_str.slice(i * 2, (i + 1) * 2);
    result.push(aa);
}

var device_flag = "";
for (var i = 0; i < result.length; i++) {
    device_flag += String.fromCharCode(parseInt(result[i],
        16));
}
msg.device_flag = device_flag;

return {
    msg: msg,
    metadata: metadata,
    msgType: msgType
};

需要解析数据,并写入msg,达到入库的作用。

{
    "msg": {
        "adv_data": "02010613ff11aae6000055444757543035000000000000",
        "battery": 85,
        "temp": 23,
        "temp_unit": "C",
        "pressed": false,
        "device_id": 0,
        "device_flag": "DGWT05"
    },
    "metadata": {
        "deviceType": "default",
        "deviceName": "Test Device",
        "ts": "1618149679239"
    },
    "msgType": "POST_TELEMETRY_REQUEST"
}

结果呈现

设备列表

yithe2.png

设备日志

yithe.png

视频信息如何作为物联网管理的一部分,一直是我们公司比较关注的一项内容,哪些内容应该被纳入物联网管理范畴?通过这篇文章,我们一起来探讨一下。

准备工作

  • 视频直播设备,比如:IPC 网络摄像机,或者可以模拟推流的ffmpeg,IPC 拍摄视频,自定义码流(比如500kbit/s),自定义分辨率(1080P),标准协议(RTMP);
  • Wi-Fi 或 有线网络是比较容易的选择,有4G当然更加灵活,这个根据具体需求来确定选择哪类传输方式;
  • 流媒体服务器,比如:YiMEDIA,可以实现流媒体服务支持,推流/拉流,状态查询;
  • 物联网插件,具备从流媒体服务器获取IPC信息,流信息,并且与物联网服务器交互,比如:YiCONNECT;

过程剖析

IPC 端

  • 首先,必须是 IPC,这样才能通过网络推流;
  • 其次,具备 RTMP 推流协议,如果只是支持 RTSP,需要一个转换器把 RTSP流 转换成 RTMP流 推送出去;
  • 再次,编码类型H264,分辨率看摄像头,一般都可以1080P,码流至少要达到 500kbps 以上,否则不一定能推流成功,且视频不清晰;
  • 再一次,如果需要音频上传,需要用 aac 编码;
  • 最后,成功推流。如果不行,建议咨询 IPC 厂家。

YiMEDIA 端

流媒体服务器,这里以我司 YiMEDIA 为基础,简单介绍一些。

  • 流媒体服务器开启 RTMP 通道,一般是 TCP 1935 端口号;
  • 开启推流验证,如果验证不通过,就断开推流客户端的连接,可以用随机数的方式,YiMEDIA 才有回调应用服务器确认连接安全性;
  • 推流/拉流 URL 建议采用: rtmp://[IPADDRESS]/[IPC]/[auth_key],或 rtmp://[IPADDRESS]/[IPC]/[MAIN/SUB]?key=[auth_key] ;
  • YiMEDIA 开启 NVR/DVR 功能,记录所有的视频信息;

YiCONNECT 端

为了将视频流数据状态和信息发送到 YiCONNECT,需要开发一个插件,获取 流信息 后,以 Thingsboard 能接收的方式发送。

  • 编写 Thingsboard gateway 应用程序,以便支持 视频流 信息的转发和解析;
开启 rest.json 服务
编写 YiMEDIA 丢过来的回调请求回应信息
  • 解析 [IPC] 和 [auth_key] 字段,并验证 auth_key 是否满足要求;
  • YiCONNECT 端新建 Gateway 网关设备,保持验证码一致;

结果预览

YiMEDIA 状态查看

  • 客户端状态
    clients.png
  • 流状态
    streams.png

YiCONNECT

  • 设备列表表
    devices.png
  • 视频流列表
    videos.png

图像预览

可以通过 VLC 播放器播放:

    1. rtmp 流: rtmp://ipaddress/ON400H16DM0FZ20210308002/high,基本处于实时播放,1-3秒
    2. flv 格式: http://ipaddress/ON400H16DM0FZ20210308002/high.flv,准实时播放,3-5秒
    3. m3u8 格式:http://ipaddress/ON400H16DM0FZ20210308002/high.m3u8,延时比较大,几秒到几十秒
    • 4G 摄像头抓图
      4g.png
    • 半夜红外线开启抓图
      midnight.jpg

    方案详解

    YiCAMERA-solution.png

    此次移植工作以中国移动 ML302 模组为基础,亿琪软件 YiDTU 设备为原型,开发基于 Modbus Master 应用;
    传统模式下,DTU 都是以透传为主要工作,所有的业务需求都是从服务器端发出,而 YiDTU 是作为边缘计算的角色,在工业现场就已经把数据处理做完;

    yigate-yidtu.png

    准备工作

    硬件设备

    • YiDTU 设备一台
      亿琪软件 YiDTU 设备内置 UART 转 RS485 模块,对外连接 RS485 双线设备,如:YiSensor 模拟量采集模块;对于开发而言,那就是主要针对 UART 串口操作;
    • Micro USB 线一根
      用于操作 YiDTU 设备:AT 串口操作,烧录操作;
    • RS485 设备若干
      可用 USB 转 RS485 转换器代替(需配合 modbus slave 软件使用);有支持 Modbus Slave 设备最好;准备了两个:模拟量 4-20mA 和 开关量各一个;
    • 9~30V 直流电源一个
      支持最大 2A 电流的直流电源一个,用于给 YiDTU 设备供电;
    • 将以上硬件设备连接好

    软件 SDK

    • 从中国移动物联网公司获取 ML302 相关 SDK,;
    • 确保 SDK 编译正常;
    • 烧录基本应用成功;

    移植过程

    Modbus Master 库

    • https://github.com/jiekechoo/ModBus-Master.git 获取源代码,使用 STM32 环境编译成功,确保你的环境没问题;
    • 将 Modbus_Master 目录移植到 ML302 工程中 src/demo/modbus 下,根据需求删除不必要的组件功能;
    • ML302 环境下编译通过,确保没有错误出现;
    • Modbus 枚举值要重新定义
    typedef enum
    {
      // Modbus function codes for bit access
      ku8MBReadCoils = 0x01,          ///< Modbus function 0x01 Read Coils
      ku8MBReadDiscreteInputs = 0x02, ///< Modbus function 0x02 Read Discrete Inputs
      ku8MBWriteSingleCoil = 0x05,    ///< Modbus function 0x05 Write Single Coil
      ku8MBWriteMultipleCoils = 0x0F, ///< Modbus function 0x0F Write Multiple Coils
    
      // Modbus function codes for 16 bit access
      ku8MBReadHoldingRegisters = 0x03,      ///< Modbus function 0x03 Read Holding Registers
      ku8MBReadInputRegisters = 0x04,        ///< Modbus function 0x04 Read Input Registers
      ku8MBWriteSingleRegister = 0x06,       ///< Modbus function 0x06 Write Single Register
      ku8MBWriteMultipleRegisters = 0x10,    ///< Modbus function 0x10 Write Multiple Registers
      ku8MBMaskWriteRegister = 0x16,         ///< Modbus function 0x16 Mask Write Register
      ku8MBReadWriteMultipleRegisters = 0x17 ///< Modbus function 0x17 Read Write Multiple Registers
    } modbusFunc;

    串口接收支持

    • 串口接收任务中,将收到的内容复制到全局变量中,目的是把 Modbus Slave 回复的内容获取到,交给 Modbus 组件使用;
    extern uint8_t u8ModbusADU[1024];
    extern uint8_t u8ModbusADUSize;
    
    // 接收处理函数中
    {
                u8ModbusADUSize = uart_buf_len;
                memcpy(u8ModbusADU, uart_buf, u8ModbusADUSize);
    }
    • 接收完成后,创建一个 信号量,通知 Modbus 组件来处理;
    {
                    uart_buf_len = 0;
                    recv_count = 0;
                    osSignalSet(OC_Main_TaskHandle, 0x0004);
    }

    Modbus 组件

    • 将 Modbus 组件中接收处理替换成信号量等待,超时重置接收缓冲区;
      // 串口收到数据信号量,2000ms 超时
      osEvent osevent = osSignalWait(0x0004, ku16MBResponseTimeout);
      if (osevent.status == osEventTimeout)
      {
        u8MBStatus = ku8MBResponseTimedOut;
        memset(u8ModbusADU, 0, sizeof(u8ModbusADU));
        u8ModbusADUSize = 0;
      }
    • Modbus 发送函数重写
    uint8_t Modbus_Master_Write(uint8_t *buf, uint8_t length)
    {
      //  if(HAL_UART_Transmit(&huart2 ,(uint8_t *)buf,length,0xff))
      if (cm_uart_send_no_cache(OPENCPU_MAIN_URAT, (uint8_t *)buf, length, 0xff) == 0)
      {
        return HAL_ERROR;
      }
      else
      {
        return HAL_OK;
      }
    }
    • 如果有 CS 控制口 IO,需要自定义相关函数
    #if 0
      // transmit request RS485接口是需要每次发送前改变接口的模式
      
      if (_preTransmission)
      {
        _preTransmission();
      }
    #endif
    
      //串口发送数据
      Modbus_Master_Write(u8ModbusADU, u8ModbusADUSize);
      u8ModbusADUSize = 0;
    
    #if 0
    // transmit request RS485接口是需要每次发送后改变接口的模式
          if (_postTransmission)
      {
        _postTransmission();
      }
    #endif

    移植过程比较简单,细节需要一定的嵌入式和 C 语言基础。

    效果展示

    在 YiDTU 业务逻辑中增加相应的功能模块,把数据传输到云平台,这样,就可以及时获取现场的模拟量数据。
    PSX_20210119_151717.jpg

    Google Protocol Buffers ,一个非常高效的数据序列化工具,ava/Python/C++/C#等都是完美支持,可惜了,官方不支持C语言,还好世界强大,有人做了C语言版本:https://github.com/protobuf-c/protobuf-c

    关于protobuf,请自行google或百度查询,这里不再啰嗦,进入正题。

    1. 第一步:安装Protocol Buffers

    https://developers.google.com/protocol-buffers/
    很简单,download下来,编译即可;make && make install

    2. 第二步:安装protobuf-c

    https://github.com/protobuf-c/protobuf-c
    也是很简单,download下来,编译即可;make && make install

    当你输入 protoc-c 出现以下信息,代表安装成功。

    jiekechoo:mqtt_protoc jiekechoo$ protoc-c 
    Usage: protoc-c [OPTION] PROTO_FILES
    Parse PROTO_FILES and generate output based on the options given:
      -IPATH, --proto_path=PATH   Specify the directory in which to search for
                                  imports.  May be specified multiple times;
                                  directories will be searched in order.  If not
                                  given, the current working directory is used.
                                  If not found in any of the these directories,
                                  the --descriptor_set_in descriptors will be
                                  checked for required proto file.
      --version                   Show version info and exit.
      -h, --help                  Show this text and exit.
      --encode=MESSAGE_TYPE       Read a text-format message of the given type
                                  from standard input and write it in binary
                                  to standard output.  The message type must
                                  be defined in PROTO_FILES or their imports.
      --decode=MESSAGE_TYPE       Read a binary message of the given type from
                                  standard input and write it in text format
                                  to standard output.  The message type must
                                  be defined in PROTO_FILES or their imports.
      --decode_raw                Read an arbitrary protocol message from
                                  standard input and write the raw tag/value
                                  pairs in text format to standard output.  No
                                  PROTO_FILES should be given when using this
                                  flag.
      --descriptor_set_in=FILES   Specifies a delimited list of FILES
                                  each containing a FileDescriptorSet (a
                                  protocol buffer defined in descriptor.proto).
                                  The FileDescriptor for each of the PROTO_FILES
                                  provided will be loaded from these
                                  FileDescriptorSets. If a FileDescriptor
                                  appears multiple times, the first occurrence
                                  will be used.
      -oFILE,                     Writes a FileDescriptorSet (a protocol buffer,
        --descriptor_set_out=FILE defined in descriptor.proto) containing all of
                                  the input files to FILE.
      --include_imports           When using --descriptor_set_out, also include
                                  all dependencies of the input files in the
                                  set, so that the set is self-contained.
      --include_source_info       When using --descriptor_set_out, do not strip
                                  SourceCodeInfo from the FileDescriptorProto.
                                  This results in vastly larger descriptors that
                                  include information about the original
                                  location of each decl in the source file as
                                  well as surrounding comments.
      --dependency_out=FILE       Write a dependency output file in the format
                                  expected by make. This writes the transitive
                                  set of input file paths to FILE
      --error_format=FORMAT       Set the format in which to print errors.
                                  FORMAT may be 'gcc' (the default) or 'msvs'
                                  (Microsoft Visual Studio format).
      --print_free_field_numbers  Print the free field numbers of the messages
                                  defined in the given proto files. Groups share
                                  the same field number space with the parent 
                                  message. Extension ranges are counted as 
                                  occupied fields numbers.
    
      --c_out=OUT_DIR             Generate C/H files.
      @<filename>                 Read options and filenames from file. If a
                                  relative file path is specified, the file
                                  will be searched in the working directory.
                                  The --proto_path option will not affect how
                                  this argument file is searched. Content of
                                  the file will be expanded in the position of
                                  @<filename> as in the argument list. Note
                                  that shell expansion is not applied to the
                                  content of the file (i.e., you cannot use
                                  quotes, wildcards, escapes, commands, etc.).
                                  Each line corresponds to a single argument,
                                  even if it contains spaces.
    

    3. 测试protobuf

    转换proto文件为 C 语言库

    protoc-c --c_out=. kurapayload.proto

    生成protoc文件

    -rw-r--r--  1 jiekechoo  staff  14836  8 18 11:36 kurapayload.pb-c.c
    -rw-r--r--  1 jiekechoo  staff   6427  8 18 11:36 kurapayload.pb-c.h
    -rw-r--r--  1 jiekechoo  staff   1749  8 18 11:17 kurapayload.proto
    

    完成protobuf集成工作

    #include <stdio.h>
    #include <stdlib.h>
    #include "MQTTClient.h"
    #include "kurapayload.pb-c.h"
     
    void main()
    {
    MQTTClient client;
    MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;
    MQTTClient_message pubmsg = MQTTClient_message_initializer;
    MQTTClient_deliveryToken token;
    int rc;
    
    MQTTClient_create(&client, ADDRESS, CLIENTID,
        MQTTCLIENT_PERSISTENCE_NONE, NULL);
    conn_opts.keepAliveInterval = 20;
    conn_opts.cleansession = 1;
    conn_opts.username = "username";
    conn_opts.password = "password";
    
    if ((rc = MQTTClient_connect(client, &conn_opts)) != MQTTCLIENT_SUCCESS)
    {
        printf("Failed to connect, return code %d\n", rc);
        exit(-1);
    }
        Kuradatatypes__KuraPayload payload = KURADATATYPES__KURA_PAYLOAD__INIT;
        Kuradatatypes__KuraPayload__KuraMetric **metrics;
        Kuradatatypes__KuraPayload__KuraPosition position = KURADATATYPES__KURA_PAYLOAD__KURA_POSITION__INIT;
     
    position.latitude = 1.1;
    position.longitude = 1.1;
    
    payload.position = &position;
    
    metrics = malloc(sizeof (Kuradatatypes__KuraPayload__KuraMetric) * 2);
    int i = 0;
    for(i=0;i<2;i++)
    {
        metrics[i] = malloc (sizeof (Kuradatatypes__KuraPayload__KuraMetric));
        kuradatatypes__kura_payload__kura_metric__init(metrics[i]);
    
        metrics[i]->name = (char*)EdcBirthPayloadNames[i];
        printf("%s, ", metrics[i]->name);
        metrics[i]->type = KURADATATYPES__KURA_PAYLOAD__KURA_METRIC__VALUE_TYPE__STRING;        
        metrics[i]->string_value = (char*)EdcBirthPayloadValues[i];
        printf("%s\r\n", metrics[i]->string_value);
    }
        payload.n_metric = 2;
        payload.metric = metrics;
    
        size_t len = kuradatatypes__kura_payload__get_packed_size(&payload);
        void* data = malloc(len); 
        kuradatatypes__kura_payload__pack(&payload, data);
        pubmsg.payload = data;
        pubmsg.payloadlen = kuradatatypes__kura_payload__get_packed_size(&payload);
        MQTTClient_publishMessage(client, BIRTH_TOPIC, &pubmsg, &token);
    
    }
    

    编译,如果没有出错就上成功。

    gcc kurapayload.pb-c.c main.c -lprotobuf-c
    

    4. 注意事项

    • 系统要能够使用 malloc 功能,好多地方要用到;
    • 结构体初始化 INIT 或 使用 init 函数;
    • 数据准备完毕,要 pack 一下,再 packed_size 计算长度,后面该怎么处理都行了;

    最终,C 语言版本的 Kura 模拟器成功连接 Kapua。
    1761599121932_.pic_hd.jpg

    Eclipse Kura 作为边缘计算网关应用套件,功能非常强大,作为一个基础性框架,在上面跑一些用户应用,那是非常合适的。

    一、启用 Kura 上面支持 MQTT broker
    进入 Kura web console,开启Simple Artemis MQTT Broker,测试中暂定 usename 和 password 都为 mqtt。

    WX20200707-174344.png

    登录 Kura 设备后台,查看 TCP 1833 端口已经开启

    root@raspberrypi:/home/pi# netstat -nlpt |grep 1883
    tcp        0      0 0.0.0.0:1883            0.0.0.0:*               LISTEN      427/java            
    

    二、模拟 MQTT 设备发送数据
    使用 Chrome 浏览器插件 MQTTBox 登录我们的 MQTT server
    WX20200707-174844@2x.png

    三、安装 Kura 插件
    使用 Eclipse marketplace 安装以下插件

    • Apache Camel MQTT endpoint
    • Apache Camel Groovy language support
    • Apache Camel GSON data format

    四、创建新的 Component
    创新 component
    Integrating-with-Kura-and-Kapua-5.png

    并输入以下内容

    <routes xmlns="http://camel.apache.org/schema/spring">
      <route>
        <from uri="paho:humidity/sensor1/humidity?brokerUrl=tcp://localhost:1883&amp;clientId=route1&amp;userName=mqtt&amp;password=mqtt"/>
        <unmarshal><json library="Gson"></json></unmarshal>
        <transform><simple>${body["humidity"]}</simple></transform>
        <transform><groovy>["HUMIDITY": request.body/100, "ASSETNAME": "HrY", "SENSOR": "sensor1"]</groovy></transform>
        <to uri="seda:wiresOutput1"/>
      </route>
    </routes>
    

    WX20200707-175458.png

    五、创建一个 Cloud publisher
    WX20200707-175640.png

    六、新建一个 Wire Graph
    Camel Consumer - camel_comsumer
    WX20200707-175756.png

    Publisher - pub_camel1
    WX20200707-175907.png

    把两个相连起来
    WX20200707-180007.png

    七、发送数据,测试 Kapua 数据接收情况
    发布/订阅 主题topic: humidity/sensor1/humidity
    WX20200707-180254@2x.png

    发送数据验证
    WX20200707-180510@2x.png

    Kapua 接收,查询结果
    WX20200707-180442.png