基于 SDK 的驱动开发

本文主要介绍如何基于 SDK 包开发一个新的驱动插件并应用到 Neuron 中。

第一步,下载安装 SDK

下载链接:https://github.com/emqx/neuron/releases基于 SDK 的驱动开发 - 图1 (opens new window)

根据不同的开发系统,下载对应的 sdk tar.gz 包( 例如,neuron-sdk-2.1.3-linux-amd64.tar.gz )到相应的开发系统中并解压得到 neuron-sdk-x.x.x,其中 x.x.x 代表的是版本号,执行以下指令。

  1. # take version 2.3.0 as an example
  2. $ cd neuron-sdk-2.3.0
  3. # install sdk
  4. $ sudo ./sdk-install.sh

脚本执行完成后,需要注意以下路径的用法。

路径说明
/usr/local/include/neuron存放 Neuron 的头文件,应用于 CMakeLists.txt 编译文件中的 include_directories
/usr/local/lib/neuron存放 Neuron 依赖库文件,应用于 CMakeLists.txt 编译文件中的 link_directories
/usr/local/bin/neuron存放运行 Neuron 所需的文件

第二步,驱动开发

在开发环境中新建一个目录文件用于存放开发驱动所需要的文件,在该目录文件下新建一个编译配置文件 CMakeLists.txt,一个 build 目录文件用于存放编译后的文件和一个 plugins 目录文件用于存放所有需要开发的驱动文件,每一个驱动都需要有一个独立的目录来存放驱动开发的所需的文件,以开发 modbus-tcp 驱动插件为例,目录层级如下图所示。

driver_tree

CMakeLists.txt 范例

最主要的是 include_directories,link_directories 和 add_subdirectory 要配置正确。

  1. cmake_minimum_required(VERSION 3.12)
  2. enable_language(C)
  3. set(CMAKE_C_STANDARD 99)
  4. find_package(Threads)
  5. # add the path to the neuron header
  6. include_directories(/usr/local/include /usr/local/include/neuron)
  7. # add the path to the neuron library
  8. link_directories(/usr/local/lib /usr/local/lib/neuron)
  9. # add driver submodule
  10. add_subdirectory(plugins/modbus)

plugins/modbus

驱动开发文件中主要包含编译配置文件 CMakeLists.txt 和 驱动配置的 json 文件和驱动代码文件。

CMakeLists.txt 示例

  1. set(LIBRARY_OUTPUT_PATH "${CMAKE_BINARY_DIR}/plugins")
  2. set(CMAKE_BUILD_RPATH ./)
  3. # set plugin name
  4. set(MODBUS_TCP_PLUGIN plugin-modbus-tcp)
  5. # set the driver development code file
  6. set(MODBUS_TCP_PLUGIN_SOURCES modbus_tcp.c)
  7. add_library(${MODBUS_TCP_PLUGIN} SHARED)
  8. target_sources(${MODBUS_TCP_PLUGIN} PRIVATE ${MODBUS_TCP_PLUGIN_SOURCES})
  9. target_link_libraries(${MODBUS_TCP_PLUGIN} neuron-base)

modbus_tcp.c

驱动插件的接口文件,具体的驱动开发示例,请参考 modbus 插件开发示例

static const neu_plugin_intf_funs_t plugin_intf_funs 结构体说明。

参数说明
.open基于 plugin 创建 node 时 neuron 第一个调用的函数,创建插件自己定义的 struct neu_plugin
.close删除 node 时,neuron 调用的最后一个函数,用于释放由 open 创建的 neu_plugin_t
.init创建 node 时,neuron 调用完 open 后,紧接着调用的函数,此函数主要做插件内需要初始化的一些资源
.uninit删除 node 时,neuron 首先调用的函数,此函数主要释放一些在 init 中申请以及初始化的资源
.start在 node 页面,将工作状态置为开始,neuron 会调用此函数,通知插件开始运行,以及开始连接设备
.stop在 node 页面,将工作状态置为停止,neuron 会调用此函数,通知插件停止运行,关闭与设备的连接,driver.group_timer 不再触发
.setting在 node 页面,进行插件设置时,将通过 json 格式设置参数,neuron 通过此函数通知插件进行设置
.request此函数在南向 driver 开发中暂未使用
.driver.validate_tagnode 添加或者更新 tag 时,neuron 会把 tag 相关参数使用此函数通知到插件,插件根据各自实现检查此 tag 是有符合插件要求,该函数返回 0,代表成功
.driver.group_timer在 node 中添加 group 并且 node 状态为 running 时,此函数将以 group 的 interval 参数定时调用读取设备数据
.driver.write_tag当使用 write API 时,neuron 调用此函数,通知插件,向点位 tag 写入特定的值

