Files
neuron/plugins/modbus/modbus_tcp.c
T
fengzero fd07e7fd5c ft: modbus tcp read/write
modbus tcp server simulator
2021-11-29 12:17:33 +08:00

696 lines
24 KiB
C

/**
* NEURON IIoT System for Industry 4.0
* Copyright (C) 2020-2021 EMQ Technologies Co., Ltd All rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
#include <errno.h>
#include <netinet/in.h>
#include <pthread.h>
#include <stdlib.h>
#include <sys/queue.h>
#include <nng/nng.h>
#include "connection/neu_tcp.h"
#include "neu_data_expr.h"
#include "neu_datatag_table.h"
#include "neu_tag_group_config.h"
#include "neu_vector.h"
#include "neuron.h"
#include "json/neu_json_param.h"
#include "modbus.h"
#include "modbus_point.h"
const neu_plugin_module_t neu_plugin_module;
struct subscribe_instance {
neu_taggrp_config_t * grp_configs;
char * name;
modbus_point_context_t *point_ctx;
nng_aio * aio;
neu_plugin_t * plugin;
TAILQ_ENTRY(subscribe_instance) node;
};
struct neu_plugin {
neu_plugin_common_t common;
pthread_mutex_t mtx;
neu_tcp_client_t * client;
neu_node_id_t node_id;
neu_datatag_table_t *tag_table;
TAILQ_HEAD(, subscribe_instance) sub_instances;
};
static void start_periodic_read(neu_plugin_t * plugin,
neu_taggrp_config_t *grp_config);
static void stop_periodic_read(neu_plugin_t * plugin,
neu_taggrp_config_t *grp_config);
static void tags_periodic_read(nng_aio *aio, void *arg, int code);
static ssize_t send_recv_callback(void *arg, char *send_buf, ssize_t send_len,
char *recv_buf, ssize_t recv_len);
static ssize_t send_recv_callback(void *arg, char *send_buf, ssize_t send_len,
char *recv_buf, ssize_t recv_len)
{
neu_plugin_t *plugin = (neu_plugin_t *) arg;
if (neu_tcp_client_is_connected(plugin->client)) {
plugin->common.link_state = NEU_PLUGIN_LINK_STATE_CONNECTED;
} else {
plugin->common.link_state = NEU_PLUGIN_LINK_STATE_CONNECTING;
}
return neu_tcp_client_send_recv(plugin->client, send_buf, send_len,
recv_buf, recv_len);
}
static void start_periodic_read(neu_plugin_t * plugin,
neu_taggrp_config_t *grp_config)
{
struct subscribe_instance *sub_inst =
calloc(1, sizeof(struct subscribe_instance));
vector_t *ids = neu_taggrp_cfg_get_datatag_ids(grp_config);
nng_aio * aio = NULL;
uint32_t interval = neu_taggrp_cfg_get_interval(grp_config);
nng_aio_alloc(&aio, NULL, NULL);
nng_aio_set_timeout(aio, interval != 0 ? interval : 10000);
sub_inst->grp_configs =
(neu_taggrp_config_t *) neu_taggrp_cfg_ref(grp_config);
sub_inst->point_ctx = modbus_point_init(plugin);
sub_inst->aio = aio;
sub_inst->plugin = plugin;
sub_inst->name = strdup(neu_taggrp_cfg_get_name(grp_config));
VECTOR_FOR_EACH(ids, iter)
{
neu_datatag_id_t *id = (neu_datatag_id_t *) iterator_get(&iter);
neu_datatag_t * tag = neu_datatag_tbl_get(plugin->tag_table, *id);
if ((tag->attribute & NEU_ATTRIBUTE_READ) == NEU_ATTRIBUTE_READ) {
switch (tag->type) {
case NEU_DTYPE_UINT16:
case NEU_DTYPE_INT16:
modbus_point_add(sub_inst->point_ctx, tag->addr_str,
MODBUS_B16);
break;
case NEU_DTYPE_FLOAT:
case NEU_DTYPE_UINT32:
case NEU_DTYPE_INT32:
modbus_point_add(sub_inst->point_ctx, tag->addr_str,
MODBUS_B32);
break;
case NEU_DTYPE_BIT:
modbus_point_add(sub_inst->point_ctx, tag->addr_str, MODBUS_B8);
break;
default:
break;
}
}
}
modbus_point_new_cmd(sub_inst->point_ctx);
pthread_mutex_lock(&plugin->mtx);
TAILQ_INSERT_TAIL(&plugin->sub_instances, sub_inst, node);
pthread_mutex_unlock(&plugin->mtx);
log_info("start periodic read, grp: %s(%p), interval: %d", sub_inst->name,
grp_config, interval);
nng_aio_defer(aio, tags_periodic_read, sub_inst);
}
static void stop_periodic_read(neu_plugin_t * plugin,
neu_taggrp_config_t *grp_config)
{
struct subscribe_instance *sub_inst = NULL;
pthread_mutex_lock(&plugin->mtx);
TAILQ_FOREACH(sub_inst, &plugin->sub_instances, node)
{
if (sub_inst->grp_configs == grp_config) {
TAILQ_REMOVE(&plugin->sub_instances, sub_inst, node);
nng_aio_cancel(sub_inst->aio);
modbus_point_destory(sub_inst->point_ctx);
neu_taggrp_cfg_free(sub_inst->grp_configs);
free(sub_inst->name);
free(sub_inst);
log_info("stop periodic read, grp: %p", grp_config);
break;
}
}
pthread_mutex_unlock(&plugin->mtx);
}
static neu_data_val_t *
setup_read_resp_data_value(neu_datatag_table_t * tag_table,
neu_taggrp_config_t * grp_config,
modbus_point_context_t *point_ctx)
{
neu_data_val_t * resp_val = NULL;
int read_count = 0;
modbus_data_t data_tags = { 0 };
vector_t * ids = neu_taggrp_cfg_get_datatag_ids(grp_config);
neu_fixed_array_t *array_resp = NULL;
int index = 1;
neu_data_val_t * val_error_resp = neu_dvalue_new(NEU_DTYPE_ERRORCODE);
neu_int_val_t int_val_resp = { 0 };
neu_err_code_e error = NEU_ERR_SUCCESS;
read_count = neu_plugin_tag_count_by_attribute(grp_config, tag_table,
NEU_ATTRIBUTE_READ);
resp_val = neu_dvalue_unit_new();
if (resp_val == NULL) {
log_error("Failed to allocate data value for response tags");
neu_dvalue_free(val_error_resp);
return NULL;
}
array_resp = neu_fixed_array_new(read_count + 1, sizeof(neu_int_val_t));
if (array_resp == NULL) {
log_error("Failed to allocate array for response tags");
neu_dvalue_free(resp_val);
neu_dvalue_free(val_error_resp);
return NULL;
}
VECTOR_FOR_EACH(ids, iter_id)
{
neu_datatag_id_t *id = (neu_datatag_id_t *) iterator_get(&iter_id);
neu_datatag_t * tag = neu_datatag_tbl_get(tag_table, *id);
int result = 0;
neu_int_val_t int_val = { 0 };
if ((tag->attribute & NEU_ATTRIBUTE_READ) == NEU_ATTRIBUTE_READ) {
switch (tag->type) {
case NEU_DTYPE_UINT32:
case NEU_DTYPE_INT32:
case NEU_DTYPE_FLOAT:
data_tags.type = MODBUS_B32;
break;
case NEU_DTYPE_UINT16:
case NEU_DTYPE_INT16:
data_tags.type = MODBUS_B16;
break;
case NEU_DTYPE_BIT:
data_tags.type = MODBUS_B8;
break;
default:
break;
}
result = modbus_point_find(point_ctx, tag->addr_str, &data_tags);
if (result == -1) {
neu_data_val_t *val_error = neu_dvalue_new(NEU_DTYPE_ERRORCODE);
log_error("Failed to find modbus point %s", tag->addr_str);
neu_dvalue_set_errorcode(val_error, result);
neu_int_val_init(&int_val, tag->id, val_error);
neu_fixed_array_set(array_resp, index, (void *) &int_val);
error = NEU_ERR_TAG_NOT_EXIST;
} else {
switch (data_tags.type) {
case MODBUS_B8: {
neu_data_val_t *val_bit = neu_dvalue_new(NEU_DTYPE_BIT);
neu_dvalue_set_bit(val_bit, data_tags.val.val_u8);
neu_int_val_init(&int_val, tag->id, val_bit);
neu_fixed_array_set(array_resp, index, (void *) &int_val);
break;
}
case MODBUS_B16:
switch (tag->type) {
case NEU_DTYPE_INT16: {
neu_data_val_t *val_i16 =
neu_dvalue_new(NEU_DTYPE_INT16);
neu_dvalue_set_int16(val_i16, data_tags.val.val_u16);
neu_int_val_init(&int_val, tag->id, val_i16);
neu_fixed_array_set(array_resp, index,
(void *) &int_val);
break;
}
case NEU_DTYPE_UINT16: {
neu_data_val_t *val_ui16 =
neu_dvalue_new(NEU_DTYPE_UINT16);
neu_dvalue_set_uint16(val_ui16, data_tags.val.val_u16);
neu_int_val_init(&int_val, tag->id, val_ui16);
neu_fixed_array_set(array_resp, index,
(void *) &int_val);
break;
}
default:
break;
}
break;
case MODBUS_B32:
switch (tag->type) {
case NEU_DTYPE_INT32: {
neu_data_val_t *val_i32 =
neu_dvalue_new(NEU_DTYPE_INT32);
neu_dvalue_set_int32(val_i32, data_tags.val.val_u32);
neu_int_val_init(&int_val, tag->id, val_i32);
neu_fixed_array_set(array_resp, index,
(void *) &int_val);
break;
}
case NEU_DTYPE_UINT32: {
neu_data_val_t *val_ui32 =
neu_dvalue_new(NEU_DTYPE_UINT32);
neu_dvalue_set_uint32(val_ui32, data_tags.val.val_u32);
neu_int_val_init(&int_val, tag->id, val_ui32);
neu_fixed_array_set(array_resp, index,
(void *) &int_val);
break;
}
case NEU_DTYPE_FLOAT: {
neu_data_val_t *val_float =
neu_dvalue_new(NEU_DTYPE_FLOAT);
neu_dvalue_set_float(val_float, data_tags.val.val_f32);
neu_int_val_init(&int_val, tag->id, val_float);
neu_fixed_array_set(array_resp, index,
(void *) &int_val);
break;
}
default:
break;
}
}
}
}
index += 1;
}
neu_dvalue_set_errorcode(val_error_resp, error);
neu_int_val_init(&int_val_resp, 0, val_error_resp);
neu_fixed_array_set(array_resp, 0, (void *) &int_val_resp);
neu_dvalue_init_move_array(resp_val, NEU_DTYPE_INT_VAL, array_resp);
return resp_val;
}
static neu_data_val_t *setup_write_resp_data_value(neu_data_val_t *write_val,
neu_plugin_t * plugin)
{
neu_fixed_array_t *array_req = NULL;
neu_fixed_array_t *array_resp = NULL;
neu_data_val_t * resp_val = NULL;
neu_data_val_t * val_error_resp = neu_dvalue_new(NEU_DTYPE_ERRORCODE);
neu_int_val_t int_val_resp = { 0 };
neu_err_code_e error = NEU_ERR_SUCCESS;
neu_dvalue_get_ref_array(write_val, &array_req);
resp_val = neu_dvalue_unit_new();
if (resp_val == NULL) {
log_error("Failed to allocate data value for response write data");
neu_dvalue_free(val_error_resp);
return NULL;
}
array_resp = neu_fixed_array_new(array_req->length, sizeof(neu_int_val_t));
if (array_resp == NULL) {
log_error("Failed to allocate array for response write data");
neu_dvalue_free(resp_val);
neu_dvalue_free(val_error_resp);
return NULL;
}
for (uint32_t i = 0; i < array_req->length; i++) {
neu_int_val_t *int_val = neu_fixed_array_get(array_req, i);
neu_dtype_e val_type =
neu_value_type_in_dtype(neu_dvalue_get_type(int_val->val));
modbus_data_t data = { 0 };
neu_int_val_t iv;
neu_data_val_t *val_error = neu_dvalue_new(NEU_DTYPE_ERRORCODE);
neu_datatag_t * tag =
neu_datatag_tbl_get(plugin->tag_table, int_val->key);
switch (val_type) {
case NEU_DTYPE_INT64: {
int64_t i64 = 0;
neu_dvalue_get_int64(int_val->val, &i64);
switch (tag->type) {
case NEU_DTYPE_UINT16:
case NEU_DTYPE_INT16:
data.type = MODBUS_B16;
data.val.val_u16 = (uint16_t) i64;
error = modbus_point_write(tag->addr_str, &data,
send_recv_callback, plugin);
break;
case NEU_DTYPE_INT32:
case NEU_DTYPE_UINT32:
data.type = MODBUS_B32;
data.val.val_u32 = (uint32_t) i64;
error = modbus_point_write(tag->addr_str, &data,
send_recv_callback, plugin);
break;
case NEU_DTYPE_BIT:
data.type = MODBUS_B8;
data.val.val_u8 = (uint8_t) i64;
error = modbus_point_write(tag->addr_str, &data,
send_recv_callback, plugin);
break;
default:
error = NEU_ERR_TAG_TYPE_NOT_SUPPORT;
break;
}
break;
}
case NEU_DTYPE_DOUBLE: {
double db = 0;
neu_dvalue_get_double(int_val->val, &db);
data.type = MODBUS_B32;
data.val.val_f32 = (float) db;
error = modbus_point_write(tag->addr_str, &data, send_recv_callback,
plugin);
break;
}
default:
error = NEU_ERR_EINTERNAL;
break;
}
neu_dvalue_set_errorcode(val_error, error);
neu_int_val_init(&iv, int_val->key, val_error);
neu_fixed_array_set(array_resp, i, (void *) &iv);
}
neu_dvalue_set_errorcode(val_error_resp, error);
neu_int_val_init(&int_val_resp, 0, val_error_resp);
neu_fixed_array_set(array_resp, 0, (void *) &int_val_resp);
neu_dvalue_init_move_array(resp_val, NEU_DTYPE_INT_VAL, array_resp);
return resp_val;
}
static void tags_periodic_read(nng_aio *aio, void *arg, int code)
{
switch (code) {
case NNG_ETIMEDOUT: {
struct subscribe_instance *sub_inst = (struct subscribe_instance *) arg;
if (neu_taggrp_cfg_is_anchored(sub_inst->grp_configs)) {
neu_response_t resp = { 0 };
neu_reqresp_data_t data_resp = { 0 };
neu_data_val_t * resp_val = NULL;
modbus_point_all_read(sub_inst->point_ctx, true,
send_recv_callback);
resp_val = setup_read_resp_data_value(sub_inst->plugin->tag_table,
sub_inst->grp_configs,
sub_inst->point_ctx);
// todo modbus point find;
data_resp.grp_config = sub_inst->grp_configs;
data_resp.data_val = resp_val;
resp.req_id = 1;
resp.resp_type = NEU_REQRESP_TRANS_DATA;
resp.buf = &data_resp;
resp.buf_len = sizeof(neu_reqresp_data_t);
sub_inst->plugin->common.adapter_callbacks->response(
sub_inst->plugin->common.adapter, &resp);
nng_aio_defer(aio, tags_periodic_read, arg);
} else {
neu_taggrp_config_t *new_config = neu_system_find_group_config(
sub_inst->plugin, sub_inst->plugin->node_id, sub_inst->name);
neu_plugin_t *plugin = sub_inst->plugin;
stop_periodic_read(sub_inst->plugin, sub_inst->grp_configs);
neu_taggrp_cfg_anchor(new_config);
if (new_config != NULL) {
start_periodic_read(plugin, new_config);
}
}
break;
}
case NNG_ECANCELED:
log_warn("aio: %p cancel", aio);
nng_aio_free(aio);
break;
default:
log_warn("aio: %p, skip error: %d", aio, code);
nng_aio_free(aio);
break;
}
}
static neu_plugin_t *modbus_tcp_open(neu_adapter_t * adapter,
const adapter_callbacks_t *callbacks)
{
neu_plugin_t *plugin;
if (adapter == NULL || callbacks == NULL) {
log_error("Open plugin with NULL adapter or callbacks");
return NULL;
}
plugin = (neu_plugin_t *) calloc(1, sizeof(neu_plugin_t));
if (plugin == NULL) {
log_error("Failed to allocate plugin %s",
neu_plugin_module.module_name);
return NULL;
}
neu_plugin_common_init(&plugin->common);
plugin->common.adapter = adapter;
plugin->common.adapter_callbacks = callbacks;
plugin->common.link_state = NEU_PLUGIN_LINK_STATE_DISCONNECTED;
TAILQ_INIT(&plugin->sub_instances);
return plugin;
}
static int modbus_tcp_close(neu_plugin_t *plugin)
{
free(plugin);
return 0;
}
static int modbus_tcp_init(neu_plugin_t *plugin)
{
pthread_mutex_init(&plugin->mtx, NULL);
log_info("modbus tcp init.....");
return 0;
}
static int modbus_tcp_uninit(neu_plugin_t *plugin)
{
struct subscribe_instance *sub_inst = NULL;
log_info("modbus uninit start...");
pthread_mutex_lock(&plugin->mtx);
sub_inst = TAILQ_FIRST(&plugin->sub_instances);
while (sub_inst != NULL) {
TAILQ_REMOVE(&plugin->sub_instances, sub_inst, node);
nng_aio_cancel(sub_inst->aio);
modbus_point_destory(sub_inst->point_ctx);
free(sub_inst->name);
free(sub_inst);
sub_inst = TAILQ_FIRST(&plugin->sub_instances);
}
pthread_mutex_unlock(&plugin->mtx);
neu_tcp_client_close(plugin->client);
pthread_mutex_destroy(&plugin->mtx);
log_info("modbus uninit end...");
return 0;
}
static int modbus_tcp_config(neu_plugin_t *plugin, neu_config_t *configs)
{
int ret = 0;
char * err_param = NULL;
neu_json_elem_t port = { .name = "port", .t = NEU_JSON_INT };
neu_json_elem_t connection_modem = { .name = "connection_mode",
.t = NEU_JSON_INT };
;
neu_json_elem_t host = { .name = "host", .t = NEU_JSON_STR };
log_info("modbus config............. type: %d, config: %s", configs->type,
configs->buf);
ret = neu_parse_param(configs->buf, &err_param, 3, &port, &connection_modem,
&host);
if (ret == 0) {
plugin->common.link_state = NEU_PLUGIN_LINK_STATE_CONNECTING;
plugin->client = neu_tcp_client_create(plugin->client, host.v.val_str,
port.v.val_int);
if (neu_tcp_client_is_connected(plugin->client)) {
plugin->common.link_state = NEU_PLUGIN_LINK_STATE_CONNECTED;
}
}
log_info("port = %d, connection_modem = %d, host = %s,ret = %d",
port.v.val_int, connection_modem.v.val_int, host.v.val_str, ret);
return ret;
}
static int modbus_tcp_request(neu_plugin_t *plugin, neu_request_t *req)
{
if (plugin->tag_table == NULL) {
plugin->node_id = neu_plugin_self_node_id(plugin);
plugin->tag_table =
neu_system_get_datatags_table(plugin, plugin->node_id);
}
switch (req->req_type) {
case NEU_REQRESP_READ_DATA: {
neu_reqresp_read_t * read_req = (neu_reqresp_read_t *) req->buf;
struct subscribe_instance *sub_inst = NULL;
neu_response_t resp = { 0 };
neu_reqresp_read_resp_t data_resp = { 0 };
pthread_mutex_lock(&plugin->mtx);
TAILQ_FOREACH(sub_inst, &plugin->sub_instances, node)
{
if (sub_inst->grp_configs == read_req->grp_config) {
data_resp.data_val = setup_read_resp_data_value(
plugin->tag_table, read_req->grp_config,
sub_inst->point_ctx);
log_info("find read grp: %p", read_req->grp_config);
break;
}
}
pthread_mutex_unlock(&plugin->mtx);
if (data_resp.data_val == NULL) {
neu_data_val_t *val_error = neu_dvalue_new(NEU_DTYPE_ERRORCODE);
neu_data_val_t *array_resp = neu_dvalue_unit_new();
;
neu_int_val_t val = { 0 };
neu_fixed_array_t *array =
neu_fixed_array_new(1, sizeof(neu_int_val_t));
neu_dvalue_set_errorcode(val_error, NEU_ERR_GRP_NOT_SUBSCRIBE);
neu_int_val_init(&val, 0, val_error);
neu_fixed_array_set(array, 0, (void *) &val);
neu_dvalue_init_move_array(array_resp, NEU_DTYPE_INT_VAL, array);
data_resp.data_val = array_resp;
}
data_resp.grp_config = read_req->grp_config;
resp.req_id = req->req_id;
resp.resp_type = NEU_REQRESP_READ_RESP;
resp.buf = &data_resp;
resp.buf_len = sizeof(neu_reqresp_read_resp_t);
resp.recver_id = req->sender_id;
plugin->common.adapter_callbacks->response(plugin->common.adapter,
&resp);
break;
}
case NEU_REQRESP_WRITE_DATA: {
neu_reqresp_write_t *write_req = (neu_reqresp_write_t *) req->buf;
neu_reqresp_data_t data_resp = { 0 };
neu_response_t resp = { 0 };
assert(req->buf_len == sizeof(neu_reqresp_write_t));
data_resp.grp_config = write_req->grp_config;
data_resp.data_val =
setup_write_resp_data_value(write_req->data_val, plugin);
memset(&resp, 0, sizeof(resp));
resp.req_id = req->req_id;
resp.resp_type = NEU_REQRESP_WRITE_RESP;
resp.buf = &data_resp;
resp.buf_len = sizeof(neu_reqresp_data_t);
resp.recver_id = req->sender_id;
plugin->common.adapter_callbacks->response(plugin->common.adapter,
&resp);
break;
}
case NEU_REQRESP_SUBSCRIBE_NODE: {
neu_reqresp_subscribe_node_t *data =
(neu_reqresp_subscribe_node_t *) req->buf;
start_periodic_read(plugin, data->grp_config);
break;
}
case NEU_REQRESP_UNSUBSCRIBE_NODE: {
neu_reqresp_unsubscribe_node_t *data =
(neu_reqresp_unsubscribe_node_t *) req->buf;
stop_periodic_read(plugin, data->grp_config);
break;
}
default:
break;
}
return 0;
}
static int modbus_tcp_event_reply(neu_plugin_t * plugin,
neu_event_reply_t *reqply)
{
(void) plugin;
(void) reqply;
return 0;
}
static int modbus_tcp_start(neu_plugin_t *plugin)
{
(void) plugin;
return 0;
}
static int modbus_tcp_stop(neu_plugin_t *plugin)
{
(void) plugin;
return 0;
}
static const neu_plugin_intf_funs_t plugin_intf_funs = {
.open = modbus_tcp_open,
.close = modbus_tcp_close,
.init = modbus_tcp_init,
.uninit = modbus_tcp_uninit,
.start = modbus_tcp_start,
.stop = modbus_tcp_stop,
.config = modbus_tcp_config,
.request = modbus_tcp_request,
.event_reply = modbus_tcp_event_reply,
};
const neu_plugin_module_t neu_plugin_module = {
.version = NEURON_PLUGIN_VER_1_0,
.module_name = "neuron-modbus-tcp-plugin",
.module_descr = "modbus tcp driver plugin",
.intf_funs = &plugin_intf_funs,
};