SDK-based Driver Development

This article mainly introduces how to develop a new driver plugin based on the SDK package and apply it to Neuron.

Step 1 Download and install the SDK

Download link: https://github.com/emqx/neuron/releases

According to different development systems, download the corresponding sdk tar.gz package, e.g. neuron-sdk-2.1.3-linux-amd64.tar.gz to the corresponding development system and decompress it to obtain neuron-sdk-x.x.x, where x.x.x represents the version number, execute the following command.

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

After the script is executed, you need to pay attention to the usage of the following paths.

Path Description
/usr/local/include/neuron Stores Neuron header files and apply to include_directories in the CMakeLists.txt compilation file
/usr/local/lib/neuron Stores Neuron dependent library files, which are applied to link_directories in the CMakeLists.txt compilation file
/usr/local/bin/neuron Holds files needed to run Neuron

Step 2 Driver development

Create a new directory file in the development environment to store the files required for the development driver, create a compilation configuration file CMakeLists.txt under the directory file, a build directory file to store the compiled files and a plugins directory file for To store all the driver files that need to be developed, each driver needs to have an independent directory to store the required files for driver development. Taking the development of modbus-tcp driver plug-in as an example, the directory level is shown in the figure below.

driver-tree

CMakeLists.txt example

The main thing is that include_directories, link_directories and add_subdirectory should be configured correctly.

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

plugins/modbus

The driver development file mainly includes the compilation configuration file CMakeLists.txt, the driver configuration json file and the driver code file.

CMakeLists.txt example

  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

The interface file of the driver plugin, for the specific driver development example, please refer to modbus plugin development example.

static const neu_plugin_intf_funs_t plugin_intf_funs structure description.

Parameters Description
.open The first function called by neuron when creating a node based on the plugin, create a struct neu_plugin defined by the plugin itself
.close The last function called by neuron when a node is removed to release the neu_plugin_t created by open
.init When creating a node, after neuron calls open, the function called immediately after that. This function is mainly used for some resources that need to be initialized in the plugin
.uninit The function that neuron calls first when deleting a node. This function mainly releases some resources applied and initialized in init
.start On the node page, set the job status to start, neuron will call this function to notify the plugin to start running, and start connecting to the device
.stop On the node page, set the working status to stop, neuron will call this function to notify the plugin to stop running, close the connection with the device, and driver.group_timer will no longer trigger
.setting On the node page, when the plugin is set, the parameters will be set in json format, and neuron will notify the plugin to set through this function
.request This function has not been used in the development of southbound driver
.driver.validate_tag When a node adds or updates a tag, neuron will use this function to notify the plug-in of the relevant parameters of the tag. The plug-in checks whether the tag meets the requirements of the plug-in according to its own implementation. The function returns 0, which means success
.driver.group_timer When a group is added to a node and the node status is running, this function will be called periodically to read device data with the interval parameter of the group
.driver.write_tag When using the write API, neuron calls this function to notify the plugin to write a specific value to the point tag

Dynamic library export data structure definition const neu_plugin_module_t neu_plugin_module Description.

Parameters Description
version Plugin version number
module_name module name
module_descr Module description
intf_funs Plugin interface function
kind Plugin type
type The type of node when the plugin is instantiated as node

The struct neu_plugin structure is the pre-name of the plugin. Each plugin needs to provide the specific definition of the structure, and the first member must be common, and other members are added according to the driver’s configuration.

  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

The driver configuration file.

Field Description
tag_regex Regex for address configuration for drivers that support different data types
description A detailed description of the field
attribute The attribute of the field, there are only two optional and required options, namely required and optional
type The type of the field, currently int and string are commonly used
default The default value to fill in
valid The range that this field can be filled in

The name of the json file should be the same as the module name 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

After the driver development code is completed, execute compilation in this directory.

  1. $ cmake ..
  2. $ make

Step 3 The plugin is applied to Neuron

Copy the driver .so file

After compiling, go to modbus/build/plugins and copy the generated driver .so file (for example, libplugin-modbus-tcp.so ) to the /usr/local/bin/neuron/plugins directory.

Copy the driver configuration .json file

Copy the modbus/modbus-tcp.json file to the /usr/local/bin/neuron/plugins/schema directory.

Modify plugins.json file

Open the plugins.json file in the /usr/local/bin/neuron/persistence directory and add the name of the newly added driver .so file to it.

Start Neuron verification driver

Go back to the /usr/local/bin/neuron directory and execute the following command to run Neuron.

  1. $ sudo ./neuron --log

Open Neuron on the web page to view the added plugins and their usage.