动态库导出数据结构定义 const neu_plugin_module_t neu_plugin_module 说明。

参数说明
version插件版本号
module_name模块名称
module_descr模块描述
intf_funs插件接口函数
kind插件类型
type插件实例化为 node 时,node 的类型

struct neu_plugin 结构体为插件的前置声名,每个插件需要提供该结构体的具体定义,且首成员必须是 common,其它成员根据驱动的配置添加。

  1. #include <stdlib.h>
  2. #include <neuron.h>
  3. static neu_plugin_t *driver_open(void);
  4. static int driver_close(neu_plugin_t *plugin);
  5. static int driver_init(neu_plugin_t *plugin);
  6. static int driver_uninit(neu_plugin_t *plugin);
  7. static int driver_start(neu_plugin_t *plugin);
  8. static int driver_stop(neu_plugin_t *plugin);
  9. static int driver_config(neu_plugin_t *plugin, const char *config);
  10. static int driver_request(neu_plugin_t *plugin, neu_reqresp_head_t *head,
  11. void *data);
  12. static int driver_validate_tag(neu_plugin_t *plugin, neu_datatag_t *tag);
  13. static int driver_group_timer(neu_plugin_t *plugin, neu_plugin_group_t *group);
  14. static int driver_write(neu_plugin_t *plugin, void *req, neu_datatag_t *tag,
  15. neu_value_u value);
  16. static const neu_plugin_intf_funs_t plugin_intf_funs = {
  17. .open = driver_open,
  18. .close = driver_close,
  19. .init = driver_init,
  20. .uninit = driver_uninit,
  21. .start = driver_start,
  22. .stop = driver_stop,
  23. .setting = driver_config,
  24. .request = driver_request,
  25. .driver.validate_tag = driver_validate_tag,
  26. .driver.group_timer = driver_group_timer,
  27. .driver.write_tag = driver_write,
  28. };
  29. const neu_plugin_module_t neu_plugin_module = {
  30. .version = NEURON_PLUGIN_VER_1_0,
  31. .module_name = "modbus-tcp",
  32. .module_descr = "modbus tcp",
  33. .intf_funs = &plugin_intf_funs,
  34. .kind = NEU_PLUGIN_KIND_SYSTEM,
  35. .type = NEU_NA_TYPE_DRIVER,
  36. };
  37. struct neu_plugin {
  38. neu_plugin_common_t common;
  39. };
  40. static neu_plugin_t *driver_open(void)
  41. {
  42. neu_plugin_t *plugin = calloc(1, sizeof(neu_plugin_t));
  43. neu_plugin_common_init(&plugin->common);
  44. return plugin;
  45. }
  46. static int driver_close(neu_plugin_t *plugin)
  47. {
  48. free(plugin);
  49. return 0;
  50. }
  51. static int driver_init(neu_plugin_t *plugin)
  52. {
  53. plog_info(plugin, "node: modbus init");
  54. return 0;
  55. }
  56. static int driver_uninit(neu_plugin_t *plugin)
  57. {
  58. plog_info(plugin, "node: modbus uninit");
  59. return 0;
  60. }
  61. static int driver_start(neu_plugin_t *plugin)
  62. {
  63. plog_info(plugin, "node: modbus start");
  64. return 0;
  65. }
  66. static int driver_stop(neu_plugin_t *plugin)
  67. {
  68. plog_info(plugin, "node: modbus stop");
  69. return 0;
  70. }
  71. static int driver_config(neu_plugin_t *plugin, const char *config)
  72. {
  73. plog_info(plugin, "config: %s", config);
  74. return 0;
  75. }
  76. static int driver_request(neu_plugin_t *plugin, neu_reqresp_head_t *head,
  77. void *data)
  78. {
  79. (void) data;
  80. (void) plugin;
  81. (void) head;
  82. return 0;
  83. }
  84. static int driver_validate_tag(neu_plugin_t *plugin, neu_datatag_t *tag)
  85. {
  86. plog_info(plugin, "validate tag: %s", tag->name);
  87. return 0;
  88. }
  89. static int driver_group_timer(neu_plugin_t *plugin, neu_plugin_group_t *group)
  90. {
  91. (void) plugin;
  92. (void) group;
  93. plog_info(plugin, "timer....");
  94. return 0;
  95. }
  96. static int driver_write(neu_plugin_t *plugin, void *req, neu_datatag_t *tag,
  97. neu_value_u value)
  98. {
  99. (void) plugin;
  100. (void) req;
  101. (void) tag;
  102. (void) value;
  103. return 0;
  104. }

