分类 边缘计算 下的文章

此次移植工作以中国移动 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

edge-computing-128x128.png

定义

边缘计算,顾名思义就是在边缘实现计算(运算),超出云计算以外的任何运算都可以泛称为边缘计算。云计算在大数据上运行,边缘计算在即时数据上运行,即时数据就是由现场传感器、用户或设备生成的实时数据。将云端数据运算能力下移或下沉到边缘端,可以降低带宽,提高运行效率。

概念

随着物联网 IoT 设备数量不断的增加,相应的数据也会呈指数型上升,完全传输到云端运算处理无意是对带宽的极大考验,即使在今天,5G 网络提高了更高的带宽和更低的时延,对数据中心的网络出入口也是非常的挑战。边缘端与云端过多的传输中间过程数据,显然是不合适的,也是低效率的,在边缘端运算处理将是改变这种问题的最好手段。我们可以利用现有的智能手机、数据网关、网络设备等,执行一些具有边缘代表性的任务,通过将云端计算服务移至边缘,可以提供内容缓存、服务交付、存储、网络管理和物联网管理。

文章部分内容来自于 https://wikipedia.org/wiki/Edge_computing