modbus-tcp.json

驱动配置文件。

字段说明
tag_regex针对驱动支持不同的数据类型,对地址配置的正则
description该字段的详细说明
attribute该字段的属性,只有两种可选和必选,即 required 和 optional
type该字段的类型,目前常用的是 int 和 string 两种类型
default填写的默认值
valid该字段可填写的范围

提示

json 文件的名称应与模块名称 module_name 保持一致。

  1. {
  2. "tag_regex": [
  3. {
  4. "type": 3,
  5. "regex": "[1-9]+![3-4][0-9]+(#B|#L|)$"
  6. },
  7. {
  8. "type": 4,
  9. "regex": "[1-9]+![3-4][0-9]+(#B|#L|)$"
  10. },
  11. {
  12. "type": 5,
  13. "regex": "[1-9]+![3-4][0-9]+(#BB|#BL|#LL|#LB|)$"
  14. },
  15. {
  16. "type": 6,
  17. "regex": "[1-9]+![3-4][0-9]+(#BB|#BL|#LL|#LB|)$"
  18. },
  19. {
  20. "type": 9,
  21. "regex": "[1-9]+![3-4][0-9]+(#BB|#BL|#LL|#LB|)$"
  22. },
  23. {
  24. "type": 11,
  25. "regex": "[1-9]+!([0-1][0-9]+|[3-4][0-9]+.([0-9]|[0-1][0-5]))$"
  26. }
  27. ],
  28. "host": {
  29. "name": "host",
  30. "description": "local ip in server mode, remote device ip in client mode",
  31. "attribute": "required",
  32. "type": "string",
  33. "valid": {
  34. "regex": "/^((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?)$/",
  35. "length": 30
  36. }
  37. },
  38. "port": {
  39. "name": "port",
  40. "description": "local port in server mode, remote device port in client mode",
  41. "attribute": "required",
  42. "type": "int",
  43. "default": 502,
  44. "valid": {
  45. "min": 1,
  46. "max": 65535
  47. }
  48. },
  49. "timeout": {
  50. "name": "timeout",
  51. "description": "recv msg timeout(ms)",
  52. "attribute": "required",
  53. "type": "int",
  54. "default": 3000,
  55. "valid": {
  56. "min": 1000,
  57. "max": 65535
  58. }
  59. }
  60. }

build

驱动开发代码完成之后,在该目录下执行编译。

  1. cmake ..
  2. make

第三步,插件应用于 Neuron

拷贝驱动 .so 文件

编译完成后,进入 modbus/build/plugins 中将生成的驱动 .so 文件(例如,libplugin-modbus-tcp.so ) 拷贝到 /usr/local/bin/neuron/plugins 目录下。

拷贝驱动配置 .json 文件

将 modbus/modbus-tcp.json 文件拷贝到 /usr/local/bin/neuron/plugins/schema 目录下。

修改 plugins.json 文件

打开 /usr/local/bin/neuron/persistence 目录下的 plugins.json 文件,将新添加的驱动 .so 文件名称添加进去。

启动 Neuron 验证驱动

回到 /usr/local/bin/neuron 目录下,执行以下指令,运行 Neuron。

  1. sudo ./neuron --log

在网页打开 Neuron 查看添加的插件及其使用。