mirror of
https://github.com/apache/nuttx-apps.git
synced 2026-08-01 20:29:00 +00:00
modbus: add nxmodbus
NxModbus is a lightweight Modbus protocol stack implementation for NuttX RTOS. This commit adds: - nxmodbus stack - nxmbserver - Modbus Server (Slave) example - nxmbclient - Modbus Client (Master) tool Supported Modbus transports: - ASCII - RTU over serial port - TCP - RAW (ADU) for custom transport implementations Signed-off-by: raiden00pl <raiden00@railab.me>
This commit is contained in:
parent
2016c0b6ae
commit
773f69d5d9
36 changed files with 8137 additions and 0 deletions
35
examples/nxmbserver/CMakeLists.txt
Normal file
35
examples/nxmbserver/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# ##############################################################################
|
||||
# apps/examples/nxmbserver/CMakeLists.txt
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more contributor
|
||||
# license agreements. See the NOTICE file distributed with this work for
|
||||
# additional information regarding copyright ownership. The ASF licenses this
|
||||
# file to you under the Apache License, Version 2.0 (the "License"); you may not
|
||||
# use this file except in compliance with the License. You may obtain a copy of
|
||||
# the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations under
|
||||
# the License.
|
||||
#
|
||||
# ##############################################################################
|
||||
|
||||
if(CONFIG_EXAMPLES_NXMBSERVER)
|
||||
nuttx_add_application(
|
||||
NAME
|
||||
${CONFIG_EXAMPLES_NXMBSERVER_PROGNAME}
|
||||
PRIORITY
|
||||
${CONFIG_EXAMPLES_NXMBSERVER_PRIORITY}
|
||||
STACKSIZE
|
||||
${CONFIG_EXAMPLES_NXMBSERVER_STACKSIZE}
|
||||
MODULE
|
||||
${CONFIG_EXAMPLES_NXMBSERVER}
|
||||
SRCS
|
||||
nxmbserver_main.c)
|
||||
endif()
|
||||
33
examples/nxmbserver/Kconfig
Normal file
33
examples/nxmbserver/Kconfig
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
#
|
||||
# For a description of the syntax of this configuration file,
|
||||
# see the file kconfig-language.txt in the NuttX tools repository.
|
||||
#
|
||||
|
||||
config EXAMPLES_NXMBSERVER
|
||||
tristate "NxModbus Server Example"
|
||||
default n
|
||||
depends on NXMODBUS_SERVER
|
||||
---help---
|
||||
Enable the NxModbus server example. This example demonstrates
|
||||
how to create a Modbus server (slave) that responds to client
|
||||
requests. Supports RTU, ASCII, and TCP transports with simulated
|
||||
register data.
|
||||
|
||||
if EXAMPLES_NXMBSERVER
|
||||
|
||||
config EXAMPLES_NXMBSERVER_PROGNAME
|
||||
string "Program name"
|
||||
default "nxmbserver"
|
||||
---help---
|
||||
This is the name of the program that will be used when the NSH ELF
|
||||
program is installed.
|
||||
|
||||
config EXAMPLES_NXMBSERVER_PRIORITY
|
||||
int "NxModbus server task priority"
|
||||
default 100
|
||||
|
||||
config EXAMPLES_NXMBSERVER_STACKSIZE
|
||||
int "NxModbus server stack size"
|
||||
default DEFAULT_TASK_STACKSIZE
|
||||
|
||||
endif # EXAMPLES_NXMBSERVER
|
||||
25
examples/nxmbserver/Make.defs
Normal file
25
examples/nxmbserver/Make.defs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
############################################################################
|
||||
# apps/examples/nxmbserver/Make.defs
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership. The
|
||||
# ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance with the
|
||||
# License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
############################################################################
|
||||
|
||||
ifneq ($(CONFIG_EXAMPLES_NXMBSERVER),)
|
||||
CONFIGURED_APPS += $(APPDIR)/examples/nxmbserver
|
||||
endif
|
||||
32
examples/nxmbserver/Makefile
Normal file
32
examples/nxmbserver/Makefile
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
############################################################################
|
||||
# apps/examples/nxmbserver/Makefile
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership. The
|
||||
# ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance with the
|
||||
# License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
############################################################################
|
||||
|
||||
include $(APPDIR)/Make.defs
|
||||
|
||||
MAINSRC = nxmbserver_main.c
|
||||
|
||||
PROGNAME = $(CONFIG_EXAMPLES_NXMBSERVER_PROGNAME)
|
||||
PRIORITY = $(CONFIG_EXAMPLES_NXMBSERVER_PRIORITY)
|
||||
STACKSIZE = $(CONFIG_EXAMPLES_NXMBSERVER_STACKSIZE)
|
||||
MODULE = $(CONFIG_EXAMPLES_NXMBSERVER)
|
||||
|
||||
include $(APPDIR)/Application.mk
|
||||
543
examples/nxmbserver/nxmbserver_main.c
Normal file
543
examples/nxmbserver/nxmbserver_main.c
Normal file
|
|
@ -0,0 +1,543 @@
|
|||
/****************************************************************************
|
||||
* apps/examples/nxmbserver/nxmbserver_main.c
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership. The
|
||||
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Included Files
|
||||
****************************************************************************/
|
||||
|
||||
#include <nuttx/config.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <nxmodbus/nxmodbus.h>
|
||||
|
||||
/****************************************************************************
|
||||
* Pre-processor Definitions
|
||||
****************************************************************************/
|
||||
|
||||
#define DEFAULT_UNIT_ID 1
|
||||
#define DEFAULT_TCP_PORT 502
|
||||
#define DEFAULT_BAUDRATE 19200
|
||||
|
||||
#define NUM_COILS 100
|
||||
#define NUM_DISCRETE 100
|
||||
#define NUM_INPUT_REGS 100
|
||||
#define NUM_HOLDING_REGS 100
|
||||
|
||||
/****************************************************************************
|
||||
* Private Types
|
||||
****************************************************************************/
|
||||
|
||||
/* Modbus type supported by example */
|
||||
|
||||
enum transport_type_e
|
||||
{
|
||||
TRANSPORT_RTU = 0,
|
||||
TRANSPORT_ASCII,
|
||||
TRANSPORT_TCP
|
||||
};
|
||||
|
||||
/* Modbus registers */
|
||||
|
||||
struct server_data_s
|
||||
{
|
||||
uint8_t coils[NUM_COILS / 8 + 1];
|
||||
uint8_t discrete[NUM_DISCRETE / 8 + 1];
|
||||
uint16_t input_regs[NUM_INPUT_REGS];
|
||||
uint16_t holding_regs[NUM_HOLDING_REGS];
|
||||
};
|
||||
|
||||
/* Modbus server configuration */
|
||||
|
||||
struct server_config_s
|
||||
{
|
||||
enum transport_type_e transport;
|
||||
enum nxmb_parity_e parity;
|
||||
FAR const char *device;
|
||||
FAR const char *bindaddr;
|
||||
uint32_t baudrate;
|
||||
uint16_t port;
|
||||
uint8_t unit_id;
|
||||
};
|
||||
|
||||
/****************************************************************************
|
||||
* Private Data
|
||||
****************************************************************************/
|
||||
|
||||
static struct server_data_s g_data;
|
||||
static volatile bool g_running = true;
|
||||
|
||||
/****************************************************************************
|
||||
* Private Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: signal_handler
|
||||
****************************************************************************/
|
||||
|
||||
static void signal_handler(int signo)
|
||||
{
|
||||
g_running = false;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: show_usage
|
||||
****************************************************************************/
|
||||
|
||||
static void show_usage(FAR const char *progname)
|
||||
{
|
||||
printf("Usage: %s [OPTIONS]\n\n", progname);
|
||||
printf("Transport Options:\n");
|
||||
printf(" -t TYPE Transport type: rtu, ascii, tcp (required)\n");
|
||||
printf(" -d DEVICE Serial device path (for RTU/ASCII)\n");
|
||||
printf(" -b BAUD Baud rate (default: %u)\n", DEFAULT_BAUDRATE);
|
||||
printf(" -p PARITY Parity: none, even, odd (default: even)\n");
|
||||
printf(" -a ADDR Bind address (for TCP, default: 0.0.0.0)\n");
|
||||
printf(" -P PORT TCP port (default: %u)\n", DEFAULT_TCP_PORT);
|
||||
printf("\nModbus Options:\n");
|
||||
printf(" -u UNIT Unit ID (default: %u)\n", DEFAULT_UNIT_ID);
|
||||
printf("\nExample:\n");
|
||||
printf(" %s -t rtu -d /dev/ttyS1 -b 19200\n", progname);
|
||||
printf(" %s -t tcp -P 502\n", progname);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: parse_parity
|
||||
****************************************************************************/
|
||||
|
||||
static int parse_parity(FAR const char *str, FAR enum nxmb_parity_e *parity)
|
||||
{
|
||||
if (strcmp(str, "none") == 0)
|
||||
{
|
||||
*parity = NXMB_PAR_NONE;
|
||||
}
|
||||
else if (strcmp(str, "even") == 0)
|
||||
{
|
||||
*parity = NXMB_PAR_EVEN;
|
||||
}
|
||||
else if (strcmp(str, "odd") == 0)
|
||||
{
|
||||
*parity = NXMB_PAR_ODD;
|
||||
}
|
||||
else
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: parse_transport
|
||||
****************************************************************************/
|
||||
|
||||
static int parse_transport(FAR const char *str,
|
||||
FAR enum transport_type_e *transport)
|
||||
{
|
||||
if (strcmp(str, "rtu") == 0)
|
||||
{
|
||||
*transport = TRANSPORT_RTU;
|
||||
}
|
||||
else if (strcmp(str, "ascii") == 0)
|
||||
{
|
||||
*transport = TRANSPORT_ASCII;
|
||||
}
|
||||
else if (strcmp(str, "tcp") == 0)
|
||||
{
|
||||
*transport = TRANSPORT_TCP;
|
||||
}
|
||||
else
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: coils_callback
|
||||
****************************************************************************/
|
||||
|
||||
static int coils_callback(FAR uint8_t *buf, uint16_t addr, uint16_t count,
|
||||
enum nxmb_regmode_e mode, FAR void *priv)
|
||||
{
|
||||
uint16_t byte_idx;
|
||||
uint16_t bit_idx;
|
||||
uint16_t i;
|
||||
|
||||
if (addr + count > NUM_COILS)
|
||||
{
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
if (mode == NXMB_REG_READ)
|
||||
{
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
byte_idx = (addr + i) / 8;
|
||||
bit_idx = (addr + i) % 8;
|
||||
|
||||
if (g_data.coils[byte_idx] & (1 << bit_idx))
|
||||
{
|
||||
buf[i / 8] |= (1 << (i % 8));
|
||||
}
|
||||
else
|
||||
{
|
||||
buf[i / 8] &= ~(1 << (i % 8));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
byte_idx = (addr + i) / 8;
|
||||
bit_idx = (addr + i) % 8;
|
||||
|
||||
if (buf[i / 8] & (1 << (i % 8)))
|
||||
{
|
||||
g_data.coils[byte_idx] |= (1 << bit_idx);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_data.coils[byte_idx] &= ~(1 << bit_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: discrete_callback
|
||||
****************************************************************************/
|
||||
|
||||
static int discrete_callback(FAR uint8_t *buf, uint16_t addr,
|
||||
uint16_t count, FAR void *priv)
|
||||
{
|
||||
uint16_t byte_idx;
|
||||
uint16_t bit_idx;
|
||||
uint16_t i;
|
||||
|
||||
if (addr + count > NUM_DISCRETE)
|
||||
{
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
byte_idx = (addr + i) / 8;
|
||||
bit_idx = (addr + i) % 8;
|
||||
|
||||
if (g_data.discrete[byte_idx] & (1 << bit_idx))
|
||||
{
|
||||
buf[i / 8] |= (1 << (i % 8));
|
||||
}
|
||||
else
|
||||
{
|
||||
buf[i / 8] &= ~(1 << (i % 8));
|
||||
}
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: input_callback
|
||||
****************************************************************************/
|
||||
|
||||
static int input_callback(FAR uint8_t *buf, uint16_t addr, uint16_t count,
|
||||
FAR void *priv)
|
||||
{
|
||||
uint16_t i;
|
||||
|
||||
if (addr + count > NUM_INPUT_REGS)
|
||||
{
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
buf[i * 2] = (uint8_t)(g_data.input_regs[addr + i] >> 8);
|
||||
buf[i * 2 + 1] = (uint8_t)(g_data.input_regs[addr + i] & 0xff);
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: holding_callback
|
||||
****************************************************************************/
|
||||
|
||||
static int holding_callback(FAR uint8_t *buf, uint16_t addr, uint16_t count,
|
||||
enum nxmb_regmode_e mode, FAR void *priv)
|
||||
{
|
||||
uint16_t i;
|
||||
|
||||
if (addr + count > NUM_HOLDING_REGS)
|
||||
{
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
if (mode == NXMB_REG_READ)
|
||||
{
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
buf[i * 2] = (uint8_t)(g_data.holding_regs[addr + i] >> 8);
|
||||
buf[i * 2 + 1] = (uint8_t)(g_data.holding_regs[addr + i] & 0xff);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
g_data.holding_regs[addr + i] =
|
||||
(uint16_t)(buf[i * 2] << 8) | (uint16_t)buf[i * 2 + 1];
|
||||
}
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: init_data
|
||||
****************************************************************************/
|
||||
|
||||
static void init_data(void)
|
||||
{
|
||||
uint16_t i;
|
||||
|
||||
memset(&g_data, 0, sizeof(g_data));
|
||||
|
||||
for (i = 0; i < NUM_INPUT_REGS; i++)
|
||||
{
|
||||
g_data.input_regs[i] = i * 10;
|
||||
}
|
||||
|
||||
for (i = 0; i < NUM_HOLDING_REGS; i++)
|
||||
{
|
||||
g_data.holding_regs[i] = i * 100;
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Public Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmbserver_main
|
||||
****************************************************************************/
|
||||
|
||||
int main(int argc, FAR char *argv[])
|
||||
{
|
||||
struct server_config_s config;
|
||||
struct nxmb_config_s mb_config;
|
||||
struct nxmb_callbacks_s callbacks;
|
||||
nxmb_handle_t handle;
|
||||
bool transport_set = false;
|
||||
int option;
|
||||
int ret;
|
||||
|
||||
/* Default config */
|
||||
|
||||
memset(&config, 0, sizeof(config));
|
||||
config.baudrate = DEFAULT_BAUDRATE;
|
||||
config.parity = NXMB_PAR_EVEN;
|
||||
config.port = DEFAULT_TCP_PORT;
|
||||
config.unit_id = DEFAULT_UNIT_ID;
|
||||
|
||||
/* Handle CLI */
|
||||
|
||||
while ((option = getopt(argc, argv, "t:d:b:p:a:P:u:h")) != -1)
|
||||
{
|
||||
switch (option)
|
||||
{
|
||||
case 't':
|
||||
if (parse_transport(optarg, &config.transport) < 0)
|
||||
{
|
||||
fprintf(stderr, "Error: invalid transport '%s'\n", optarg);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
transport_set = true;
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
config.device = optarg;
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
config.baudrate = (uint32_t)strtoul(optarg, NULL, 0);
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
if (parse_parity(optarg, &config.parity) < 0)
|
||||
{
|
||||
fprintf(stderr, "Error: invalid parity '%s'\n", optarg);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'a':
|
||||
config.bindaddr = optarg;
|
||||
break;
|
||||
|
||||
case 'P':
|
||||
config.port = (uint16_t)strtoul(optarg, NULL, 0);
|
||||
break;
|
||||
|
||||
case 'u':
|
||||
config.unit_id = (uint8_t)strtoul(optarg, NULL, 0);
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
default:
|
||||
show_usage(argv[0]);
|
||||
return (option == 'h') ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
/* Validate input */
|
||||
|
||||
if (!transport_set)
|
||||
{
|
||||
fprintf(stderr, "Error: transport type (-t) is required\n\n");
|
||||
show_usage(argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if ((config.transport == TRANSPORT_RTU ||
|
||||
config.transport == TRANSPORT_ASCII) &&
|
||||
config.device == NULL)
|
||||
{
|
||||
fprintf(stderr, "Error: device path (-d) required for RTU/ASCII\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* Connect signals */
|
||||
|
||||
signal(SIGINT, signal_handler);
|
||||
signal(SIGTERM, signal_handler);
|
||||
|
||||
/* Init modbus data */
|
||||
|
||||
init_data();
|
||||
|
||||
/* Init modbus stack */
|
||||
|
||||
memset(&callbacks, 0, sizeof(callbacks));
|
||||
callbacks.coil_cb = coils_callback;
|
||||
callbacks.discrete_cb = discrete_callback;
|
||||
callbacks.input_cb = input_callback;
|
||||
callbacks.holding_cb = holding_callback;
|
||||
|
||||
memset(&mb_config, 0, sizeof(mb_config));
|
||||
mb_config.unit_id = config.unit_id;
|
||||
mb_config.is_client = false;
|
||||
|
||||
switch (config.transport)
|
||||
{
|
||||
case TRANSPORT_RTU:
|
||||
mb_config.mode = NXMB_MODE_RTU;
|
||||
mb_config.transport.serial.devpath = config.device;
|
||||
mb_config.transport.serial.baudrate = config.baudrate;
|
||||
mb_config.transport.serial.parity = config.parity;
|
||||
printf("Starting Modbus RTU server on %s (baud=%" PRId32
|
||||
", unit=%u)\n", config.device, config.baudrate,
|
||||
config.unit_id);
|
||||
break;
|
||||
|
||||
case TRANSPORT_ASCII:
|
||||
mb_config.mode = NXMB_MODE_ASCII;
|
||||
mb_config.transport.serial.devpath = config.device;
|
||||
mb_config.transport.serial.baudrate = config.baudrate;
|
||||
mb_config.transport.serial.parity = config.parity;
|
||||
printf("Starting Modbus ASCII server on %s (baud=%" PRId32
|
||||
", unit=%u)\n", config.device, config.baudrate,
|
||||
config.unit_id);
|
||||
break;
|
||||
|
||||
case TRANSPORT_TCP:
|
||||
mb_config.mode = NXMB_MODE_TCP;
|
||||
mb_config.transport.tcp.port = config.port;
|
||||
mb_config.transport.tcp.bindaddr = config.bindaddr;
|
||||
printf("Starting Modbus TCP server on port %u (unit=%u)\n",
|
||||
config.port, config.unit_id);
|
||||
break;
|
||||
}
|
||||
|
||||
ret = nxmb_create(&handle, &mb_config);
|
||||
if (ret < 0)
|
||||
{
|
||||
fprintf(stderr, "Error: failed to create Modbus context: %d\n", ret);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
ret = nxmb_set_callbacks(handle, &callbacks);
|
||||
if (ret < 0)
|
||||
{
|
||||
fprintf(stderr, "Error: failed to set callbacks: %d\n", ret);
|
||||
nxmb_destroy(handle);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
ret = nxmb_enable(handle);
|
||||
if (ret < 0)
|
||||
{
|
||||
fprintf(stderr, "Error: failed to enable context: %d\n", ret);
|
||||
nxmb_destroy(handle);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
printf("Server running. Press Ctrl+C to stop.\n");
|
||||
printf("Register map:\n");
|
||||
printf(" Coils: 1-%d (read/write)\n", NUM_COILS);
|
||||
printf(" Discrete: 1-%d (read-only)\n", NUM_DISCRETE);
|
||||
printf(" Input regs: 1-%d (read-only, value=addr*10)\n",
|
||||
NUM_INPUT_REGS);
|
||||
printf(" Holding regs: 1-%d (read/write, initial=addr*100)\n",
|
||||
NUM_HOLDING_REGS);
|
||||
|
||||
/* Poll loop */
|
||||
|
||||
while (g_running)
|
||||
{
|
||||
ret = nxmb_poll(handle);
|
||||
if (ret < 0 && ret != -EAGAIN)
|
||||
{
|
||||
fprintf(stderr, "Error: poll failed: %d\n", ret);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
printf("\nShutting down...\n");
|
||||
nxmb_disable(handle);
|
||||
nxmb_destroy(handle);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
261
include/nxmodbus/nxmb_client.h
Normal file
261
include/nxmodbus/nxmb_client.h
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
/****************************************************************************
|
||||
* apps/include/nxmodbus/nxmb_client.h
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership. The
|
||||
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef __APPS_INCLUDE_NXMODBUS_NXMB_CLIENT_H
|
||||
#define __APPS_INCLUDE_NXMODBUS_NXMB_CLIENT_H
|
||||
|
||||
/****************************************************************************
|
||||
* Included Files
|
||||
****************************************************************************/
|
||||
|
||||
#include <nuttx/compiler.h>
|
||||
#include <nuttx/config.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <nxmodbus/nxmodbus.h>
|
||||
|
||||
/****************************************************************************
|
||||
* Public Function Prototypes
|
||||
****************************************************************************/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_read_coils
|
||||
*
|
||||
* Description:
|
||||
* Read coil values from a remote unit.
|
||||
*
|
||||
* Input Parameters:
|
||||
* h - The NxModbus client instance.
|
||||
* uid - The remote unit identifier.
|
||||
* addr - The first coil address to read.
|
||||
* count - The number of coils to read.
|
||||
* buf - The destination buffer for the returned coil values.
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_read_coils(nxmb_handle_t h, uint8_t uid, uint16_t addr,
|
||||
uint16_t count, FAR uint8_t *buf);
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_read_discrete
|
||||
*
|
||||
* Description:
|
||||
* Read discrete input values from a remote unit.
|
||||
*
|
||||
* Input Parameters:
|
||||
* h - The NxModbus client instance.
|
||||
* uid - The remote unit identifier.
|
||||
* addr - The first discrete input address to read.
|
||||
* count - The number of discrete inputs to read.
|
||||
* buf - The destination buffer for the returned values.
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_read_discrete(nxmb_handle_t h, uint8_t uid, uint16_t addr,
|
||||
uint16_t count, FAR uint8_t *buf);
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_read_input
|
||||
*
|
||||
* Description:
|
||||
* Read input registers from a remote unit.
|
||||
*
|
||||
* Input Parameters:
|
||||
* h - The NxModbus client instance.
|
||||
* uid - The remote unit identifier.
|
||||
* addr - The first input register address to read.
|
||||
* count - The number of registers to read.
|
||||
* buf - The destination buffer for the returned register values.
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_read_input(nxmb_handle_t h, uint8_t uid, uint16_t addr,
|
||||
uint16_t count, FAR uint16_t *buf);
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_read_holding
|
||||
*
|
||||
* Description:
|
||||
* Read holding registers from a remote unit.
|
||||
*
|
||||
* Input Parameters:
|
||||
* h - The NxModbus client instance.
|
||||
* uid - The remote unit identifier.
|
||||
* addr - The first holding register address to read.
|
||||
* count - The number of registers to read.
|
||||
* buf - The destination buffer for the returned register values.
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_read_holding(nxmb_handle_t h, uint8_t uid, uint16_t addr,
|
||||
uint16_t count, FAR uint16_t *buf);
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_write_coil
|
||||
*
|
||||
* Description:
|
||||
* Write a single coil on a remote unit.
|
||||
*
|
||||
* Input Parameters:
|
||||
* h - The NxModbus client instance.
|
||||
* uid - The remote unit identifier.
|
||||
* addr - The coil address to update.
|
||||
* value - The coil state to write.
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_write_coil(nxmb_handle_t h, uint8_t uid, uint16_t addr, bool value);
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_write_holding
|
||||
*
|
||||
* Description:
|
||||
* Write a single holding register on a remote unit.
|
||||
*
|
||||
* Input Parameters:
|
||||
* h - The NxModbus client instance.
|
||||
* uid - The remote unit identifier.
|
||||
* addr - The holding register address to update.
|
||||
* value - The register value to write.
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_write_holding(nxmb_handle_t h, uint8_t uid, uint16_t addr,
|
||||
uint16_t value);
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_write_coils
|
||||
*
|
||||
* Description:
|
||||
* Write multiple coils on a remote unit.
|
||||
*
|
||||
* Input Parameters:
|
||||
* h - The NxModbus client instance.
|
||||
* uid - The remote unit identifier.
|
||||
* addr - The first coil address to update.
|
||||
* count - The number of coils to write.
|
||||
* buf - The source buffer containing coil values.
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_write_coils(nxmb_handle_t h, uint8_t uid, uint16_t addr,
|
||||
uint16_t count, FAR const uint8_t *buf);
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_write_holdings
|
||||
*
|
||||
* Description:
|
||||
* Write multiple holding registers on a remote unit.
|
||||
*
|
||||
* Input Parameters:
|
||||
* h - The NxModbus client instance.
|
||||
* uid - The remote unit identifier.
|
||||
* addr - The first holding register address to update.
|
||||
* count - The number of registers to write.
|
||||
* buf - The source buffer containing register values.
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_write_holdings(nxmb_handle_t h, uint8_t uid, uint16_t addr,
|
||||
uint16_t count, FAR const uint16_t *buf);
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_readwrite_holdings
|
||||
*
|
||||
* Description:
|
||||
* Perform a combined read/write of holding registers on a remote unit
|
||||
* using FC23 (0x17). The write is performed before the read on the
|
||||
* server side.
|
||||
*
|
||||
* Input Parameters:
|
||||
* h - The NxModbus client instance.
|
||||
* uid - The remote unit identifier.
|
||||
* rd_addr - The first holding register address to read.
|
||||
* rd_count - The number of registers to read (1-125).
|
||||
* rd_buf - The destination buffer for the returned register values.
|
||||
* wr_addr - The first holding register address to write.
|
||||
* wr_count - The number of registers to write (1-121).
|
||||
* wr_buf - The source buffer containing register values to write.
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_readwrite_holdings(nxmb_handle_t h, uint8_t uid, uint16_t rd_addr,
|
||||
uint16_t rd_count, FAR uint16_t *rd_buf,
|
||||
uint16_t wr_addr, uint16_t wr_count,
|
||||
FAR const uint16_t *wr_buf);
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_set_timeout
|
||||
*
|
||||
* Description:
|
||||
* Set the client-side response timeout.
|
||||
*
|
||||
* Input Parameters:
|
||||
* h - The NxModbus client instance.
|
||||
* timeout_ms - The timeout in milliseconds.
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_set_timeout(nxmb_handle_t h, uint32_t timeout_ms);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __APPS_INCLUDE_NXMODBUS_NXMB_CLIENT_H */
|
||||
108
include/nxmodbus/nxmb_raw.h
Normal file
108
include/nxmodbus/nxmb_raw.h
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
/****************************************************************************
|
||||
* apps/include/nxmodbus/nxmb_raw.h
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership. The
|
||||
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef __APPS_INCLUDE_NXMODBUS_NXMB_RAW_H
|
||||
#define __APPS_INCLUDE_NXMODBUS_NXMB_RAW_H
|
||||
|
||||
/****************************************************************************
|
||||
* Included Files
|
||||
****************************************************************************/
|
||||
|
||||
#include <nuttx/compiler.h>
|
||||
#include <nuttx/config.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <nxmodbus/nxmodbus.h>
|
||||
|
||||
/****************************************************************************
|
||||
* Pre-processor Definitions
|
||||
****************************************************************************/
|
||||
|
||||
#define NXMB_RAW_HDR_SIZE 8
|
||||
|
||||
/****************************************************************************
|
||||
* Public Function Prototypes
|
||||
****************************************************************************/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_raw_submit_rx
|
||||
*
|
||||
* Description:
|
||||
* Submit a received raw ADU into the NxModbus protocol core.
|
||||
*
|
||||
* Input Parameters:
|
||||
* h - The NxModbus instance receiving the frame.
|
||||
* adu - The received raw ADU container.
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_raw_submit_rx(nxmb_handle_t h, FAR const struct nxmb_adu_s *adu);
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_raw_put_header
|
||||
*
|
||||
* Description:
|
||||
* Serialize the 8-byte MBAP+FC header for an ADU.
|
||||
*
|
||||
* Input Parameters:
|
||||
* adu - The ADU providing the header fields.
|
||||
* hdr - The destination buffer for the serialized header.
|
||||
*
|
||||
* Returned Value:
|
||||
* None.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
void nxmb_raw_put_header(FAR const struct nxmb_adu_s *adu, FAR uint8_t *hdr);
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_raw_get_header
|
||||
*
|
||||
* Description:
|
||||
* Parse the 8-byte MBAP+FC header into an ADU structure.
|
||||
*
|
||||
* Input Parameters:
|
||||
* adu - The destination ADU structure.
|
||||
* hdr - The source buffer containing the serialized header.
|
||||
*
|
||||
* Returned Value:
|
||||
* None.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
void nxmb_raw_get_header(FAR struct nxmb_adu_s *adu, FAR const uint8_t *hdr);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __APPS_INCLUDE_NXMODBUS_NXMB_RAW_H */
|
||||
378
include/nxmodbus/nxmodbus.h
Normal file
378
include/nxmodbus/nxmodbus.h
Normal file
|
|
@ -0,0 +1,378 @@
|
|||
/****************************************************************************
|
||||
* apps/include/nxmodbus/nxmodbus.h
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership. The
|
||||
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef __APPS_INCLUDE_NXMODBUS_NXMODBUS_H
|
||||
#define __APPS_INCLUDE_NXMODBUS_NXMODBUS_H
|
||||
|
||||
/****************************************************************************
|
||||
* Included Files
|
||||
****************************************************************************/
|
||||
|
||||
#include <nuttx/compiler.h>
|
||||
#include <nuttx/config.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/****************************************************************************
|
||||
* Pre-processor Definitions
|
||||
****************************************************************************/
|
||||
|
||||
#define NXMB_TCP_PORT_DEFAULT 502
|
||||
|
||||
/* Maximum number of PDU data bytes (everything after the FC byte).
|
||||
* Configured via Kconfig (CONFIG_NXMODBUS_ADU_DATA_MAX).
|
||||
*/
|
||||
|
||||
#define NXMB_ADU_DATA_MAX CONFIG_NXMODBUS_ADU_DATA_MAX
|
||||
|
||||
/****************************************************************************
|
||||
* Public Types
|
||||
****************************************************************************/
|
||||
|
||||
/* Opaque handle to an NxModbus instance. */
|
||||
|
||||
typedef FAR struct nxmb_context_s *nxmb_handle_t;
|
||||
|
||||
/* Custom frame handler */
|
||||
|
||||
typedef CODE int (*nxmb_custom_fc_handler_t)(nxmb_handle_t ctx);
|
||||
|
||||
/* Modbus Application Data Unit container.
|
||||
*
|
||||
* Single canonical ADU representation shared by the protocol core and all
|
||||
* transports. Transports parse incoming wire frames into this struct and
|
||||
* serialize this struct back out on transmit.
|
||||
*
|
||||
* trans_id, proto_id, length - MBAP header fields (TCP-only).
|
||||
* unit_id - Modbus address byte.
|
||||
* fc - Modbus function code byte.
|
||||
* data[] - PDU payload after the function code.
|
||||
* crc - RTU/ASCII checksum (serial-only).
|
||||
*
|
||||
* The MBAP `length` field counts the bytes following it: unit_id + fc +
|
||||
* data_count. Transports that don't carry MBAP still populate this field
|
||||
* with the same value so that consumers can derive data_count uniformly:
|
||||
* data_count = adu.length - 2
|
||||
*/
|
||||
|
||||
struct nxmb_adu_s
|
||||
{
|
||||
uint16_t trans_id;
|
||||
uint16_t proto_id;
|
||||
uint16_t length;
|
||||
uint8_t unit_id;
|
||||
uint8_t fc;
|
||||
uint8_t data[NXMB_ADU_DATA_MAX];
|
||||
uint16_t crc;
|
||||
};
|
||||
|
||||
/* Callback used by raw ADU mode to emit a fully-formed frame. */
|
||||
|
||||
typedef int (*nxmb_raw_tx_cb_t)(FAR const struct nxmb_adu_s *adu,
|
||||
FAR void *user_data);
|
||||
|
||||
/* Supported transport modes. */
|
||||
|
||||
enum nxmb_mode_e
|
||||
{
|
||||
NXMB_MODE_INVAL = 0,
|
||||
NXMB_MODE_RTU, /* Modbus RTU */
|
||||
NXMB_MODE_ASCII, /* Modbus ASCII */
|
||||
NXMB_MODE_TCP, /* Modbus TCP */
|
||||
NXMB_MODE_RAW /* Custom ADU transport */
|
||||
};
|
||||
|
||||
/* Serial parity configuration. */
|
||||
|
||||
enum nxmb_parity_e
|
||||
{
|
||||
NXMB_PAR_NONE = 0,
|
||||
NXMB_PAR_EVEN,
|
||||
NXMB_PAR_ODD
|
||||
};
|
||||
|
||||
/* Register access direction passed to callbacks. */
|
||||
|
||||
enum nxmb_regmode_e
|
||||
{
|
||||
NXMB_REG_READ = 0,
|
||||
NXMB_REG_WRITE
|
||||
};
|
||||
|
||||
/* Standard Modbus exception codes. */
|
||||
|
||||
enum nxmb_exception_e
|
||||
{
|
||||
NXMB_EX_NONE = 0x00,
|
||||
NXMB_EX_ILLEGAL_FUNCTION = 0x01,
|
||||
NXMB_EX_ILLEGAL_DATA_ADDRESS = 0x02,
|
||||
NXMB_EX_ILLEGAL_DATA_VALUE = 0x03,
|
||||
NXMB_EX_DEVICE_FAILURE = 0x04,
|
||||
NXMB_EX_ACKNOWLEDGE = 0x05,
|
||||
NXMB_EX_DEVICE_BUSY = 0x06,
|
||||
NXMB_EX_MEMORY_PARITY_ERROR = 0x08,
|
||||
NXMB_EX_GATEWAY_PATH_FAILED = 0x0a,
|
||||
NXMB_EX_GATEWAY_TGT_FAILED = 0x0b
|
||||
};
|
||||
|
||||
/* Serial transport configuration. */
|
||||
|
||||
struct nxmb_serial_config_s
|
||||
{
|
||||
FAR const char *devpath;
|
||||
enum nxmb_parity_e parity;
|
||||
uint32_t baudrate;
|
||||
};
|
||||
|
||||
/* TCP transport configuration.
|
||||
*
|
||||
* For slave mode, bindaddr selects the local address and port selects the
|
||||
* listening port. For client mode, host selects the remote endpoint and
|
||||
* port selects the remote port.
|
||||
*/
|
||||
|
||||
struct nxmb_tcp_config_s
|
||||
{
|
||||
FAR const char *host;
|
||||
FAR const char *bindaddr;
|
||||
uint16_t port;
|
||||
};
|
||||
|
||||
/* Raw ADU transport configuration. */
|
||||
|
||||
struct nxmb_raw_config_s
|
||||
{
|
||||
FAR void *user_data;
|
||||
nxmb_raw_tx_cb_t tx_cb;
|
||||
};
|
||||
|
||||
union nxmb_transport_config_u
|
||||
{
|
||||
struct nxmb_serial_config_s serial;
|
||||
struct nxmb_tcp_config_s tcp;
|
||||
struct nxmb_raw_config_s raw;
|
||||
};
|
||||
|
||||
/* Generic NxModbus instance configuration. */
|
||||
|
||||
struct nxmb_config_s
|
||||
{
|
||||
union nxmb_transport_config_u transport;
|
||||
enum nxmb_mode_e mode;
|
||||
uint8_t unit_id;
|
||||
bool is_client;
|
||||
};
|
||||
|
||||
/* Application callback table for Modbus data model access.
|
||||
*
|
||||
* The protocol stack passes buffers covering a contiguous address range and
|
||||
* the application fills or consumes them depending on the register mode.
|
||||
*/
|
||||
|
||||
struct nxmb_callbacks_s
|
||||
{
|
||||
CODE int (*coil_cb)(FAR uint8_t *buf, uint16_t addr, uint16_t ncoils,
|
||||
enum nxmb_regmode_e mode, FAR void *priv);
|
||||
CODE int (*discrete_cb)(FAR uint8_t *buf, uint16_t addr,
|
||||
uint16_t ndiscrete, FAR void *priv);
|
||||
CODE int (*input_cb)(FAR uint8_t *buf, uint16_t addr, uint16_t nregs,
|
||||
FAR void *priv);
|
||||
CODE int (*holding_cb)(FAR uint8_t *buf, uint16_t addr, uint16_t nregs,
|
||||
enum nxmb_regmode_e mode, FAR void *priv);
|
||||
FAR void *priv;
|
||||
};
|
||||
|
||||
/****************************************************************************
|
||||
* Public Function Prototypes
|
||||
****************************************************************************/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_create
|
||||
*
|
||||
* Description:
|
||||
* Create and initialize an NxModbus instance. This function allocates one
|
||||
* entry from the static NxModbus instance pool and initializes it for the
|
||||
* selected transport mode.
|
||||
*
|
||||
* Input Parameters:
|
||||
* handle - A location to receive the opaque NxModbus handle.
|
||||
* config - The transport and role configuration for the new instance.
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_create(FAR nxmb_handle_t *handle,
|
||||
FAR const struct nxmb_config_s *config);
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_destroy
|
||||
*
|
||||
* Description:
|
||||
* Destroy a previously created NxModbus instance. This function releases
|
||||
* a handle previously obtained from nxmb_create().
|
||||
*
|
||||
* Input Parameters:
|
||||
* handle - The NxModbus instance to destroy.
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_destroy(nxmb_handle_t handle);
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_set_callbacks
|
||||
*
|
||||
* Description:
|
||||
* Register the application callback table for an instance. The registered
|
||||
* callback table defines how the application exposes coils, discrete
|
||||
* inputs, input registers, and holding registers to the protocol stack.
|
||||
*
|
||||
* Input Parameters:
|
||||
* handle - The NxModbus instance to update.
|
||||
* cbs - The callback table to associate with the instance.
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_set_callbacks(nxmb_handle_t handle,
|
||||
FAR const struct nxmb_callbacks_s *cbs);
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_enable
|
||||
*
|
||||
* Description:
|
||||
* Enable the configured transport and protocol handling. This function
|
||||
* prepares the configured transport backend so the instance can start
|
||||
* sending or receiving Modbus ADUs.
|
||||
*
|
||||
* Input Parameters:
|
||||
* handle - The NxModbus instance to enable.
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_enable(nxmb_handle_t handle);
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_disable
|
||||
*
|
||||
* Description:
|
||||
* Disable an active NxModbus instance. This function stops frame
|
||||
* processing and releases any transport state held by an enabled instance.
|
||||
*
|
||||
* Input Parameters:
|
||||
* handle - The NxModbus instance to disable.
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_disable(nxmb_handle_t handle);
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_poll
|
||||
*
|
||||
* Description:
|
||||
* Execute one server-side polling iteration. This function performs a
|
||||
* single poll of the transport backend and processes one server-side
|
||||
* Modbus transaction when data is available.
|
||||
*
|
||||
* Input Parameters:
|
||||
* handle - The NxModbus instance to poll.
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_poll(nxmb_handle_t handle);
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_set_server_id
|
||||
*
|
||||
* Description:
|
||||
* Configure the data returned by FC17 (Report Server ID). The response
|
||||
* contains the server ID byte, a run indicator, and optional additional
|
||||
* data supplied by the application.
|
||||
*
|
||||
* Input Parameters:
|
||||
* handle - The NxModbus instance to update.
|
||||
* id - The server ID byte.
|
||||
* is_running - True if the device is in a running state.
|
||||
* additional - Optional additional data bytes (may be NULL).
|
||||
* addlen - Length of additional data.
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_set_server_id(nxmb_handle_t handle, uint8_t id, bool is_running,
|
||||
FAR const uint8_t *additional, uint16_t addlen);
|
||||
|
||||
#ifdef CONFIG_NXMODBUS_CUSTOM_FC
|
||||
/****************************************************************************
|
||||
* Name: nxmb_register_custom_fc
|
||||
*
|
||||
* Description:
|
||||
* Register a custom Modbus function code handler for an instance.
|
||||
* Custom handlers are checked after standard handlers, so standard
|
||||
* function codes cannot be overridden.
|
||||
*
|
||||
* Input Parameters:
|
||||
* h - The NxModbus instance handle.
|
||||
* fc - The custom function code to register.
|
||||
* handler - The handler function. Receives the instance handle and
|
||||
* operates on ctx->adu in place: the request fields
|
||||
* (adu.fc, adu.data[], adu.length) are overwritten with the
|
||||
* response. Returns zero on success or a negated errno value
|
||||
* on failure.
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_register_custom_fc(nxmb_handle_t h, uint8_t fc,
|
||||
nxmb_custom_fc_handler_t handler);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __APPS_INCLUDE_NXMODBUS_NXMODBUS_H */
|
||||
68
industry/nxmodbus/CMakeLists.txt
Normal file
68
industry/nxmodbus/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
# ##############################################################################
|
||||
# apps/industry/nxmodbus/CMakeLists.txt
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more contributor
|
||||
# license agreements. See the NOTICE file distributed with this work for
|
||||
# additional information regarding copyright ownership. The ASF licenses this
|
||||
# file to you under the Apache License, Version 2.0 (the "License"); you may not
|
||||
# use this file except in compliance with the License. You may obtain a copy of
|
||||
# the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations under
|
||||
# the License.
|
||||
#
|
||||
# ##############################################################################
|
||||
|
||||
if(CONFIG_INDUSTRY_NXMODBUS)
|
||||
|
||||
set(CSRCS)
|
||||
|
||||
# Core sources
|
||||
list(APPEND CSRCS core/nxmb_context.c core/nxmb_utils.c)
|
||||
|
||||
if(CONFIG_NXMODBUS_SERVER)
|
||||
list(APPEND CSRCS core/nxmb_server.c)
|
||||
list(APPEND CSRCS core/nxmb_func.c)
|
||||
list(APPEND CSRCS core/nxmb_func_coils.c core/nxmb_func_discrete.c)
|
||||
list(APPEND CSRCS core/nxmb_func_holding.c core/nxmb_func_input.c)
|
||||
list(APPEND CSRCS core/nxmb_func_diag.c core/nxmb_func_other.c)
|
||||
endif()
|
||||
|
||||
if(CONFIG_NXMODBUS_CLIENT)
|
||||
list(APPEND CSRCS core/nxmb_client.c)
|
||||
endif()
|
||||
|
||||
# Transport layer sources
|
||||
if(CONFIG_NXMODBUS_RTU OR CONFIG_NXMODBUS_ASCII)
|
||||
list(APPEND CSRCS transport/nxmb_serial_common.c)
|
||||
endif()
|
||||
|
||||
if(CONFIG_NXMODBUS_RTU)
|
||||
list(APPEND CSRCS transport/nxmb_crc.c transport/nxmb_serial.c)
|
||||
endif()
|
||||
|
||||
if(CONFIG_NXMODBUS_ASCII)
|
||||
list(APPEND CSRCS transport/nxmb_lrc.c transport/nxmb_ascii.c)
|
||||
endif()
|
||||
|
||||
if(CONFIG_NXMODBUS_RAW_ADU)
|
||||
list(APPEND CSRCS transport/nxmb_raw.c)
|
||||
endif()
|
||||
|
||||
if(CONFIG_NXMODBUS_TCP)
|
||||
list(APPEND CSRCS transport/nxmb_tcp.c)
|
||||
endif()
|
||||
|
||||
if(CSRCS)
|
||||
target_include_directories(apps PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_sources(apps PRIVATE ${CSRCS})
|
||||
endif()
|
||||
|
||||
endif()
|
||||
245
industry/nxmodbus/Kconfig
Normal file
245
industry/nxmodbus/Kconfig
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
#
|
||||
# For a description of the syntax of this configuration file,
|
||||
# see the file kconfig-language.txt in the NuttX tools repository.
|
||||
#
|
||||
|
||||
menuconfig INDUSTRY_NXMODBUS
|
||||
bool "NxModbus - New Modbus Stack"
|
||||
default n
|
||||
---help---
|
||||
NxModbus is a modern Modbus stack for NuttX with multi-instance
|
||||
support, per-instance register callbacks, and pluggable transport
|
||||
backends (RTU, ASCII, TCP, Raw ADU).
|
||||
|
||||
This implementation follows NuttX coding standards and is
|
||||
designed for upstream contribution to nuttx-apps.
|
||||
|
||||
if INDUSTRY_NXMODBUS
|
||||
|
||||
config NXMODBUS_SERVER
|
||||
bool "Server (slave) support"
|
||||
default y
|
||||
---help---
|
||||
Enable Modbus server (slave) support. The server processes
|
||||
incoming Modbus requests and calls registered register
|
||||
callbacks.
|
||||
|
||||
config NXMODBUS_CLIENT
|
||||
bool "Client (master) support"
|
||||
default n
|
||||
---help---
|
||||
Enable Modbus client (master) support. The client sends
|
||||
Modbus requests to a server and receives responses.
|
||||
|
||||
config NXMODBUS_RTU
|
||||
bool "Modbus RTU mode"
|
||||
default y
|
||||
---help---
|
||||
Enable Modbus RTU (Remote Terminal Unit) serial transmission
|
||||
mode. Uses binary encoding with CRC16 error checking.
|
||||
|
||||
config NXMODBUS_ASCII
|
||||
bool "Modbus ASCII mode"
|
||||
default n
|
||||
---help---
|
||||
Enable Modbus ASCII serial transmission mode. Uses
|
||||
hex-encoded text with LRC error checking. Slower but
|
||||
more human-readable than RTU.
|
||||
|
||||
config NXMODBUS_TCP
|
||||
bool "Modbus TCP mode"
|
||||
default n
|
||||
depends on NET_TCP
|
||||
---help---
|
||||
Enable Modbus TCP support. Modbus frames are encapsulated
|
||||
in TCP/IP using the MBAP header format. Requires network
|
||||
stack with TCP support.
|
||||
|
||||
config NXMODBUS_RAW_ADU
|
||||
bool "Raw ADU transport support"
|
||||
default n
|
||||
---help---
|
||||
Enable raw ADU (Application Data Unit) transport mode.
|
||||
The application provides callbacks for transmitting and
|
||||
receiving raw Modbus frames. This enables custom transports
|
||||
such as TLS, CAN, BLE, or MQTT.
|
||||
|
||||
config NXMODBUS_MAX_INSTANCES
|
||||
int "Maximum number of NxModbus instances"
|
||||
default 1
|
||||
range 1 16
|
||||
---help---
|
||||
Maximum number of simultaneous NxModbus instances. Each
|
||||
instance can use a different transport (RTU, TCP, etc.)
|
||||
and configuration. Instances are allocated from a static
|
||||
pool at compile time.
|
||||
|
||||
config NXMODBUS_ADU_DATA_MAX
|
||||
int "ADU data buffer size in bytes"
|
||||
default 252
|
||||
range 4 252
|
||||
---help---
|
||||
Size of the data[] payload in struct nxmb_adu_s, in bytes.
|
||||
This is the PDU body after the function code. The Modbus
|
||||
specification allows up to 252 data bytes (1 fc + 252 data =
|
||||
253-byte PDU max). Reduce this value to save RAM on
|
||||
resource-constrained systems.
|
||||
|
||||
config NXMODBUS_REP_SERVER_ID_BUF
|
||||
int "Report Server ID buffer size"
|
||||
default 32
|
||||
range 4 253
|
||||
---help---
|
||||
Size of the buffer used by FC17 (Report Server ID).
|
||||
This buffer holds the server ID byte, run indicator, and
|
||||
optional additional data configured via nxmb_set_server_id().
|
||||
|
||||
config NXMODBUS_ASCII_TIMEOUT_SEC
|
||||
int "ASCII character timeout (seconds)"
|
||||
default 1
|
||||
range 1 60
|
||||
depends on NXMODBUS_ASCII
|
||||
---help---
|
||||
Character timeout for Modbus ASCII mode in seconds. If no
|
||||
character is received within this period, the current frame
|
||||
is discarded. Default is 1 second per the Modbus specification.
|
||||
|
||||
config NXMODBUS_CLIENT_TIMEOUT_MS
|
||||
int "Client default response timeout (ms)"
|
||||
default 1000
|
||||
range 100 60000
|
||||
depends on NXMODBUS_CLIENT
|
||||
---help---
|
||||
Default response timeout for client (master) mode in
|
||||
milliseconds. If no response is received from the server
|
||||
within this period, the request fails with ETIMEDOUT.
|
||||
Can be overridden at runtime via nxmb_set_timeout().
|
||||
|
||||
config NXMODBUS_TCP_MAX_CLIENTS
|
||||
int "Maximum simultaneous TCP client connections"
|
||||
default 1
|
||||
range 1 8
|
||||
depends on NXMODBUS_TCP
|
||||
---help---
|
||||
Maximum number of simultaneous TCP client connections
|
||||
accepted by a Modbus TCP server instance. Each connection
|
||||
uses one file descriptor. Set to 1 for minimal resource
|
||||
usage.
|
||||
|
||||
config NXMODBUS_TCP_TIMEOUT_SEC
|
||||
int "TCP idle connection timeout (seconds)"
|
||||
default 60
|
||||
range 1 3600
|
||||
depends on NXMODBUS_TCP
|
||||
---help---
|
||||
Idle timeout for TCP connections in seconds. If no data is
|
||||
received on a client connection within this period, the
|
||||
connection is closed. Set to a higher value for slow networks.
|
||||
|
||||
config NXMODBUS_RTU_IDLE_TIMEOUT_MS
|
||||
int "RTU inter-frame idle timeout (ms)"
|
||||
default 50
|
||||
range 1 1000
|
||||
depends on NXMODBUS_RTU
|
||||
---help---
|
||||
Fallback idle timeout in milliseconds used by the RTU
|
||||
transport when waiting for a new frame via select(). This
|
||||
is the maximum time to block waiting for data when no
|
||||
frame is in progress. The T3.5 character timing is still
|
||||
used for frame delimiting within an active reception.
|
||||
|
||||
menu "Function Code Selection"
|
||||
|
||||
config NXMODBUS_FUNC_READ_COILS
|
||||
bool "FC01 Read Coils"
|
||||
default y
|
||||
depends on NXMODBUS_SERVER
|
||||
---help---
|
||||
Enable FC01 Read Coils function code handler.
|
||||
|
||||
config NXMODBUS_FUNC_READ_DISCRETE
|
||||
bool "FC02 Read Discrete Inputs"
|
||||
default y
|
||||
depends on NXMODBUS_SERVER
|
||||
---help---
|
||||
Enable FC02 Read Discrete Inputs function code handler.
|
||||
|
||||
config NXMODBUS_FUNC_READ_HOLDING
|
||||
bool "FC03 Read Holding Registers"
|
||||
default y
|
||||
depends on NXMODBUS_SERVER
|
||||
---help---
|
||||
Enable FC03 Read Holding Registers function code handler.
|
||||
|
||||
config NXMODBUS_FUNC_READ_INPUT
|
||||
bool "FC04 Read Input Registers"
|
||||
default y
|
||||
depends on NXMODBUS_SERVER
|
||||
---help---
|
||||
Enable FC04 Read Input Registers function code handler.
|
||||
|
||||
config NXMODBUS_FUNC_WRITE_COIL
|
||||
bool "FC05 Write Single Coil"
|
||||
default y
|
||||
depends on NXMODBUS_SERVER
|
||||
---help---
|
||||
Enable FC05 Write Single Coil function code handler.
|
||||
|
||||
config NXMODBUS_FUNC_WRITE_HOLDING
|
||||
bool "FC06 Write Single Holding Register"
|
||||
default y
|
||||
depends on NXMODBUS_SERVER
|
||||
---help---
|
||||
Enable FC06 Write Single Holding Register function
|
||||
code handler.
|
||||
|
||||
config NXMODBUS_FUNC_DIAGNOSTICS
|
||||
bool "FC08 Diagnostics"
|
||||
default y
|
||||
depends on NXMODBUS_SERVER
|
||||
---help---
|
||||
Enable FC08 Diagnostics function code handler.
|
||||
Only sub-function 0x0000 (Return Query Data) is supported.
|
||||
|
||||
config NXMODBUS_FUNC_WRITE_COILS
|
||||
bool "FC15 Write Multiple Coils"
|
||||
default y
|
||||
depends on NXMODBUS_SERVER
|
||||
---help---
|
||||
Enable FC15 Write Multiple Coils function code handler.
|
||||
|
||||
config NXMODBUS_FUNC_WRITE_HOLDINGS
|
||||
bool "FC16 Write Multiple Holding Registers"
|
||||
default y
|
||||
depends on NXMODBUS_SERVER
|
||||
---help---
|
||||
Enable FC16 Write Multiple Holding Registers function
|
||||
code handler.
|
||||
|
||||
config NXMODBUS_FUNC_REPORT_SERVER_ID
|
||||
bool "FC17 Report Server ID"
|
||||
default y
|
||||
depends on NXMODBUS_SERVER
|
||||
---help---
|
||||
Enable FC17 Report Server ID function code handler.
|
||||
|
||||
config NXMODBUS_FUNC_READWRITE_HOLDINGS
|
||||
bool "FC23 Read/Write Multiple Holding Registers"
|
||||
default y
|
||||
depends on NXMODBUS_SERVER
|
||||
---help---
|
||||
Enable FC23 Read/Write Multiple Holding Registers
|
||||
function code handler.
|
||||
|
||||
endmenu # Function Code Selection
|
||||
|
||||
config NXMODBUS_CUSTOM_FC
|
||||
bool "Custom function code extension support"
|
||||
default y
|
||||
depends on NXMODBUS
|
||||
---help---
|
||||
Enable support for registering custom Modbus function
|
||||
code handlers. Applications can extend the Modbus
|
||||
function code set via nxmb_register_custom_fc().
|
||||
|
||||
endif # INDUSTRY_NXMODBUS
|
||||
25
industry/nxmodbus/Make.defs
Normal file
25
industry/nxmodbus/Make.defs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
############################################################################
|
||||
# apps/modbus/nxmodbus/Make.defs
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership. The
|
||||
# ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance with the
|
||||
# License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
############################################################################
|
||||
|
||||
ifneq ($(CONFIG_INDUSTRY_NXMODBUS),)
|
||||
CONFIGURED_APPS += $(APPDIR)/industry/nxmodbus
|
||||
endif
|
||||
76
industry/nxmodbus/Makefile
Normal file
76
industry/nxmodbus/Makefile
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
############################################################################
|
||||
# apps/industry/nxmodbus/Makefile
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership. The
|
||||
# ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance with the
|
||||
# License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
############################################################################
|
||||
|
||||
include $(APPDIR)/Make.defs
|
||||
|
||||
# NxModbus Library
|
||||
|
||||
ifeq ($(CONFIG_INDUSTRY_NXMODBUS),y)
|
||||
|
||||
# Core sources
|
||||
CSRCS := core/nxmb_context.c
|
||||
CSRCS += core/nxmb_utils.c
|
||||
|
||||
ifeq ($(CONFIG_NXMODBUS_SERVER),y)
|
||||
CSRCS += core/nxmb_server.c
|
||||
CSRCS += core/nxmb_func.c
|
||||
CSRCS += core/nxmb_func_coils.c
|
||||
CSRCS += core/nxmb_func_discrete.c
|
||||
CSRCS += core/nxmb_func_holding.c
|
||||
CSRCS += core/nxmb_func_input.c
|
||||
CSRCS += core/nxmb_func_diag.c
|
||||
CSRCS += core/nxmb_func_other.c
|
||||
endif
|
||||
|
||||
ifeq ($(CONFIG_NXMODBUS_CLIENT),y)
|
||||
CSRCS += core/nxmb_client.c
|
||||
endif
|
||||
|
||||
# Transport layer sources
|
||||
ifneq (,$(filter y,$(CONFIG_NXMODBUS_RTU) $(CONFIG_NXMODBUS_ASCII)))
|
||||
CSRCS += transport/nxmb_serial_common.c
|
||||
endif
|
||||
|
||||
ifeq ($(CONFIG_NXMODBUS_RTU),y)
|
||||
CSRCS += transport/nxmb_crc.c
|
||||
CSRCS += transport/nxmb_serial.c
|
||||
endif
|
||||
|
||||
ifeq ($(CONFIG_NXMODBUS_ASCII),y)
|
||||
CSRCS += transport/nxmb_lrc.c
|
||||
CSRCS += transport/nxmb_ascii.c
|
||||
endif
|
||||
|
||||
ifeq ($(CONFIG_NXMODBUS_RAW_ADU),y)
|
||||
CSRCS += transport/nxmb_raw.c
|
||||
endif
|
||||
|
||||
ifeq ($(CONFIG_NXMODBUS_TCP),y)
|
||||
CSRCS += transport/nxmb_tcp.c
|
||||
endif
|
||||
|
||||
# Include private headers
|
||||
CFLAGS += ${INCDIR_PREFIX}$(CURDIR)
|
||||
|
||||
endif
|
||||
|
||||
include $(APPDIR)/Application.mk
|
||||
739
industry/nxmodbus/core/nxmb_client.c
Normal file
739
industry/nxmodbus/core/nxmb_client.c
Normal file
|
|
@ -0,0 +1,739 @@
|
|||
/****************************************************************************
|
||||
* apps/industry/nxmodbus/core/nxmb_client.c
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership. The
|
||||
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Included Files
|
||||
****************************************************************************/
|
||||
|
||||
#include <nuttx/config.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <nxmodbus/nxmb_client.h>
|
||||
#include <nxmodbus/nxmodbus.h>
|
||||
|
||||
#include "nxmb_internal.h"
|
||||
|
||||
/****************************************************************************
|
||||
* Private Types
|
||||
****************************************************************************/
|
||||
|
||||
struct nxmb_client_state_s
|
||||
{
|
||||
uint32_t timeout_ms;
|
||||
};
|
||||
|
||||
/****************************************************************************
|
||||
* Private Function Prototypes
|
||||
****************************************************************************/
|
||||
|
||||
static int nxmb_client_tx_wait_rx(nxmb_handle_t ctx,
|
||||
uint8_t expected_uid, uint8_t expected_fc);
|
||||
static int nxmb_client_validate_response(nxmb_handle_t ctx,
|
||||
uint8_t expected_uid,
|
||||
uint8_t expected_fc);
|
||||
|
||||
/****************************************************************************
|
||||
* Private Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_exception_to_errno
|
||||
****************************************************************************/
|
||||
|
||||
static int nxmb_exception_to_errno(uint8_t exception)
|
||||
{
|
||||
switch (exception)
|
||||
{
|
||||
case NXMB_EX_ILLEGAL_FUNCTION:
|
||||
return -ENXIO;
|
||||
|
||||
case NXMB_EX_ILLEGAL_DATA_ADDRESS:
|
||||
return -EFAULT;
|
||||
|
||||
case NXMB_EX_ILLEGAL_DATA_VALUE:
|
||||
return -EINVAL;
|
||||
|
||||
case NXMB_EX_DEVICE_FAILURE:
|
||||
return -EIO;
|
||||
|
||||
case NXMB_EX_ACKNOWLEDGE:
|
||||
return -EBUSY;
|
||||
|
||||
case NXMB_EX_DEVICE_BUSY:
|
||||
return -EBUSY;
|
||||
|
||||
default:
|
||||
return -EPROTO;
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_client_validate_response
|
||||
****************************************************************************/
|
||||
|
||||
static int nxmb_client_validate_response(nxmb_handle_t ctx,
|
||||
uint8_t expected_uid,
|
||||
uint8_t expected_fc)
|
||||
{
|
||||
uint8_t rx_uid;
|
||||
uint8_t rx_fc;
|
||||
|
||||
if (ctx->adu.length < 2)
|
||||
{
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
rx_uid = ctx->adu.unit_id;
|
||||
rx_fc = ctx->adu.fc;
|
||||
|
||||
if (rx_uid != expected_uid)
|
||||
{
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
if (rx_fc == (expected_fc | 0x80))
|
||||
{
|
||||
if (ctx->adu.length < 3)
|
||||
{
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
return nxmb_exception_to_errno(ctx->adu.data[0]);
|
||||
}
|
||||
|
||||
if (rx_fc != expected_fc)
|
||||
{
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_client_tx_wait_rx
|
||||
****************************************************************************/
|
||||
|
||||
static int nxmb_client_tx_wait_rx(nxmb_handle_t ctx, uint8_t expected_uid,
|
||||
uint8_t expected_fc)
|
||||
{
|
||||
FAR struct nxmb_client_state_s *state;
|
||||
uint64_t deadline;
|
||||
int ret;
|
||||
|
||||
if (ctx == NULL || ctx->client_state == NULL)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
state = (FAR struct nxmb_client_state_s *)ctx->client_state;
|
||||
|
||||
ret = ctx->transport_ops->send(ctx);
|
||||
if (ret < 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Broadcast frames do not receive a response */
|
||||
|
||||
if (expected_uid == NXMB_ADDRESS_BROADCAST)
|
||||
{
|
||||
return OK;
|
||||
}
|
||||
|
||||
deadline = nxmb_util_clock_ms() + state->timeout_ms;
|
||||
|
||||
for (; ; )
|
||||
{
|
||||
if (nxmb_util_clock_ms() >= deadline)
|
||||
{
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
ret = ctx->transport_ops->receive(ctx);
|
||||
if (ret > 0)
|
||||
{
|
||||
return nxmb_client_validate_response(ctx, expected_uid,
|
||||
expected_fc);
|
||||
}
|
||||
else if (ret < 0 && ret != -EAGAIN)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_client_read_bits
|
||||
*
|
||||
* Description:
|
||||
* Common helper for FC01 (Read Coils) and FC02 (Read Discrete Inputs).
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static int nxmb_client_read_bits(nxmb_handle_t ctx, uint8_t uid, uint8_t fc,
|
||||
uint16_t addr, uint16_t count,
|
||||
FAR uint8_t *buf)
|
||||
{
|
||||
uint16_t nbytes;
|
||||
int ret;
|
||||
|
||||
nbytes = (count + 7) / 8;
|
||||
|
||||
pthread_mutex_lock(&ctx->lock);
|
||||
|
||||
ctx->adu.unit_id = uid;
|
||||
ctx->adu.fc = fc;
|
||||
nxmb_util_put_u16_be(&ctx->adu.data[0], addr);
|
||||
nxmb_util_put_u16_be(&ctx->adu.data[2], count);
|
||||
ctx->adu.length = 6;
|
||||
|
||||
ret = nxmb_client_tx_wait_rx(ctx, uid, fc);
|
||||
if (ret < 0)
|
||||
{
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (ctx->adu.length < (3 + nbytes) || ctx->adu.data[0] != nbytes)
|
||||
{
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
memcpy(buf, &ctx->adu.data[1], nbytes);
|
||||
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_client_read_regs
|
||||
*
|
||||
* Description:
|
||||
* Common helper for FC03 (Read Holding) and FC04 (Read Input) registers.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static int nxmb_client_read_regs(nxmb_handle_t ctx, uint8_t uid, uint8_t fc,
|
||||
uint16_t addr, uint16_t count,
|
||||
FAR uint16_t *buf)
|
||||
{
|
||||
uint16_t nbytes;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
nbytes = count * 2;
|
||||
|
||||
pthread_mutex_lock(&ctx->lock);
|
||||
|
||||
ctx->adu.unit_id = uid;
|
||||
ctx->adu.fc = fc;
|
||||
nxmb_util_put_u16_be(&ctx->adu.data[0], addr);
|
||||
nxmb_util_put_u16_be(&ctx->adu.data[2], count);
|
||||
ctx->adu.length = 6;
|
||||
|
||||
ret = nxmb_client_tx_wait_rx(ctx, uid, fc);
|
||||
if (ret < 0)
|
||||
{
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (ctx->adu.length < (3 + nbytes) || ctx->adu.data[0] != nbytes)
|
||||
{
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
buf[i] = nxmb_util_get_u16_be(&ctx->adu.data[1 + i * 2]);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Public Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_read_coils
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_read_coils(nxmb_handle_t ctx, uint8_t uid, uint16_t addr,
|
||||
uint16_t count, FAR uint8_t *buf)
|
||||
{
|
||||
DEBUGASSERT(ctx && buf);
|
||||
|
||||
if (uid == NXMB_ADDRESS_BROADCAST || count == 0 || count > 2000)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!ctx->is_client)
|
||||
{
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
return nxmb_client_read_bits(ctx, uid, NXMB_FC_READ_COILS, addr,
|
||||
count, buf);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_read_discrete
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_read_discrete(nxmb_handle_t ctx, uint8_t uid, uint16_t addr,
|
||||
uint16_t count, FAR uint8_t *buf)
|
||||
{
|
||||
DEBUGASSERT(ctx && buf);
|
||||
|
||||
if (uid == NXMB_ADDRESS_BROADCAST || count == 0 || count > 2000)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!ctx->is_client)
|
||||
{
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
return nxmb_client_read_bits(ctx, uid, NXMB_FC_READ_DISCRETE, addr,
|
||||
count, buf);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_read_input
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_read_input(nxmb_handle_t ctx, uint8_t uid, uint16_t addr,
|
||||
uint16_t count, FAR uint16_t *buf)
|
||||
{
|
||||
DEBUGASSERT(ctx && buf);
|
||||
|
||||
if (uid == NXMB_ADDRESS_BROADCAST || count == 0 || count > 125)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!ctx->is_client)
|
||||
{
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
return nxmb_client_read_regs(ctx, uid, NXMB_FC_READ_INPUT, addr,
|
||||
count, buf);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_read_holding
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_read_holding(nxmb_handle_t ctx, uint8_t uid, uint16_t addr,
|
||||
uint16_t count, FAR uint16_t *buf)
|
||||
{
|
||||
DEBUGASSERT(ctx && buf);
|
||||
|
||||
if (uid == NXMB_ADDRESS_BROADCAST || count == 0 || count > 125)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!ctx->is_client)
|
||||
{
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
return nxmb_client_read_regs(ctx, uid, NXMB_FC_READ_HOLDING, addr,
|
||||
count, buf);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_write_coil
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_write_coil(nxmb_handle_t ctx, uint8_t uid, uint16_t addr,
|
||||
bool value)
|
||||
{
|
||||
uint16_t coil_value;
|
||||
int ret;
|
||||
|
||||
DEBUGASSERT(ctx);
|
||||
|
||||
if (!ctx->is_client)
|
||||
{
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
coil_value = value ? 0xff00 : 0x0000;
|
||||
|
||||
pthread_mutex_lock(&ctx->lock);
|
||||
|
||||
ctx->adu.unit_id = uid;
|
||||
ctx->adu.fc = NXMB_FC_WRITE_COIL;
|
||||
nxmb_util_put_u16_be(&ctx->adu.data[0], addr);
|
||||
nxmb_util_put_u16_be(&ctx->adu.data[2], coil_value);
|
||||
ctx->adu.length = 6;
|
||||
|
||||
ret = nxmb_client_tx_wait_rx(ctx, uid, NXMB_FC_WRITE_COIL);
|
||||
if (ret < 0)
|
||||
{
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (uid != NXMB_ADDRESS_BROADCAST && ctx->adu.length < 6)
|
||||
{
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_write_holding
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_write_holding(nxmb_handle_t ctx, uint8_t uid, uint16_t addr,
|
||||
uint16_t value)
|
||||
{
|
||||
int ret;
|
||||
|
||||
DEBUGASSERT(ctx);
|
||||
|
||||
if (!ctx->is_client)
|
||||
{
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&ctx->lock);
|
||||
|
||||
ctx->adu.unit_id = uid;
|
||||
ctx->adu.fc = NXMB_FC_WRITE_HOLDING;
|
||||
nxmb_util_put_u16_be(&ctx->adu.data[0], addr);
|
||||
nxmb_util_put_u16_be(&ctx->adu.data[2], value);
|
||||
ctx->adu.length = 6;
|
||||
|
||||
ret = nxmb_client_tx_wait_rx(ctx, uid, NXMB_FC_WRITE_HOLDING);
|
||||
if (ret < 0)
|
||||
{
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (uid != NXMB_ADDRESS_BROADCAST && ctx->adu.length < 6)
|
||||
{
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_write_coils
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_write_coils(nxmb_handle_t ctx, uint8_t uid, uint16_t addr,
|
||||
uint16_t count, FAR const uint8_t *buf)
|
||||
{
|
||||
uint16_t nbytes;
|
||||
int ret;
|
||||
|
||||
DEBUGASSERT(ctx && buf);
|
||||
|
||||
if (count == 0 || count > 1968)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!ctx->is_client)
|
||||
{
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
nbytes = (count + 7) / 8;
|
||||
|
||||
/* Request layout in adu.data[]: addr(2) + count(2) + bcnt(1) + nbytes */
|
||||
|
||||
if (5 + nbytes > NXMB_ADU_DATA_MAX)
|
||||
{
|
||||
return -EMSGSIZE;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&ctx->lock);
|
||||
|
||||
ctx->adu.unit_id = uid;
|
||||
ctx->adu.fc = NXMB_FC_WRITE_COILS;
|
||||
nxmb_util_put_u16_be(&ctx->adu.data[0], addr);
|
||||
nxmb_util_put_u16_be(&ctx->adu.data[2], count);
|
||||
ctx->adu.data[4] = nbytes;
|
||||
memcpy(&ctx->adu.data[5], buf, nbytes);
|
||||
ctx->adu.length = 7 + nbytes;
|
||||
|
||||
ret = nxmb_client_tx_wait_rx(ctx, uid, NXMB_FC_WRITE_COILS);
|
||||
if (ret < 0)
|
||||
{
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (uid != NXMB_ADDRESS_BROADCAST && ctx->adu.length < 6)
|
||||
{
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_write_holdings
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_write_holdings(nxmb_handle_t ctx, uint8_t uid, uint16_t addr,
|
||||
uint16_t count, FAR const uint16_t *buf)
|
||||
{
|
||||
uint16_t nbytes;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
DEBUGASSERT(ctx && buf);
|
||||
|
||||
if (count == 0 || count > 123)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!ctx->is_client)
|
||||
{
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
nbytes = count * 2;
|
||||
|
||||
/* Request layout in adu.data[]: addr(2) + count(2) + bcnt(1) + nbytes */
|
||||
|
||||
if (5 + nbytes > NXMB_ADU_DATA_MAX)
|
||||
{
|
||||
return -EMSGSIZE;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&ctx->lock);
|
||||
|
||||
ctx->adu.unit_id = uid;
|
||||
ctx->adu.fc = NXMB_FC_WRITE_HOLDINGS;
|
||||
nxmb_util_put_u16_be(&ctx->adu.data[0], addr);
|
||||
nxmb_util_put_u16_be(&ctx->adu.data[2], count);
|
||||
ctx->adu.data[4] = nbytes;
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
nxmb_util_put_u16_be(&ctx->adu.data[5 + i * 2], buf[i]);
|
||||
}
|
||||
|
||||
ctx->adu.length = 7 + nbytes;
|
||||
|
||||
ret = nxmb_client_tx_wait_rx(ctx, uid, NXMB_FC_WRITE_HOLDINGS);
|
||||
if (ret < 0)
|
||||
{
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (uid != NXMB_ADDRESS_BROADCAST && ctx->adu.length < 6)
|
||||
{
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_readwrite_holdings
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_readwrite_holdings(nxmb_handle_t ctx, uint8_t uid, uint16_t rd_addr,
|
||||
uint16_t rd_count, FAR uint16_t *rd_buf,
|
||||
uint16_t wr_addr, uint16_t wr_count,
|
||||
FAR const uint16_t *wr_buf)
|
||||
{
|
||||
uint16_t rd_nbytes;
|
||||
uint16_t wr_nbytes;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
DEBUGASSERT(ctx && rd_buf && wr_buf);
|
||||
|
||||
if (uid == NXMB_ADDRESS_BROADCAST || rd_count == 0 || rd_count > 125 ||
|
||||
wr_count == 0 || wr_count > 121)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!ctx->is_client)
|
||||
{
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
rd_nbytes = rd_count * 2;
|
||||
wr_nbytes = wr_count * 2;
|
||||
|
||||
/* Request layout: rd_addr(2) + rd_qty(2) + wr_addr(2) + wr_qty(2) +
|
||||
* wr_bcnt(1) + wr_nbytes
|
||||
*/
|
||||
|
||||
if (9 + wr_nbytes > NXMB_ADU_DATA_MAX)
|
||||
{
|
||||
return -EMSGSIZE;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&ctx->lock);
|
||||
|
||||
ctx->adu.unit_id = uid;
|
||||
ctx->adu.fc = NXMB_FC_READWRITE_HOLDINGS;
|
||||
nxmb_util_put_u16_be(&ctx->adu.data[0], rd_addr);
|
||||
nxmb_util_put_u16_be(&ctx->adu.data[2], rd_count);
|
||||
nxmb_util_put_u16_be(&ctx->adu.data[4], wr_addr);
|
||||
nxmb_util_put_u16_be(&ctx->adu.data[6], wr_count);
|
||||
ctx->adu.data[8] = (uint8_t)wr_nbytes;
|
||||
|
||||
for (i = 0; i < wr_count; i++)
|
||||
{
|
||||
nxmb_util_put_u16_be(&ctx->adu.data[9 + i * 2], wr_buf[i]);
|
||||
}
|
||||
|
||||
ctx->adu.length = 11 + wr_nbytes;
|
||||
|
||||
ret = nxmb_client_tx_wait_rx(ctx, uid, NXMB_FC_READWRITE_HOLDINGS);
|
||||
if (ret < 0)
|
||||
{
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (ctx->adu.length < (3 + rd_nbytes))
|
||||
{
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
if (ctx->adu.data[0] != rd_nbytes)
|
||||
{
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
for (i = 0; i < rd_count; i++)
|
||||
{
|
||||
rd_buf[i] = nxmb_util_get_u16_be(&ctx->adu.data[1 + i * 2]);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_set_timeout
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_set_timeout(nxmb_handle_t ctx, uint32_t timeout_ms)
|
||||
{
|
||||
FAR struct nxmb_client_state_s *state;
|
||||
|
||||
DEBUGASSERT(ctx && ctx->client_state);
|
||||
|
||||
if (!ctx->is_client)
|
||||
{
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
state = (FAR struct nxmb_client_state_s *)ctx->client_state;
|
||||
|
||||
pthread_mutex_lock(&ctx->lock);
|
||||
state->timeout_ms = timeout_ms;
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_client_init
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_client_init(nxmb_handle_t ctx)
|
||||
{
|
||||
FAR struct nxmb_client_state_s *state;
|
||||
|
||||
DEBUGASSERT(ctx && ctx->is_client);
|
||||
|
||||
state = calloc(1, sizeof(struct nxmb_client_state_s));
|
||||
if (state == NULL)
|
||||
{
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
state->timeout_ms = CONFIG_NXMODBUS_CLIENT_TIMEOUT_MS;
|
||||
|
||||
ctx->client_state = state;
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_client_deinit
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_client_deinit(nxmb_handle_t ctx)
|
||||
{
|
||||
FAR struct nxmb_client_state_s *state;
|
||||
|
||||
DEBUGASSERT(ctx && ctx->client_state);
|
||||
|
||||
state = (FAR struct nxmb_client_state_s *)ctx->client_state;
|
||||
|
||||
free(state);
|
||||
|
||||
ctx->client_state = NULL;
|
||||
|
||||
return OK;
|
||||
}
|
||||
392
industry/nxmodbus/core/nxmb_context.c
Normal file
392
industry/nxmodbus/core/nxmb_context.c
Normal file
|
|
@ -0,0 +1,392 @@
|
|||
/****************************************************************************
|
||||
* apps/industry/nxmodbus/core/nxmb_context.c
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership. The
|
||||
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Included Files
|
||||
****************************************************************************/
|
||||
|
||||
#include <nuttx/config.h>
|
||||
|
||||
#include <nuttx/compiler.h>
|
||||
#include <nuttx/debug.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <nxmodbus/nxmodbus.h>
|
||||
|
||||
#include "nxmb_internal.h"
|
||||
|
||||
/****************************************************************************
|
||||
* Private Data
|
||||
****************************************************************************/
|
||||
|
||||
static struct nxmb_context_s g_nxmb_pool[CONFIG_NXMODBUS_MAX_INSTANCES];
|
||||
static pthread_mutex_t g_pool_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
/****************************************************************************
|
||||
* Public Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_create
|
||||
*
|
||||
* Description:
|
||||
* Create a new NxModbus instance.
|
||||
*
|
||||
* Input Parameters:
|
||||
* handle - Pointer to receive the instance handle
|
||||
* config - Instance configuration
|
||||
*
|
||||
* Returned Value:
|
||||
* 0 on success, negative errno on failure
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_create(FAR nxmb_handle_t *handle,
|
||||
FAR const struct nxmb_config_s *config)
|
||||
{
|
||||
nxmb_handle_t ctx = NULL;
|
||||
int ret = -ENOMEM;
|
||||
int i;
|
||||
|
||||
DEBUGASSERT(handle && config);
|
||||
|
||||
#ifndef CONFIG_NXMODBUS_CLIENT
|
||||
if (config->is_client)
|
||||
{
|
||||
/* Master not supported */
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
#endif
|
||||
|
||||
pthread_mutex_lock(&g_pool_lock);
|
||||
|
||||
for (i = 0; i < CONFIG_NXMODBUS_MAX_INSTANCES; i++)
|
||||
{
|
||||
if (g_nxmb_pool[i].mode == NXMB_MODE_INVAL)
|
||||
{
|
||||
ctx = &g_nxmb_pool[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx != NULL)
|
||||
{
|
||||
memset(ctx, 0, sizeof(struct nxmb_context_s));
|
||||
|
||||
ret = pthread_mutex_init(&ctx->lock, NULL);
|
||||
if (ret == 0)
|
||||
{
|
||||
ctx->mode = config->mode;
|
||||
ctx->unit_id = config->unit_id;
|
||||
ctx->is_client = config->is_client;
|
||||
|
||||
switch (config->mode)
|
||||
{
|
||||
#ifdef CONFIG_NXMODBUS_RTU
|
||||
case NXMB_MODE_RTU:
|
||||
ctx->transport_cfg.serial = config->transport.serial;
|
||||
ctx->transport_ops = &g_nxmb_serial_ops;
|
||||
break;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_NXMODBUS_ASCII
|
||||
case NXMB_MODE_ASCII:
|
||||
ctx->transport_cfg.serial = config->transport.serial;
|
||||
ctx->transport_ops = &g_nxmb_ascii_ops;
|
||||
break;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_NXMODBUS_TCP
|
||||
case NXMB_MODE_TCP:
|
||||
ctx->transport_cfg.tcp = config->transport.tcp;
|
||||
ctx->transport_ops = &g_nxmb_tcp_ops;
|
||||
break;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_NXMODBUS_RAW_ADU
|
||||
case NXMB_MODE_RAW:
|
||||
ctx->transport_cfg.raw = config->transport.raw;
|
||||
ctx->transport_ops = &g_nxmb_raw_ops;
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
pthread_mutex_destroy(&ctx->lock);
|
||||
memset(ctx, 0, sizeof(struct nxmb_context_s));
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
*handle = ctx;
|
||||
ret = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
memset(ctx, 0, sizeof(struct nxmb_context_s));
|
||||
ret = -ret;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
pthread_mutex_unlock(&g_pool_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_destroy
|
||||
*
|
||||
* Description:
|
||||
* Destroy an NxModbus instance.
|
||||
*
|
||||
* Input Parameters:
|
||||
* handle - Instance handle
|
||||
*
|
||||
* Returned Value:
|
||||
* 0 on success, negative errno on failure
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_destroy(nxmb_handle_t ctx)
|
||||
{
|
||||
#ifdef CONFIG_NXMODBUS_CUSTOM_FC
|
||||
FAR struct nxmb_custom_fc_s *entry;
|
||||
FAR struct nxmb_custom_fc_s *next;
|
||||
#endif
|
||||
|
||||
DEBUGASSERT(ctx);
|
||||
|
||||
pthread_mutex_lock(&g_pool_lock);
|
||||
pthread_mutex_lock(&ctx->lock);
|
||||
|
||||
if (ctx->enabled)
|
||||
{
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
pthread_mutex_unlock(&g_pool_lock);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
pthread_mutex_destroy(&ctx->lock);
|
||||
|
||||
#ifdef CONFIG_NXMODBUS_CUSTOM_FC
|
||||
entry = ctx->custom_fc_list;
|
||||
while (entry != NULL)
|
||||
{
|
||||
next = entry->next;
|
||||
free(entry);
|
||||
entry = next;
|
||||
}
|
||||
#endif
|
||||
|
||||
memset(ctx, 0, sizeof(struct nxmb_context_s));
|
||||
pthread_mutex_unlock(&g_pool_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_set_callbacks
|
||||
*
|
||||
* Description:
|
||||
* Set application callbacks for register access.
|
||||
*
|
||||
* Input Parameters:
|
||||
* handle - Instance handle
|
||||
* callbacks - Pointer to callback structure
|
||||
*
|
||||
* Returned Value:
|
||||
* 0 on success, negative errno on failure
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_set_callbacks(nxmb_handle_t ctx,
|
||||
FAR const struct nxmb_callbacks_s *callbacks)
|
||||
{
|
||||
DEBUGASSERT(ctx);
|
||||
|
||||
pthread_mutex_lock(&ctx->lock);
|
||||
ctx->callbacks = callbacks;
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_enable
|
||||
*
|
||||
* Description:
|
||||
* Enable an NxModbus instance and initialize transport.
|
||||
*
|
||||
* Input Parameters:
|
||||
* handle - Instance handle
|
||||
*
|
||||
* Returned Value:
|
||||
* 0 on success, negative errno on failure
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_enable(nxmb_handle_t ctx)
|
||||
{
|
||||
int ret;
|
||||
|
||||
DEBUGASSERT(ctx && ctx->transport_ops && ctx->transport_ops->init);
|
||||
|
||||
pthread_mutex_lock(&ctx->lock);
|
||||
|
||||
if (ctx->enabled)
|
||||
{
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_NXMODBUS_CLIENT
|
||||
if (ctx->is_client)
|
||||
{
|
||||
ret = nxmb_client_init(ctx);
|
||||
if (ret < 0)
|
||||
{
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Initialize transport */
|
||||
|
||||
ret = ctx->transport_ops->init(ctx);
|
||||
if (ret < 0)
|
||||
{
|
||||
#ifdef CONFIG_NXMODBUS_CLIENT
|
||||
if (ctx->is_client)
|
||||
{
|
||||
nxmb_client_deinit(ctx);
|
||||
}
|
||||
#endif
|
||||
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ctx->enabled = true;
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_set_server_id
|
||||
*
|
||||
* Description:
|
||||
* Configure the data returned by FC17 (Report Server ID).
|
||||
*
|
||||
* Input Parameters:
|
||||
* handle - Instance handle
|
||||
* id - Server ID byte
|
||||
* is_running - True if device is running
|
||||
* additional - Optional additional data (may be NULL)
|
||||
* addlen - Length of additional data
|
||||
*
|
||||
* Returned Value:
|
||||
* 0 on success, negative errno on failure
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_set_server_id(nxmb_handle_t ctx, uint8_t id, bool is_running,
|
||||
FAR const uint8_t *additional, uint16_t addlen)
|
||||
{
|
||||
DEBUGASSERT(ctx);
|
||||
|
||||
/* The first two bytes are server ID and run indicator.
|
||||
* The rest is optional additional data.
|
||||
*/
|
||||
|
||||
if (addlen + 2 > CONFIG_NXMODBUS_REP_SERVER_ID_BUF)
|
||||
{
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&ctx->lock);
|
||||
|
||||
ctx->server_id_len = 0;
|
||||
ctx->server_id_buf[ctx->server_id_len++] = id;
|
||||
ctx->server_id_buf[ctx->server_id_len++] =
|
||||
(uint8_t)(is_running ? 0xff : 0x00);
|
||||
|
||||
if (addlen > 0 && additional != NULL)
|
||||
{
|
||||
memcpy(&ctx->server_id_buf[ctx->server_id_len], additional,
|
||||
(size_t)addlen);
|
||||
ctx->server_id_len += addlen;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_disable
|
||||
*
|
||||
* Description:
|
||||
* Disable an NxModbus instance and deinitialize transport.
|
||||
*
|
||||
* Input Parameters:
|
||||
* handle - Instance handle
|
||||
*
|
||||
* Returned Value:
|
||||
* 0 on success, negative errno on failure
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_disable(nxmb_handle_t ctx)
|
||||
{
|
||||
DEBUGASSERT(ctx && ctx->transport_ops && ctx->transport_ops->deinit);
|
||||
|
||||
pthread_mutex_lock(&ctx->lock);
|
||||
|
||||
if (!ctx->enabled)
|
||||
{
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ctx->transport_ops->deinit(ctx);
|
||||
|
||||
#ifdef CONFIG_NXMODBUS_CLIENT
|
||||
if (ctx->is_client)
|
||||
{
|
||||
nxmb_client_deinit(ctx);
|
||||
}
|
||||
#endif
|
||||
|
||||
ctx->enabled = false;
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
354
industry/nxmodbus/core/nxmb_func.c
Normal file
354
industry/nxmodbus/core/nxmb_func.c
Normal file
|
|
@ -0,0 +1,354 @@
|
|||
/****************************************************************************
|
||||
* apps/industry/nxmodbus/core/nxmb_func.c
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership. The
|
||||
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Included Files
|
||||
****************************************************************************/
|
||||
|
||||
#include <nuttx/config.h>
|
||||
|
||||
#include <nuttx/compiler.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <nxmodbus/nxmodbus.h>
|
||||
|
||||
#include "nxmb_internal.h"
|
||||
|
||||
/****************************************************************************
|
||||
* Pre-processor Definitions
|
||||
****************************************************************************/
|
||||
|
||||
#define NXMB_EXCEPTION_FLAG 0x80
|
||||
|
||||
/****************************************************************************
|
||||
* Private Types
|
||||
****************************************************************************/
|
||||
|
||||
/* Function code handler signature */
|
||||
|
||||
typedef enum nxmb_exception_e (*nxmb_fc_handler_t)(nxmb_handle_t ctx);
|
||||
|
||||
/* Dispatch table entry */
|
||||
|
||||
struct nxmb_fc_entry_s
|
||||
{
|
||||
uint8_t fc;
|
||||
nxmb_fc_handler_t handler;
|
||||
};
|
||||
|
||||
/****************************************************************************
|
||||
* Private Data
|
||||
****************************************************************************/
|
||||
|
||||
/* Standard function code dispatch table */
|
||||
|
||||
static const struct nxmb_fc_entry_s g_nxmb_fc_table[] =
|
||||
{
|
||||
#ifdef CONFIG_NXMODBUS_FUNC_READ_COILS
|
||||
{ NXMB_FC_READ_COILS, nxmb_fc01_read_coils },
|
||||
#endif
|
||||
#ifdef CONFIG_NXMODBUS_FUNC_READ_DISCRETE
|
||||
{ NXMB_FC_READ_DISCRETE, nxmb_fc02_read_discrete },
|
||||
#endif
|
||||
#ifdef CONFIG_NXMODBUS_FUNC_READ_HOLDING
|
||||
{ NXMB_FC_READ_HOLDING, nxmb_fc03_read_holding },
|
||||
#endif
|
||||
#ifdef CONFIG_NXMODBUS_FUNC_READ_INPUT
|
||||
{ NXMB_FC_READ_INPUT, nxmb_fc04_read_input },
|
||||
#endif
|
||||
#ifdef CONFIG_NXMODBUS_FUNC_WRITE_COIL
|
||||
{ NXMB_FC_WRITE_COIL, nxmb_fc05_write_coil },
|
||||
#endif
|
||||
#ifdef CONFIG_NXMODBUS_FUNC_WRITE_HOLDING
|
||||
{ NXMB_FC_WRITE_HOLDING, nxmb_fc06_write_holding },
|
||||
#endif
|
||||
#ifdef CONFIG_NXMODBUS_FUNC_DIAGNOSTICS
|
||||
{ NXMB_FC_DIAGNOSTICS, nxmb_fc08_diagnostics },
|
||||
#endif
|
||||
#ifdef CONFIG_NXMODBUS_FUNC_WRITE_COILS
|
||||
{ NXMB_FC_WRITE_COILS, nxmb_fc15_write_coils },
|
||||
#endif
|
||||
#ifdef CONFIG_NXMODBUS_FUNC_WRITE_HOLDINGS
|
||||
{ NXMB_FC_WRITE_HOLDINGS, nxmb_fc16_write_holdings },
|
||||
#endif
|
||||
#ifdef CONFIG_NXMODBUS_FUNC_REPORT_SERVER_ID
|
||||
{ NXMB_FC_REPORT_SERVER_ID, nxmb_fc17_report_server_id },
|
||||
#endif
|
||||
#ifdef CONFIG_NXMODBUS_FUNC_READWRITE_HOLDINGS
|
||||
{ NXMB_FC_READWRITE_HOLDINGS, nxmb_fc23_readwrite_holding }
|
||||
#endif
|
||||
};
|
||||
|
||||
#define NXMB_FC_TABLE_SIZE \
|
||||
(sizeof(g_nxmb_fc_table) / sizeof(g_nxmb_fc_table[0]))
|
||||
|
||||
/****************************************************************************
|
||||
* Private Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_build_exception
|
||||
*
|
||||
* Description:
|
||||
* Build a Modbus exception response in ctx->adu in place.
|
||||
*
|
||||
* Input Parameters:
|
||||
* ctx - Instance context
|
||||
* fc - Function code
|
||||
* exception - NxModbus exception code
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static void nxmb_build_exception(nxmb_handle_t ctx, uint8_t fc,
|
||||
enum nxmb_exception_e exception)
|
||||
{
|
||||
ctx->adu.fc = fc | NXMB_EXCEPTION_FLAG;
|
||||
ctx->adu.data[0] = (uint8_t)exception;
|
||||
|
||||
/* unit_id + fc + 1 exception byte */
|
||||
|
||||
ctx->adu.length = 3;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_lookup_standard_fc
|
||||
*
|
||||
* Description:
|
||||
* Look up a standard function code handler in the dispatch table.
|
||||
*
|
||||
* Input Parameters:
|
||||
* fc - Function code to look up
|
||||
*
|
||||
* Returned Value:
|
||||
* Handler function pointer, or NULL if not found
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static nxmb_fc_handler_t nxmb_lookup_standard_fc(uint8_t fc)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < NXMB_FC_TABLE_SIZE; i++)
|
||||
{
|
||||
if (g_nxmb_fc_table[i].fc == fc)
|
||||
{
|
||||
return g_nxmb_fc_table[i].handler;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_NXMODBUS_CUSTOM_FC
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_lookup_custom_fc
|
||||
*
|
||||
* Description:
|
||||
* Look up a custom function code handler in the instance list.
|
||||
*
|
||||
* Input Parameters:
|
||||
* ctx - Instance context
|
||||
* fc - Function code to look up
|
||||
*
|
||||
* Returned Value:
|
||||
* Handler function pointer, or NULL if not found
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static nxmb_custom_fc_handler_t nxmb_lookup_custom_fc(
|
||||
nxmb_handle_t ctx, uint8_t fc)
|
||||
{
|
||||
FAR struct nxmb_custom_fc_s *entry;
|
||||
|
||||
for (entry = ctx->custom_fc_list; entry != NULL; entry = entry->next)
|
||||
{
|
||||
if (entry->fc == fc)
|
||||
{
|
||||
return entry->handler;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_NXMODBUS_CUSTOM_FC */
|
||||
|
||||
/****************************************************************************
|
||||
* Public Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_dispatch_function
|
||||
*
|
||||
* Description:
|
||||
* Dispatch a Modbus function code to the appropriate handler. Operates in
|
||||
* place on ctx->adu. Checks standard handlers first, then custom handlers
|
||||
* if enabled. Builds an exception response if no handler is found or the
|
||||
* handler fails.
|
||||
*
|
||||
* Input Parameters:
|
||||
* ctx - Instance context
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_dispatch_function(nxmb_handle_t ctx)
|
||||
{
|
||||
nxmb_fc_handler_t handler;
|
||||
enum nxmb_exception_e exception;
|
||||
uint8_t fc;
|
||||
#ifdef CONFIG_NXMODBUS_CUSTOM_FC
|
||||
nxmb_custom_fc_handler_t custom_handler;
|
||||
int ret;
|
||||
#endif
|
||||
|
||||
DEBUGASSERT(ctx);
|
||||
|
||||
if (ctx->adu.length < 2)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
fc = ctx->adu.fc;
|
||||
|
||||
/* Look up standard handler first */
|
||||
|
||||
handler = nxmb_lookup_standard_fc(fc);
|
||||
|
||||
#ifdef CONFIG_NXMODBUS_CUSTOM_FC
|
||||
|
||||
/* If not found, check custom handlers */
|
||||
|
||||
if (handler == NULL)
|
||||
{
|
||||
custom_handler = nxmb_lookup_custom_fc(ctx, fc);
|
||||
if (custom_handler != NULL)
|
||||
{
|
||||
ret = custom_handler(ctx);
|
||||
if (ret < 0)
|
||||
{
|
||||
nxmb_build_exception(ctx, fc, NXMB_EX_DEVICE_FAILURE);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* If no handler found, return illegal function exception */
|
||||
|
||||
if (handler == NULL)
|
||||
{
|
||||
nxmb_build_exception(ctx, fc, NXMB_EX_ILLEGAL_FUNCTION);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Call the handler */
|
||||
|
||||
exception = handler(ctx);
|
||||
|
||||
/* If handler returned exception, build exception response */
|
||||
|
||||
if (exception != NXMB_EX_NONE)
|
||||
{
|
||||
nxmb_build_exception(ctx, fc, exception);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_NXMODBUS_CUSTOM_FC
|
||||
/****************************************************************************
|
||||
* Name: nxmb_register_custom_fc
|
||||
*
|
||||
* Description:
|
||||
* Register a custom Modbus function code handler for an instance.
|
||||
*
|
||||
* Custom handlers are checked after standard handlers, so standard
|
||||
* function codes (FC01-FC16) cannot be overridden.
|
||||
*
|
||||
* Input Parameters:
|
||||
* h - The NxModbus instance handle
|
||||
* fc - The custom function code to register
|
||||
* handler - The handler function
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_register_custom_fc(nxmb_handle_t ctx, uint8_t fc,
|
||||
nxmb_custom_fc_handler_t handler)
|
||||
{
|
||||
FAR struct nxmb_custom_fc_s *entry;
|
||||
FAR struct nxmb_custom_fc_s *new_entry;
|
||||
|
||||
DEBUGASSERT(ctx && handler);
|
||||
|
||||
/* Check if FC is already a standard handler */
|
||||
|
||||
if (nxmb_lookup_standard_fc(fc) != NULL)
|
||||
{
|
||||
return -EEXIST;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&ctx->lock);
|
||||
|
||||
/* Check if FC is already registered as custom */
|
||||
|
||||
for (entry = ctx->custom_fc_list; entry != NULL; entry = entry->next)
|
||||
{
|
||||
if (entry->fc == fc)
|
||||
{
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
return -EEXIST;
|
||||
}
|
||||
}
|
||||
|
||||
/* Allocate new entry */
|
||||
|
||||
new_entry = malloc(sizeof(struct nxmb_custom_fc_s));
|
||||
if (new_entry == NULL)
|
||||
{
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Initialize and prepend to list */
|
||||
|
||||
new_entry->fc = fc;
|
||||
new_entry->handler = handler;
|
||||
new_entry->next = ctx->custom_fc_list;
|
||||
ctx->custom_fc_list = new_entry;
|
||||
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_NXMODBUS_CUSTOM_FC */
|
||||
259
industry/nxmodbus/core/nxmb_func_coils.c
Normal file
259
industry/nxmodbus/core/nxmb_func_coils.c
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
/****************************************************************************
|
||||
* apps/industry/nxmodbus/core/nxmb_func_coils.c
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership. The
|
||||
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Included Files
|
||||
****************************************************************************/
|
||||
|
||||
#include <nuttx/config.h>
|
||||
|
||||
#include <nuttx/compiler.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <nxmodbus/nxmodbus.h>
|
||||
|
||||
#include "nxmb_internal.h"
|
||||
|
||||
/****************************************************************************
|
||||
* Pre-processor Definitions
|
||||
****************************************************************************/
|
||||
|
||||
/* Offsets are in adu.data[]; lengths refer to data only. */
|
||||
|
||||
/* FC01 Read Coils */
|
||||
|
||||
#define NXMB_FC01_ADDR_OFF 0
|
||||
#define NXMB_FC01_QTY_OFF 2
|
||||
#define NXMB_FC01_REQ_DATA_LEN 4
|
||||
|
||||
/* FC05 Write Single Coil */
|
||||
|
||||
#define NXMB_FC05_ADDR_OFF 0
|
||||
#define NXMB_FC05_VALUE_OFF 2
|
||||
#define NXMB_FC05_REQ_DATA_LEN 4
|
||||
|
||||
/* FC15 Write Multiple Coils */
|
||||
|
||||
#define NXMB_FC15_ADDR_OFF 0
|
||||
#define NXMB_FC15_QTY_OFF 2
|
||||
#define NXMB_FC15_BCNT_OFF 4
|
||||
#define NXMB_FC15_DATA_OFF 5
|
||||
#define NXMB_FC15_RESP_DATA_LEN 4
|
||||
|
||||
/* Maximum coil quantity per Modbus specification.
|
||||
* FC01/FC02 read max is 2000 (0x07D0).
|
||||
* FC15 write max is 1968 (0x07B0).
|
||||
*/
|
||||
|
||||
#define NXMB_COIL_READ_QTY_MAX 2000
|
||||
#define NXMB_COIL_WRITE_QTY_MAX 1968
|
||||
|
||||
/* Coil ON/OFF wire values for FC05 */
|
||||
|
||||
#define NXMB_COIL_ON 0xff00
|
||||
#define NXMB_COIL_OFF 0x0000
|
||||
|
||||
/****************************************************************************
|
||||
* Public Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_fc01_read_coils
|
||||
****************************************************************************/
|
||||
|
||||
enum nxmb_exception_e nxmb_fc01_read_coils(nxmb_handle_t ctx)
|
||||
{
|
||||
uint16_t data_len = ctx->adu.length - 2;
|
||||
uint16_t addr;
|
||||
uint16_t qty;
|
||||
uint8_t nbytes;
|
||||
int ret;
|
||||
|
||||
if (data_len != NXMB_FC01_REQ_DATA_LEN)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_VALUE;
|
||||
}
|
||||
|
||||
if (ctx->callbacks == NULL || ctx->callbacks->coil_cb == NULL)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_FUNCTION;
|
||||
}
|
||||
|
||||
addr = nxmb_util_get_u16_be(&ctx->adu.data[NXMB_FC01_ADDR_OFF]);
|
||||
qty = nxmb_util_get_u16_be(&ctx->adu.data[NXMB_FC01_QTY_OFF]);
|
||||
|
||||
if (qty < 1 || qty > NXMB_COIL_READ_QTY_MAX)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_VALUE;
|
||||
}
|
||||
|
||||
if ((uint32_t)addr + qty > 0xffffu)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_ADDRESS;
|
||||
}
|
||||
|
||||
nbytes = (uint8_t)((qty + 7) / 8);
|
||||
|
||||
/* Response is byte_count + coil data */
|
||||
|
||||
if (1 + nbytes > NXMB_ADU_DATA_MAX)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_VALUE;
|
||||
}
|
||||
|
||||
/* Build response: byte_count + coil data */
|
||||
|
||||
ctx->adu.fc = NXMB_FC_READ_COILS;
|
||||
ctx->adu.data[0] = nbytes;
|
||||
|
||||
/* Zero data area so unused trailing bits are clear */
|
||||
|
||||
memset(&ctx->adu.data[1], 0, nbytes);
|
||||
|
||||
ret = ctx->callbacks->coil_cb(&ctx->adu.data[1], addr, qty, NXMB_REG_READ,
|
||||
ctx->callbacks->priv);
|
||||
if (ret != 0)
|
||||
{
|
||||
return nxmb_cb_ret_to_exception(ret);
|
||||
}
|
||||
|
||||
ctx->adu.length = (uint16_t)(2 + 1 + nbytes);
|
||||
return NXMB_EX_NONE;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_fc05_write_coil
|
||||
****************************************************************************/
|
||||
|
||||
enum nxmb_exception_e nxmb_fc05_write_coil(nxmb_handle_t ctx)
|
||||
{
|
||||
uint16_t data_len = ctx->adu.length - 2;
|
||||
uint16_t addr;
|
||||
uint16_t value;
|
||||
uint8_t coil_buf[2];
|
||||
int ret;
|
||||
|
||||
if (data_len != NXMB_FC05_REQ_DATA_LEN)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_VALUE;
|
||||
}
|
||||
|
||||
if (ctx->callbacks == NULL || ctx->callbacks->coil_cb == NULL)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_FUNCTION;
|
||||
}
|
||||
|
||||
addr = nxmb_util_get_u16_be(&ctx->adu.data[NXMB_FC05_ADDR_OFF]);
|
||||
value = nxmb_util_get_u16_be(&ctx->adu.data[NXMB_FC05_VALUE_OFF]);
|
||||
|
||||
if (addr == 0xffffu)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_ADDRESS;
|
||||
}
|
||||
|
||||
if (value != NXMB_COIL_ON && value != NXMB_COIL_OFF)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_VALUE;
|
||||
}
|
||||
|
||||
/* Convert wire value to single-bit buffer for callback */
|
||||
|
||||
coil_buf[0] = (value == NXMB_COIL_ON) ? 1 : 0;
|
||||
coil_buf[1] = 0;
|
||||
|
||||
ret = ctx->callbacks->coil_cb(coil_buf, addr, 1, NXMB_REG_WRITE,
|
||||
ctx->callbacks->priv);
|
||||
if (ret != 0)
|
||||
{
|
||||
return nxmb_cb_ret_to_exception(ret);
|
||||
}
|
||||
|
||||
/* Response echoes the request — adu.length is already correct */
|
||||
|
||||
return NXMB_EX_NONE;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_fc15_write_coils
|
||||
****************************************************************************/
|
||||
|
||||
enum nxmb_exception_e nxmb_fc15_write_coils(nxmb_handle_t ctx)
|
||||
{
|
||||
uint16_t data_len = ctx->adu.length - 2;
|
||||
uint16_t addr;
|
||||
uint16_t qty;
|
||||
uint8_t byte_count;
|
||||
uint8_t byte_count_exp;
|
||||
int ret;
|
||||
|
||||
if (data_len < NXMB_FC15_DATA_OFF)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_VALUE;
|
||||
}
|
||||
|
||||
if (ctx->callbacks == NULL || ctx->callbacks->coil_cb == NULL)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_FUNCTION;
|
||||
}
|
||||
|
||||
addr = nxmb_util_get_u16_be(&ctx->adu.data[NXMB_FC15_ADDR_OFF]);
|
||||
qty = nxmb_util_get_u16_be(&ctx->adu.data[NXMB_FC15_QTY_OFF]);
|
||||
byte_count = ctx->adu.data[NXMB_FC15_BCNT_OFF];
|
||||
|
||||
if (qty < 1 || qty > NXMB_COIL_WRITE_QTY_MAX)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_VALUE;
|
||||
}
|
||||
|
||||
if ((uint32_t)addr + qty > 0xffffu)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_ADDRESS;
|
||||
}
|
||||
|
||||
byte_count_exp = (uint8_t)((qty + 7) / 8);
|
||||
|
||||
if (byte_count != byte_count_exp)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_VALUE;
|
||||
}
|
||||
|
||||
/* Verify total data length matches header + payload */
|
||||
|
||||
if (data_len != (uint16_t)(NXMB_FC15_DATA_OFF + byte_count))
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_VALUE;
|
||||
}
|
||||
|
||||
ret = ctx->callbacks->coil_cb(&ctx->adu.data[NXMB_FC15_DATA_OFF], addr,
|
||||
qty, NXMB_REG_WRITE, ctx->callbacks->priv);
|
||||
if (ret != 0)
|
||||
{
|
||||
return nxmb_cb_ret_to_exception(ret);
|
||||
}
|
||||
|
||||
/* Response: addr(2) + qty(2) — already in data[0..3] from request */
|
||||
|
||||
ctx->adu.length = (uint16_t)(2 + NXMB_FC15_RESP_DATA_LEN);
|
||||
return NXMB_EX_NONE;
|
||||
}
|
||||
83
industry/nxmodbus/core/nxmb_func_diag.c
Normal file
83
industry/nxmodbus/core/nxmb_func_diag.c
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
/****************************************************************************
|
||||
* apps/industry/nxmodbus/core/nxmb_func_diag.c
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership. The
|
||||
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Included Files
|
||||
****************************************************************************/
|
||||
|
||||
#include <nuttx/config.h>
|
||||
|
||||
#include <nuttx/compiler.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <nxmodbus/nxmodbus.h>
|
||||
|
||||
#include "nxmb_internal.h"
|
||||
|
||||
/****************************************************************************
|
||||
* Pre-processor Definitions
|
||||
****************************************************************************/
|
||||
|
||||
#define NXMB_DIAG_SUBFUNC_OFF 0
|
||||
#define NXMB_DIAG_DATA_OFF 2
|
||||
#define NXMB_DIAG_REQ_MIN_DATA_LEN 4
|
||||
|
||||
/* Sub-function codes */
|
||||
|
||||
#define NXMB_DIAG_RETURN_QUERY 0x0000
|
||||
|
||||
/****************************************************************************
|
||||
* Public Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_fc08_diagnostics
|
||||
****************************************************************************/
|
||||
|
||||
enum nxmb_exception_e nxmb_fc08_diagnostics(nxmb_handle_t ctx)
|
||||
{
|
||||
uint16_t data_len = ctx->adu.length - 2;
|
||||
uint16_t subfunc;
|
||||
|
||||
if (data_len < NXMB_DIAG_REQ_MIN_DATA_LEN)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_VALUE;
|
||||
}
|
||||
|
||||
subfunc = nxmb_util_get_u16_be(&ctx->adu.data[NXMB_DIAG_SUBFUNC_OFF]);
|
||||
|
||||
switch (subfunc)
|
||||
{
|
||||
case NXMB_DIAG_RETURN_QUERY:
|
||||
{
|
||||
/* Response echoes the request — adu is already correct */
|
||||
|
||||
return NXMB_EX_NONE;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_FUNCTION;
|
||||
}
|
||||
}
|
||||
}
|
||||
110
industry/nxmodbus/core/nxmb_func_discrete.c
Normal file
110
industry/nxmodbus/core/nxmb_func_discrete.c
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
/****************************************************************************
|
||||
* apps/industry/nxmodbus/core/nxmb_func_discrete.c
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership. The
|
||||
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Included Files
|
||||
****************************************************************************/
|
||||
|
||||
#include <nuttx/config.h>
|
||||
|
||||
#include <nuttx/compiler.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <nxmodbus/nxmodbus.h>
|
||||
|
||||
#include "nxmb_internal.h"
|
||||
|
||||
/****************************************************************************
|
||||
* Pre-processor Definitions
|
||||
****************************************************************************/
|
||||
|
||||
#define NXMB_FC02_ADDR_OFF 0
|
||||
#define NXMB_FC02_QTY_OFF 2
|
||||
#define NXMB_FC02_REQ_DATA_LEN 4
|
||||
|
||||
#define NXMB_DISCRETE_QTY_MAX 2000
|
||||
|
||||
/****************************************************************************
|
||||
* Public Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_fc02_read_discrete
|
||||
****************************************************************************/
|
||||
|
||||
enum nxmb_exception_e nxmb_fc02_read_discrete(nxmb_handle_t ctx)
|
||||
{
|
||||
uint16_t data_len = ctx->adu.length - 2;
|
||||
uint16_t addr;
|
||||
uint16_t qty;
|
||||
uint8_t nbytes;
|
||||
int ret;
|
||||
|
||||
if (data_len != NXMB_FC02_REQ_DATA_LEN)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_VALUE;
|
||||
}
|
||||
|
||||
if (ctx->callbacks == NULL || ctx->callbacks->discrete_cb == NULL)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_FUNCTION;
|
||||
}
|
||||
|
||||
addr = nxmb_util_get_u16_be(&ctx->adu.data[NXMB_FC02_ADDR_OFF]);
|
||||
qty = nxmb_util_get_u16_be(&ctx->adu.data[NXMB_FC02_QTY_OFF]);
|
||||
|
||||
if (qty < 1 || qty > NXMB_DISCRETE_QTY_MAX)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_VALUE;
|
||||
}
|
||||
|
||||
if ((uint32_t)addr + qty > 0xffffu)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_ADDRESS;
|
||||
}
|
||||
|
||||
nbytes = (uint8_t)((qty + 7) / 8);
|
||||
|
||||
/* Response is byte_count + discrete data */
|
||||
|
||||
if (1 + nbytes > NXMB_ADU_DATA_MAX)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_VALUE;
|
||||
}
|
||||
|
||||
ctx->adu.fc = NXMB_FC_READ_DISCRETE;
|
||||
ctx->adu.data[0] = nbytes;
|
||||
|
||||
memset(&ctx->adu.data[1], 0, nbytes);
|
||||
|
||||
ret = ctx->callbacks->discrete_cb(&ctx->adu.data[1], addr, qty,
|
||||
ctx->callbacks->priv);
|
||||
if (ret != 0)
|
||||
{
|
||||
return nxmb_cb_ret_to_exception(ret);
|
||||
}
|
||||
|
||||
ctx->adu.length = (uint16_t)(2 + 1 + nbytes);
|
||||
return NXMB_EX_NONE;
|
||||
}
|
||||
331
industry/nxmodbus/core/nxmb_func_holding.c
Normal file
331
industry/nxmodbus/core/nxmb_func_holding.c
Normal file
|
|
@ -0,0 +1,331 @@
|
|||
/****************************************************************************
|
||||
* apps/industry/nxmodbus/core/nxmb_func_holding.c
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership. The
|
||||
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Included Files
|
||||
****************************************************************************/
|
||||
|
||||
#include <nuttx/config.h>
|
||||
|
||||
#include <nuttx/compiler.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <nxmodbus/nxmodbus.h>
|
||||
|
||||
#include "nxmb_internal.h"
|
||||
|
||||
/****************************************************************************
|
||||
* Pre-processor Definitions
|
||||
****************************************************************************/
|
||||
|
||||
/* PDU data offsets are relative to ctx->adu.data[] (after unit_id + fc).
|
||||
* Lengths refer to the data payload only — adu.length = 2 + data_len.
|
||||
*/
|
||||
|
||||
/* FC03 Read Holding Registers */
|
||||
|
||||
#define NXMB_FC03_ADDR_OFF 0
|
||||
#define NXMB_FC03_QTY_OFF 2
|
||||
#define NXMB_FC03_REQ_DATA_LEN 4
|
||||
|
||||
/* FC06 Write Single Holding Register */
|
||||
|
||||
#define NXMB_FC06_ADDR_OFF 0
|
||||
#define NXMB_FC06_VALUE_OFF 2
|
||||
#define NXMB_FC06_REQ_DATA_LEN 4
|
||||
|
||||
/* FC16 Write Multiple Holding Registers */
|
||||
|
||||
#define NXMB_FC16_ADDR_OFF 0
|
||||
#define NXMB_FC16_QTY_OFF 2
|
||||
#define NXMB_FC16_BCNT_OFF 4
|
||||
#define NXMB_FC16_DATA_OFF 5
|
||||
#define NXMB_FC16_RESP_DATA_LEN 4
|
||||
|
||||
/* FC23 Read/Write Multiple Holding Registers */
|
||||
|
||||
#define NXMB_FC23_RD_ADDR_OFF 0
|
||||
#define NXMB_FC23_RD_QTY_OFF 2
|
||||
#define NXMB_FC23_WR_ADDR_OFF 4
|
||||
#define NXMB_FC23_WR_QTY_OFF 6
|
||||
#define NXMB_FC23_WR_BCNT_OFF 8
|
||||
#define NXMB_FC23_WR_DATA_OFF 9
|
||||
#define NXMB_FC23_REQ_MIN_DATA_LEN 9
|
||||
|
||||
/* Quantity limits per Modbus Application Protocol Specification */
|
||||
|
||||
#define NXMB_REG_READ_QTY_MAX 125 /* 0x007D */
|
||||
#define NXMB_REG_WRITE_MUL_QTY_MAX 123 /* 0x007B */
|
||||
#define NXMB_REG_READWRITE_RD_QTY_MAX 125 /* 0x007D */
|
||||
#define NXMB_REG_READWRITE_WR_QTY_MAX 121 /* 0x0079 */
|
||||
|
||||
/****************************************************************************
|
||||
* Public Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_fc03_read_holding
|
||||
****************************************************************************/
|
||||
|
||||
enum nxmb_exception_e nxmb_fc03_read_holding(nxmb_handle_t ctx)
|
||||
{
|
||||
uint16_t data_len = ctx->adu.length - 2;
|
||||
uint16_t addr;
|
||||
uint16_t qty;
|
||||
uint8_t nbytes;
|
||||
int ret;
|
||||
|
||||
if (data_len != NXMB_FC03_REQ_DATA_LEN)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_VALUE;
|
||||
}
|
||||
|
||||
if (ctx->callbacks == NULL || ctx->callbacks->holding_cb == NULL)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_FUNCTION;
|
||||
}
|
||||
|
||||
addr = nxmb_util_get_u16_be(&ctx->adu.data[NXMB_FC03_ADDR_OFF]);
|
||||
qty = nxmb_util_get_u16_be(&ctx->adu.data[NXMB_FC03_QTY_OFF]);
|
||||
|
||||
if (qty < 1 || qty > NXMB_REG_READ_QTY_MAX)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_VALUE;
|
||||
}
|
||||
|
||||
if ((uint32_t)addr + qty > 0xffffu)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_ADDRESS;
|
||||
}
|
||||
|
||||
nbytes = (uint8_t)(qty * 2);
|
||||
|
||||
/* Response is byte_count + register data */
|
||||
|
||||
if (1 + nbytes > NXMB_ADU_DATA_MAX)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_VALUE;
|
||||
}
|
||||
|
||||
/* Build response: byte_count + register data (big-endian) */
|
||||
|
||||
ctx->adu.fc = NXMB_FC_READ_HOLDING;
|
||||
ctx->adu.data[0] = nbytes;
|
||||
|
||||
ret = ctx->callbacks->holding_cb(&ctx->adu.data[1], addr, qty,
|
||||
NXMB_REG_READ, ctx->callbacks->priv);
|
||||
if (ret != 0)
|
||||
{
|
||||
return nxmb_cb_ret_to_exception(ret);
|
||||
}
|
||||
|
||||
ctx->adu.length = (uint16_t)(2 + 1 + nbytes);
|
||||
return NXMB_EX_NONE;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_fc06_write_holding
|
||||
****************************************************************************/
|
||||
|
||||
enum nxmb_exception_e nxmb_fc06_write_holding(nxmb_handle_t ctx)
|
||||
{
|
||||
uint16_t data_len = ctx->adu.length - 2;
|
||||
uint16_t addr;
|
||||
int ret;
|
||||
|
||||
if (data_len != NXMB_FC06_REQ_DATA_LEN)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_VALUE;
|
||||
}
|
||||
|
||||
if (ctx->callbacks == NULL || ctx->callbacks->holding_cb == NULL)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_FUNCTION;
|
||||
}
|
||||
|
||||
addr = nxmb_util_get_u16_be(&ctx->adu.data[NXMB_FC06_ADDR_OFF]);
|
||||
|
||||
if (addr == 0xffffu)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_ADDRESS;
|
||||
}
|
||||
|
||||
ret = ctx->callbacks->holding_cb(&ctx->adu.data[NXMB_FC06_VALUE_OFF], addr,
|
||||
1, NXMB_REG_WRITE, ctx->callbacks->priv);
|
||||
if (ret != 0)
|
||||
{
|
||||
return nxmb_cb_ret_to_exception(ret);
|
||||
}
|
||||
|
||||
/* Response echoes the request — adu.length is already correct */
|
||||
|
||||
return NXMB_EX_NONE;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_fc16_write_holdings
|
||||
****************************************************************************/
|
||||
|
||||
enum nxmb_exception_e nxmb_fc16_write_holdings(nxmb_handle_t ctx)
|
||||
{
|
||||
uint16_t data_len = ctx->adu.length - 2;
|
||||
uint16_t addr;
|
||||
uint16_t qty;
|
||||
uint8_t byte_count;
|
||||
int ret;
|
||||
|
||||
if (data_len < NXMB_FC16_DATA_OFF)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_VALUE;
|
||||
}
|
||||
|
||||
if (ctx->callbacks == NULL || ctx->callbacks->holding_cb == NULL)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_FUNCTION;
|
||||
}
|
||||
|
||||
addr = nxmb_util_get_u16_be(&ctx->adu.data[NXMB_FC16_ADDR_OFF]);
|
||||
qty = nxmb_util_get_u16_be(&ctx->adu.data[NXMB_FC16_QTY_OFF]);
|
||||
byte_count = ctx->adu.data[NXMB_FC16_BCNT_OFF];
|
||||
|
||||
if (qty < 1 || qty > NXMB_REG_WRITE_MUL_QTY_MAX)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_VALUE;
|
||||
}
|
||||
|
||||
if ((uint32_t)addr + qty > 0xffffu)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_ADDRESS;
|
||||
}
|
||||
|
||||
if (byte_count != (uint8_t)(qty * 2))
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_VALUE;
|
||||
}
|
||||
|
||||
/* Verify total data length matches header + payload */
|
||||
|
||||
if (data_len != (uint16_t)(NXMB_FC16_DATA_OFF + byte_count))
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_VALUE;
|
||||
}
|
||||
|
||||
ret = ctx->callbacks->holding_cb(&ctx->adu.data[NXMB_FC16_DATA_OFF], addr,
|
||||
qty, NXMB_REG_WRITE,
|
||||
ctx->callbacks->priv);
|
||||
if (ret != 0)
|
||||
{
|
||||
return nxmb_cb_ret_to_exception(ret);
|
||||
}
|
||||
|
||||
/* Response: addr(2) + qty(2) — already in data[0..3] from request */
|
||||
|
||||
ctx->adu.length = (uint16_t)(2 + NXMB_FC16_RESP_DATA_LEN);
|
||||
return NXMB_EX_NONE;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_fc23_readwrite_holding
|
||||
****************************************************************************/
|
||||
|
||||
enum nxmb_exception_e nxmb_fc23_readwrite_holding(nxmb_handle_t ctx)
|
||||
{
|
||||
uint16_t data_len = ctx->adu.length - 2;
|
||||
uint16_t rd_addr;
|
||||
uint16_t rd_qty;
|
||||
uint16_t wr_addr;
|
||||
uint16_t wr_qty;
|
||||
uint8_t wr_bcnt;
|
||||
uint16_t rd_bcnt;
|
||||
int ret;
|
||||
|
||||
if (data_len < NXMB_FC23_REQ_MIN_DATA_LEN)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_VALUE;
|
||||
}
|
||||
|
||||
if (ctx->callbacks == NULL || ctx->callbacks->holding_cb == NULL)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_FUNCTION;
|
||||
}
|
||||
|
||||
rd_addr = nxmb_util_get_u16_be(&ctx->adu.data[NXMB_FC23_RD_ADDR_OFF]);
|
||||
rd_qty = nxmb_util_get_u16_be(&ctx->adu.data[NXMB_FC23_RD_QTY_OFF]);
|
||||
wr_addr = nxmb_util_get_u16_be(&ctx->adu.data[NXMB_FC23_WR_ADDR_OFF]);
|
||||
wr_qty = nxmb_util_get_u16_be(&ctx->adu.data[NXMB_FC23_WR_QTY_OFF]);
|
||||
wr_bcnt = ctx->adu.data[NXMB_FC23_WR_BCNT_OFF];
|
||||
|
||||
if (rd_qty < 1 || rd_qty > NXMB_REG_READWRITE_RD_QTY_MAX || wr_qty < 1 ||
|
||||
wr_qty > NXMB_REG_READWRITE_WR_QTY_MAX || wr_bcnt != (wr_qty * 2))
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_VALUE;
|
||||
}
|
||||
|
||||
if ((uint32_t)rd_addr + rd_qty > 0xffffu)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_ADDRESS;
|
||||
}
|
||||
|
||||
if ((uint32_t)wr_addr + wr_qty > 0xffffu)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_ADDRESS;
|
||||
}
|
||||
|
||||
/* Verify total request data length matches header + write payload */
|
||||
|
||||
if (data_len != (uint16_t)(NXMB_FC23_WR_DATA_OFF + wr_bcnt))
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_VALUE;
|
||||
}
|
||||
|
||||
/* Response is byte_count + read register data — must fit in adu.data[] */
|
||||
|
||||
if (1 + rd_qty * 2 > NXMB_ADU_DATA_MAX)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_VALUE;
|
||||
}
|
||||
|
||||
ret = ctx->callbacks->holding_cb(&ctx->adu.data[NXMB_FC23_WR_DATA_OFF],
|
||||
wr_addr, wr_qty, NXMB_REG_WRITE,
|
||||
ctx->callbacks->priv);
|
||||
if (ret != 0)
|
||||
{
|
||||
return nxmb_cb_ret_to_exception(ret);
|
||||
}
|
||||
|
||||
/* Build response: byte_count + read register data, overwriting request */
|
||||
|
||||
ctx->adu.fc = NXMB_FC_READWRITE_HOLDINGS;
|
||||
rd_bcnt = rd_qty * 2;
|
||||
ctx->adu.data[0] = (uint8_t)rd_bcnt;
|
||||
|
||||
ret = ctx->callbacks->holding_cb(&ctx->adu.data[1], rd_addr, rd_qty,
|
||||
NXMB_REG_READ, ctx->callbacks->priv);
|
||||
if (ret != 0)
|
||||
{
|
||||
return nxmb_cb_ret_to_exception(ret);
|
||||
}
|
||||
|
||||
ctx->adu.length = (uint16_t)(2 + 1 + rd_bcnt);
|
||||
return NXMB_EX_NONE;
|
||||
}
|
||||
115
industry/nxmodbus/core/nxmb_func_input.c
Normal file
115
industry/nxmodbus/core/nxmb_func_input.c
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
/****************************************************************************
|
||||
* apps/industry/nxmodbus/core/nxmb_func_input.c
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership. The
|
||||
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Included Files
|
||||
****************************************************************************/
|
||||
|
||||
#include <nuttx/config.h>
|
||||
|
||||
#include <nuttx/compiler.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <nxmodbus/nxmodbus.h>
|
||||
|
||||
#include "nxmb_internal.h"
|
||||
|
||||
/****************************************************************************
|
||||
* Pre-processor Definitions
|
||||
****************************************************************************/
|
||||
|
||||
/* FC04 Read Input Registers — offsets in adu.data[], lengths are data
|
||||
* only
|
||||
*/
|
||||
|
||||
#define NXMB_FC04_ADDR_OFF 0
|
||||
#define NXMB_FC04_QTY_OFF 2
|
||||
#define NXMB_FC04_REQ_DATA_LEN 4
|
||||
|
||||
/* Maximum register quantity per Modbus specification */
|
||||
|
||||
#define NXMB_REG_READ_QTY_MAX 125 /* 0x007D */
|
||||
|
||||
/****************************************************************************
|
||||
* Public Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_fc04_read_input
|
||||
****************************************************************************/
|
||||
|
||||
enum nxmb_exception_e nxmb_fc04_read_input(nxmb_handle_t ctx)
|
||||
{
|
||||
uint16_t data_len = ctx->adu.length - 2;
|
||||
uint16_t addr;
|
||||
uint16_t qty;
|
||||
uint8_t nbytes;
|
||||
int ret;
|
||||
|
||||
if (data_len != NXMB_FC04_REQ_DATA_LEN)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_VALUE;
|
||||
}
|
||||
|
||||
if (ctx->callbacks == NULL || ctx->callbacks->input_cb == NULL)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_FUNCTION;
|
||||
}
|
||||
|
||||
addr = nxmb_util_get_u16_be(&ctx->adu.data[NXMB_FC04_ADDR_OFF]);
|
||||
qty = nxmb_util_get_u16_be(&ctx->adu.data[NXMB_FC04_QTY_OFF]);
|
||||
|
||||
if (qty < 1 || qty > NXMB_REG_READ_QTY_MAX)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_VALUE;
|
||||
}
|
||||
|
||||
if ((uint32_t)addr + qty > 0xffffu)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_ADDRESS;
|
||||
}
|
||||
|
||||
nbytes = (uint8_t)(qty * 2);
|
||||
|
||||
/* Response is byte_count + register data */
|
||||
|
||||
if (1 + nbytes > NXMB_ADU_DATA_MAX)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_VALUE;
|
||||
}
|
||||
|
||||
/* Build response: byte_count + register data (big-endian) */
|
||||
|
||||
ctx->adu.fc = NXMB_FC_READ_INPUT;
|
||||
ctx->adu.data[0] = nbytes;
|
||||
|
||||
ret = ctx->callbacks->input_cb(&ctx->adu.data[1], addr, qty,
|
||||
ctx->callbacks->priv);
|
||||
if (ret != 0)
|
||||
{
|
||||
return nxmb_cb_ret_to_exception(ret);
|
||||
}
|
||||
|
||||
ctx->adu.length = (uint16_t)(2 + 1 + nbytes);
|
||||
return NXMB_EX_NONE;
|
||||
}
|
||||
99
industry/nxmodbus/core/nxmb_func_other.c
Normal file
99
industry/nxmodbus/core/nxmb_func_other.c
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
/****************************************************************************
|
||||
* apps/industry/nxmodbus/core/nxmb_func_other.c
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership. The
|
||||
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Included Files
|
||||
****************************************************************************/
|
||||
|
||||
#include <nuttx/config.h>
|
||||
|
||||
#include <nuttx/compiler.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <nxmodbus/nxmodbus.h>
|
||||
|
||||
#include "nxmb_internal.h"
|
||||
|
||||
/****************************************************************************
|
||||
* Pre-processor Definitions
|
||||
****************************************************************************/
|
||||
|
||||
#define NXMB_REPORT_ID_REQ_DATA_LEN 0
|
||||
|
||||
/* Run indicator status */
|
||||
|
||||
#define NXMB_RUN_STATUS_ON 0xff
|
||||
#define NXMB_RUN_STATUS_OFF 0x00
|
||||
|
||||
/****************************************************************************
|
||||
* Public Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_fc17_report_server_id
|
||||
*
|
||||
* Description:
|
||||
* Handle FC17 (0x11) Report Server ID request. Returns the configured
|
||||
* unit ID and a run indicator byte (0xFF = ON).
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
enum nxmb_exception_e nxmb_fc17_report_server_id(nxmb_handle_t ctx)
|
||||
{
|
||||
uint16_t data_len = ctx->adu.length - 2;
|
||||
|
||||
if (data_len != NXMB_REPORT_ID_REQ_DATA_LEN)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_VALUE;
|
||||
}
|
||||
|
||||
/* If the application configured server ID data via nxmb_set_server_id(),
|
||||
* use that; otherwise fall back to unit_id + run status ON.
|
||||
*/
|
||||
|
||||
ctx->adu.fc = NXMB_FC_REPORT_SERVER_ID;
|
||||
|
||||
if (ctx->server_id_len > 0)
|
||||
{
|
||||
/* Response is byte_count + server_id_buf */
|
||||
|
||||
if (1 + ctx->server_id_len > NXMB_ADU_DATA_MAX)
|
||||
{
|
||||
return NXMB_EX_DEVICE_FAILURE;
|
||||
}
|
||||
|
||||
ctx->adu.data[0] = (uint8_t)ctx->server_id_len;
|
||||
memcpy(&ctx->adu.data[1], ctx->server_id_buf, ctx->server_id_len);
|
||||
ctx->adu.length = (uint16_t)(2 + 1 + ctx->server_id_len);
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx->adu.data[0] = 2; /* byte count */
|
||||
ctx->adu.data[1] = ctx->unit_id; /* server ID */
|
||||
ctx->adu.data[2] = NXMB_RUN_STATUS_ON; /* run indicator */
|
||||
ctx->adu.length = (uint16_t)(2 + 3);
|
||||
}
|
||||
|
||||
return NXMB_EX_NONE;
|
||||
}
|
||||
121
industry/nxmodbus/core/nxmb_server.c
Normal file
121
industry/nxmodbus/core/nxmb_server.c
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
/****************************************************************************
|
||||
* apps/industry/nxmodbus/core/nxmb_server.c
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership. The
|
||||
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Included Files
|
||||
****************************************************************************/
|
||||
|
||||
#include <nuttx/config.h>
|
||||
|
||||
#include <nuttx/compiler.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <nxmodbus/nxmodbus.h>
|
||||
|
||||
#include "nxmb_internal.h"
|
||||
|
||||
/****************************************************************************
|
||||
* Pre-processor Definitions
|
||||
****************************************************************************/
|
||||
|
||||
#define NXMB_FUNC_ERROR 0x80
|
||||
|
||||
/****************************************************************************
|
||||
* Public Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_poll
|
||||
*
|
||||
* Description:
|
||||
* Poll for Modbus events and process received frames.
|
||||
*
|
||||
* Input Parameters:
|
||||
* handle - Instance handle
|
||||
*
|
||||
* Returned Value:
|
||||
* 0 on success, negative errno on failure
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_poll(nxmb_handle_t ctx)
|
||||
{
|
||||
uint8_t rcv_address;
|
||||
int ret;
|
||||
|
||||
DEBUGASSERT(ctx && ctx->transport_ops && ctx->transport_ops->receive);
|
||||
|
||||
pthread_mutex_lock(&ctx->lock);
|
||||
|
||||
if (!ctx->enabled)
|
||||
{
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
ret = ctx->transport_ops->receive(ctx);
|
||||
if (ret < 0)
|
||||
{
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (ret == 0 || ctx->adu.length == 0)
|
||||
{
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
rcv_address = ctx->adu.unit_id;
|
||||
|
||||
if (rcv_address != NXMB_ADDRESS_BROADCAST && rcv_address != ctx->unit_id)
|
||||
{
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Dispatch to function code handler. Handlers mutate ctx->adu in place,
|
||||
* turning the request into the response.
|
||||
*/
|
||||
|
||||
ret = nxmb_dispatch_function(ctx);
|
||||
|
||||
if (rcv_address != NXMB_ADDRESS_BROADCAST)
|
||||
{
|
||||
if (ctx->transport_ops->send != NULL)
|
||||
{
|
||||
ret = ctx->transport_ops->send(ctx);
|
||||
if (ret < 0)
|
||||
{
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
return 0;
|
||||
}
|
||||
127
industry/nxmodbus/core/nxmb_utils.c
Normal file
127
industry/nxmodbus/core/nxmb_utils.c
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
/****************************************************************************
|
||||
* apps/industry/nxmodbus/core/nxmb_utils.c
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership. The
|
||||
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Included Files
|
||||
****************************************************************************/
|
||||
|
||||
#include <nuttx/config.h>
|
||||
|
||||
#include <nuttx/compiler.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "nxmb_internal.h"
|
||||
|
||||
/****************************************************************************
|
||||
* Public Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_util_set_bits
|
||||
*
|
||||
* Description:
|
||||
* Set bits in a byte buffer.
|
||||
*
|
||||
* Input Parameters:
|
||||
* buf - Byte buffer
|
||||
* bit_offset - Bit offset from start of buffer
|
||||
* nbits - Number of bits to set (max 8)
|
||||
* value - Value to set
|
||||
*
|
||||
* Returned Value:
|
||||
* None
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
void nxmb_util_set_bits(FAR uint8_t *buf, uint16_t bit_offset, uint8_t nbits,
|
||||
uint8_t value)
|
||||
{
|
||||
uint16_t word_buf;
|
||||
uint16_t mask;
|
||||
uint16_t byte_offset;
|
||||
uint16_t pre_bits;
|
||||
uint16_t val = value;
|
||||
|
||||
byte_offset = bit_offset / 8;
|
||||
pre_bits = bit_offset - (byte_offset * 8);
|
||||
|
||||
val <<= pre_bits;
|
||||
mask = (uint16_t)((1 << nbits) - 1);
|
||||
mask <<= pre_bits;
|
||||
|
||||
word_buf = buf[byte_offset];
|
||||
if (pre_bits + nbits > 8)
|
||||
{
|
||||
word_buf |= buf[byte_offset + 1] << 8;
|
||||
}
|
||||
|
||||
word_buf = (word_buf & (~mask)) | val;
|
||||
|
||||
buf[byte_offset] = (uint8_t)(word_buf & 0xff);
|
||||
if (pre_bits + nbits > 8)
|
||||
{
|
||||
buf[byte_offset + 1] = (uint8_t)(word_buf >> 8);
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_util_get_bits
|
||||
*
|
||||
* Description:
|
||||
* Get bits from a byte buffer.
|
||||
*
|
||||
* Input Parameters:
|
||||
* buf - Byte buffer
|
||||
* bit_offset - Bit offset from start of buffer
|
||||
* nbits - Number of bits to get (max 8)
|
||||
*
|
||||
* Returned Value:
|
||||
* Bit value
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
uint8_t nxmb_util_get_bits(FAR const uint8_t *buf, uint16_t bit_offset,
|
||||
uint8_t nbits)
|
||||
{
|
||||
uint16_t word_buf;
|
||||
uint16_t mask;
|
||||
uint16_t byte_offset;
|
||||
uint16_t pre_bits;
|
||||
|
||||
byte_offset = bit_offset / 8;
|
||||
pre_bits = bit_offset - (byte_offset * 8);
|
||||
|
||||
mask = (uint16_t)((1 << nbits) - 1);
|
||||
|
||||
word_buf = buf[byte_offset];
|
||||
if (pre_bits + nbits > 8)
|
||||
{
|
||||
word_buf |= buf[byte_offset + 1] << 8;
|
||||
}
|
||||
|
||||
word_buf >>= pre_bits;
|
||||
word_buf &= mask;
|
||||
|
||||
return (uint8_t)word_buf;
|
||||
}
|
||||
|
||||
379
industry/nxmodbus/nxmb_internal.h
Normal file
379
industry/nxmodbus/nxmb_internal.h
Normal file
|
|
@ -0,0 +1,379 @@
|
|||
/****************************************************************************
|
||||
* apps/industry/nxmodbus/nxmb_internal.h
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership. The
|
||||
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef __APPS_INDUSTRY_NXMODBUS_NXMB_INTERNAL_H
|
||||
#define __APPS_INDUSTRY_NXMODBUS_NXMB_INTERNAL_H
|
||||
|
||||
/****************************************************************************
|
||||
* Included Files
|
||||
****************************************************************************/
|
||||
|
||||
#include <nuttx/compiler.h>
|
||||
#include <nuttx/config.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <nxmodbus/nxmodbus.h>
|
||||
|
||||
/****************************************************************************
|
||||
* Public Types
|
||||
****************************************************************************/
|
||||
|
||||
/* Pre-processor Definitions */
|
||||
|
||||
#define NXMB_ADDRESS_BROADCAST 0
|
||||
|
||||
/* Modbus function codes */
|
||||
|
||||
#define NXMB_FC_READ_COILS 0x01
|
||||
#define NXMB_FC_READ_DISCRETE 0x02
|
||||
#define NXMB_FC_READ_HOLDING 0x03
|
||||
#define NXMB_FC_READ_INPUT 0x04
|
||||
#define NXMB_FC_WRITE_COIL 0x05
|
||||
#define NXMB_FC_WRITE_HOLDING 0x06
|
||||
#define NXMB_FC_DIAGNOSTICS 0x08
|
||||
#define NXMB_FC_WRITE_COILS 0x0f
|
||||
#define NXMB_FC_WRITE_HOLDINGS 0x10
|
||||
#define NXMB_FC_REPORT_SERVER_ID 0x11
|
||||
#define NXMB_FC_READWRITE_HOLDINGS 0x17
|
||||
|
||||
/* Transport operations interface.
|
||||
*
|
||||
* receive() must not block indefinitely. Implementations should use an
|
||||
* internal timeout (e.g. select()) and return 0 or -EAGAIN when no
|
||||
* complete frame is available yet. The client polling loop relies on
|
||||
* this to enforce its own response timeout.
|
||||
*/
|
||||
|
||||
struct nxmb_transport_ops_s
|
||||
{
|
||||
CODE int (*init)(nxmb_handle_t ctx);
|
||||
CODE int (*deinit)(nxmb_handle_t ctx);
|
||||
CODE int (*send)(nxmb_handle_t ctx);
|
||||
CODE int (*receive)(nxmb_handle_t ctx);
|
||||
};
|
||||
|
||||
/* Custom function code handler */
|
||||
|
||||
struct nxmb_custom_fc_s
|
||||
{
|
||||
FAR struct nxmb_custom_fc_s *next;
|
||||
nxmb_custom_fc_handler_t handler;
|
||||
uint8_t fc;
|
||||
};
|
||||
|
||||
/* NxModbus instance context */
|
||||
|
||||
struct nxmb_context_s
|
||||
{
|
||||
FAR const struct nxmb_callbacks_s *callbacks;
|
||||
FAR const struct nxmb_transport_ops_s *transport_ops;
|
||||
enum nxmb_mode_e mode;
|
||||
pthread_mutex_t lock;
|
||||
uint8_t unit_id;
|
||||
bool is_client;
|
||||
bool enabled;
|
||||
|
||||
/* Transport configuration */
|
||||
|
||||
union nxmb_transport_config_u transport_cfg;
|
||||
|
||||
/* Single ADU container reused for both RX and TX. Modbus is half-duplex,
|
||||
* so request and response never coexist: handlers mutate the request
|
||||
* in place to form the response.
|
||||
*/
|
||||
|
||||
struct nxmb_adu_s adu;
|
||||
|
||||
/* Report Server ID (FC17) data */
|
||||
|
||||
uint8_t server_id_buf[CONFIG_NXMODBUS_REP_SERVER_ID_BUF];
|
||||
uint16_t server_id_len;
|
||||
|
||||
/* Custom handlers */
|
||||
|
||||
FAR struct nxmb_custom_fc_s *custom_fc_list;
|
||||
FAR void *transport_state;
|
||||
FAR void *client_state;
|
||||
};
|
||||
|
||||
/****************************************************************************
|
||||
* Public Data
|
||||
****************************************************************************/
|
||||
|
||||
#ifdef CONFIG_NXMODBUS_RTU
|
||||
extern const struct nxmb_transport_ops_s g_nxmb_serial_ops;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_NXMODBUS_ASCII
|
||||
extern const struct nxmb_transport_ops_s g_nxmb_ascii_ops;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_NXMODBUS_RAW_ADU
|
||||
extern const struct nxmb_transport_ops_s g_nxmb_raw_ops;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_NXMODBUS_TCP
|
||||
extern const struct nxmb_transport_ops_s g_nxmb_tcp_ops;
|
||||
#endif
|
||||
|
||||
/****************************************************************************
|
||||
* Inline Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_cb_ret_to_exception
|
||||
*
|
||||
* Description:
|
||||
* Map a callback return value to a Modbus exception code.
|
||||
*
|
||||
* -ENOENT -> address not supported by the application
|
||||
* anything else -> generic device failure
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static inline enum nxmb_exception_e nxmb_cb_ret_to_exception(int ret)
|
||||
{
|
||||
if (ret == -ENOENT)
|
||||
{
|
||||
return NXMB_EX_ILLEGAL_DATA_ADDRESS;
|
||||
}
|
||||
|
||||
return NXMB_EX_DEVICE_FAILURE;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_util_get_u16_be
|
||||
*
|
||||
* Description:
|
||||
* Extract a big-endian 16-bit value from a buffer.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static inline uint16_t nxmb_util_get_u16_be(FAR const uint8_t *buf)
|
||||
{
|
||||
return (uint16_t)((buf[0] << 8) | buf[1]);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_util_put_u16_be
|
||||
*
|
||||
* Description:
|
||||
* Store a 16-bit value as big-endian in a buffer.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static inline void nxmb_util_put_u16_be(FAR uint8_t *buf, uint16_t value)
|
||||
{
|
||||
buf[0] = (uint8_t)(value >> 8);
|
||||
buf[1] = (uint8_t)(value & 0xff);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_util_clock_ms
|
||||
*
|
||||
* Description:
|
||||
* Return the current monotonic clock value in milliseconds.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static inline uint64_t nxmb_util_clock_ms(void)
|
||||
{
|
||||
struct timespec ts;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (uint64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Public Function Prototypes
|
||||
****************************************************************************/
|
||||
|
||||
#ifdef CONFIG_SERIAL_TERMIOS
|
||||
/****************************************************************************
|
||||
* Name: nxmb_serial_configure
|
||||
*
|
||||
* Description:
|
||||
* Configure serial port with termios settings for Modbus communication.
|
||||
* Shared by RTU and ASCII transports.
|
||||
*
|
||||
* Input Parameters:
|
||||
* fd - File descriptor
|
||||
* baudrate - Baud rate
|
||||
* parity - Parity setting
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_serial_configure(int fd, uint32_t baudrate,
|
||||
enum nxmb_parity_e parity);
|
||||
#endif
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_util_set_bits
|
||||
*
|
||||
* Description:
|
||||
* Set up to 8 bits in a byte buffer at the given bit offset.
|
||||
*
|
||||
* Input Parameters:
|
||||
* buf - Byte buffer
|
||||
* bit_offset - Bit offset from start of buffer
|
||||
* nbits - Number of bits to set (max 8)
|
||||
* value - Bit values to write
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
void nxmb_util_set_bits(FAR uint8_t *buf, uint16_t bit_offset, uint8_t nbits,
|
||||
uint8_t value);
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_util_get_bits
|
||||
*
|
||||
* Description:
|
||||
* Extract up to 8 bits from a byte buffer at the given bit offset.
|
||||
*
|
||||
* Input Parameters:
|
||||
* buf - Byte buffer
|
||||
* bit_offset - Bit offset from start of buffer
|
||||
* nbits - Number of bits to extract (max 8)
|
||||
*
|
||||
* Returned Value:
|
||||
* Extracted bit values
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
uint8_t nxmb_util_get_bits(FAR const uint8_t *buf, uint16_t bit_offset,
|
||||
uint8_t nbits);
|
||||
|
||||
#ifdef CONFIG_NXMODBUS_RTU
|
||||
/****************************************************************************
|
||||
* Name: nxmb_crc16
|
||||
*
|
||||
* Description:
|
||||
* Calculate Modbus RTU CRC16 checksum.
|
||||
*
|
||||
* Input Parameters:
|
||||
* buf - Pointer to data buffer
|
||||
* len - Length of data in bytes
|
||||
*
|
||||
* Returned Value:
|
||||
* CRC16 value
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
uint16_t nxmb_crc16(FAR const uint8_t *buf, uint16_t len);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_NXMODBUS_ASCII
|
||||
/****************************************************************************
|
||||
* Name: nxmb_lrc
|
||||
*
|
||||
* Description:
|
||||
* Calculate Modbus ASCII LRC checksum.
|
||||
*
|
||||
* Input Parameters:
|
||||
* buf - Pointer to data buffer
|
||||
* len - Length of data in bytes
|
||||
*
|
||||
* Returned Value:
|
||||
* LRC value (8-bit)
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
uint8_t nxmb_lrc(FAR const uint8_t *buf, uint16_t len);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_NXMODBUS_CLIENT
|
||||
/****************************************************************************
|
||||
* Name: nxmb_client_init
|
||||
*
|
||||
* Description:
|
||||
* Initialize client state for master mode.
|
||||
*
|
||||
* Input Parameters:
|
||||
* ctx - Instance context
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_client_init(nxmb_handle_t ctx);
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_client_deinit
|
||||
*
|
||||
* Description:
|
||||
* Deinitialize client state.
|
||||
*
|
||||
* Input Parameters:
|
||||
* ctx - Instance context
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_client_deinit(nxmb_handle_t ctx);
|
||||
#endif
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_dispatch_function
|
||||
*
|
||||
* Description:
|
||||
* Dispatch a Modbus function code to the appropriate handler. Operates
|
||||
* in place on ctx->adu.
|
||||
*
|
||||
* Input Parameters:
|
||||
* ctx - Instance context
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_dispatch_function(nxmb_handle_t ctx);
|
||||
|
||||
/* Function-code handlers operate on ctx->adu in place: they read the
|
||||
* request from adu.fc/adu.data[]/adu.length and overwrite those fields
|
||||
* with the response.
|
||||
*/
|
||||
|
||||
enum nxmb_exception_e nxmb_fc01_read_coils(nxmb_handle_t ctx);
|
||||
enum nxmb_exception_e nxmb_fc02_read_discrete(nxmb_handle_t ctx);
|
||||
enum nxmb_exception_e nxmb_fc05_write_coil(nxmb_handle_t ctx);
|
||||
enum nxmb_exception_e nxmb_fc15_write_coils(nxmb_handle_t ctx);
|
||||
enum nxmb_exception_e nxmb_fc03_read_holding(nxmb_handle_t ctx);
|
||||
enum nxmb_exception_e nxmb_fc04_read_input(nxmb_handle_t ctx);
|
||||
enum nxmb_exception_e nxmb_fc06_write_holding(nxmb_handle_t ctx);
|
||||
enum nxmb_exception_e nxmb_fc16_write_holdings(nxmb_handle_t ctx);
|
||||
enum nxmb_exception_e nxmb_fc23_readwrite_holding(nxmb_handle_t ctx);
|
||||
enum nxmb_exception_e nxmb_fc08_diagnostics(nxmb_handle_t ctx);
|
||||
enum nxmb_exception_e nxmb_fc17_report_server_id(nxmb_handle_t ctx);
|
||||
|
||||
#endif /* __APPS_INDUSTRY_NXMODBUS_NXMB_INTERNAL_H */
|
||||
550
industry/nxmodbus/transport/nxmb_ascii.c
Normal file
550
industry/nxmodbus/transport/nxmb_ascii.c
Normal file
|
|
@ -0,0 +1,550 @@
|
|||
/****************************************************************************
|
||||
* apps/industry/nxmodbus/transport/nxmb_ascii.c
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership. The
|
||||
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Included Files
|
||||
****************************************************************************/
|
||||
|
||||
#include <nuttx/config.h>
|
||||
|
||||
#include <nuttx/compiler.h>
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/select.h>
|
||||
#include <unistd.h>
|
||||
#ifdef CONFIG_SERIAL_TERMIOS
|
||||
# include <termios.h>
|
||||
#endif
|
||||
|
||||
#include <nxmodbus/nxmodbus.h>
|
||||
|
||||
#include "nxmb_internal.h"
|
||||
|
||||
/****************************************************************************
|
||||
* Pre-processor Definitions
|
||||
****************************************************************************/
|
||||
|
||||
#define NXMB_ASCII_START_CHAR ':'
|
||||
#define NXMB_ASCII_CR '\r'
|
||||
#define NXMB_ASCII_LF '\n'
|
||||
|
||||
/* Serialized binary frame: unit_id(1) + fc(1) + data + LRC(1) */
|
||||
|
||||
#define NXMB_ASCII_BIN_MAX (NXMB_ADU_DATA_MAX + 3)
|
||||
|
||||
/* Hex-encoded ASCII frame: ':' + 2*bin + CR + LF */
|
||||
|
||||
#define NXMB_ASCII_BUFLEN (NXMB_ASCII_BIN_MAX * 2 + 4)
|
||||
|
||||
/****************************************************************************
|
||||
* Private Types
|
||||
****************************************************************************/
|
||||
|
||||
struct nxmb_ascii_state_s
|
||||
{
|
||||
int fd;
|
||||
uint8_t rx_buf[NXMB_ASCII_BUFLEN];
|
||||
uint16_t rx_pos;
|
||||
bool in_frame;
|
||||
#ifdef CONFIG_SERIAL_TERMIOS
|
||||
struct termios old_tio;
|
||||
#endif
|
||||
};
|
||||
|
||||
/****************************************************************************
|
||||
* Private Function Prototypes
|
||||
****************************************************************************/
|
||||
|
||||
static int nxmb_ascii_char_to_hex(char c);
|
||||
static char nxmb_ascii_hex_to_char(uint8_t nibble);
|
||||
static uint16_t nxmb_ascii_encode(FAR const uint8_t *bin, uint16_t bin_len,
|
||||
FAR char *ascii);
|
||||
static int nxmb_ascii_decode(FAR const char *ascii, uint16_t ascii_len,
|
||||
FAR uint8_t *bin);
|
||||
static int nxmb_ascii_init(nxmb_handle_t ctx);
|
||||
static int nxmb_ascii_deinit(nxmb_handle_t ctx);
|
||||
static int nxmb_ascii_send(nxmb_handle_t ctx);
|
||||
static int nxmb_ascii_receive(nxmb_handle_t ctx);
|
||||
|
||||
/****************************************************************************
|
||||
* Public Data
|
||||
****************************************************************************/
|
||||
|
||||
const struct nxmb_transport_ops_s g_nxmb_ascii_ops =
|
||||
{
|
||||
.init = nxmb_ascii_init,
|
||||
.deinit = nxmb_ascii_deinit,
|
||||
.send = nxmb_ascii_send,
|
||||
.receive = nxmb_ascii_receive
|
||||
};
|
||||
|
||||
/****************************************************************************
|
||||
* Private Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_ascii_char_to_hex
|
||||
*
|
||||
* Description:
|
||||
* Convert ASCII hex character to binary nibble.
|
||||
*
|
||||
* Input Parameters:
|
||||
* c - ASCII hex character
|
||||
*
|
||||
* Returned Value:
|
||||
* Binary value 0-15, or -1 on error
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static int nxmb_ascii_char_to_hex(char c)
|
||||
{
|
||||
if (c >= '0' && c <= '9')
|
||||
{
|
||||
return c - '0';
|
||||
}
|
||||
else if (c >= 'A' && c <= 'F')
|
||||
{
|
||||
return c - 'A' + 10;
|
||||
}
|
||||
else if (c >= 'a' && c <= 'f')
|
||||
{
|
||||
return c - 'a' + 10;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_ascii_hex_to_char
|
||||
*
|
||||
* Description:
|
||||
* Convert binary nibble to ASCII hex character.
|
||||
*
|
||||
* Input Parameters:
|
||||
* nibble - Binary value 0-15
|
||||
*
|
||||
* Returned Value:
|
||||
* ASCII hex character
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static char nxmb_ascii_hex_to_char(uint8_t nibble)
|
||||
{
|
||||
if (nibble < 10)
|
||||
{
|
||||
return '0' + nibble;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 'A' + (nibble - 10);
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_ascii_encode
|
||||
*
|
||||
* Description:
|
||||
* Encode binary data to ASCII hex string.
|
||||
*
|
||||
* Input Parameters:
|
||||
* bin - Binary data
|
||||
* bin_len - Binary data length
|
||||
* ascii - Output ASCII buffer
|
||||
*
|
||||
* Returned Value:
|
||||
* ASCII string length
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static uint16_t nxmb_ascii_encode(FAR const uint8_t *bin, uint16_t bin_len,
|
||||
FAR char *ascii)
|
||||
{
|
||||
uint16_t pos = 0;
|
||||
uint16_t i;
|
||||
|
||||
for (i = 0; i < bin_len; i++)
|
||||
{
|
||||
ascii[pos++] = nxmb_ascii_hex_to_char(bin[i] >> 4);
|
||||
ascii[pos++] = nxmb_ascii_hex_to_char(bin[i] & 0x0f);
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_ascii_decode
|
||||
*
|
||||
* Description:
|
||||
* Decode ASCII hex string to binary data.
|
||||
*
|
||||
* Input Parameters:
|
||||
* ascii - ASCII hex string
|
||||
* ascii_len - ASCII string length (must be even)
|
||||
* bin - Output binary buffer
|
||||
*
|
||||
* Returned Value:
|
||||
* Binary data length, or negative errno on error
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static int nxmb_ascii_decode(FAR const char *ascii, uint16_t ascii_len,
|
||||
FAR uint8_t *bin)
|
||||
{
|
||||
uint16_t i;
|
||||
int high;
|
||||
int low;
|
||||
|
||||
if (ascii_len % 2 != 0)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
for (i = 0; i < ascii_len; i += 2)
|
||||
{
|
||||
high = nxmb_ascii_char_to_hex(ascii[i]);
|
||||
low = nxmb_ascii_char_to_hex(ascii[i + 1]);
|
||||
|
||||
if (high < 0 || low < 0)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
bin[i / 2] = (high << 4) | low;
|
||||
}
|
||||
|
||||
return ascii_len / 2;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_ascii_init
|
||||
*
|
||||
* Description:
|
||||
* Initialize serial ASCII transport.
|
||||
*
|
||||
* Input Parameters:
|
||||
* ctx - Instance context
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static int nxmb_ascii_init(nxmb_handle_t ctx)
|
||||
{
|
||||
FAR struct nxmb_ascii_state_s *state;
|
||||
FAR const struct nxmb_serial_config_s *cfg;
|
||||
int ret;
|
||||
|
||||
DEBUGASSERT(ctx);
|
||||
|
||||
cfg = &ctx->transport_cfg.serial;
|
||||
|
||||
if (cfg->devpath == NULL)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
state = calloc(1, sizeof(struct nxmb_ascii_state_s));
|
||||
if (state == NULL)
|
||||
{
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
state->fd = open(cfg->devpath, O_RDWR | O_NOCTTY);
|
||||
if (state->fd < 0)
|
||||
{
|
||||
ret = -errno;
|
||||
free(state);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SERIAL_TERMIOS
|
||||
if (tcgetattr(state->fd, &state->old_tio) < 0)
|
||||
{
|
||||
ret = -errno;
|
||||
close(state->fd);
|
||||
free(state);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = nxmb_serial_configure(state->fd, cfg->baudrate, cfg->parity);
|
||||
if (ret < 0)
|
||||
{
|
||||
close(state->fd);
|
||||
free(state);
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
state->rx_pos = 0;
|
||||
state->in_frame = false;
|
||||
|
||||
ctx->transport_state = state;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_ascii_deinit
|
||||
*
|
||||
* Description:
|
||||
* Deinitialize serial ASCII transport.
|
||||
*
|
||||
* Input Parameters:
|
||||
* ctx - Instance context
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static int nxmb_ascii_deinit(nxmb_handle_t ctx)
|
||||
{
|
||||
FAR struct nxmb_ascii_state_s *state;
|
||||
|
||||
DEBUGASSERT(ctx && ctx->transport_state);
|
||||
|
||||
state = ctx->transport_state;
|
||||
|
||||
#ifdef CONFIG_SERIAL_TERMIOS
|
||||
tcsetattr(state->fd, TCSANOW, &state->old_tio);
|
||||
#endif
|
||||
close(state->fd);
|
||||
free(state);
|
||||
|
||||
ctx->transport_state = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_ascii_send
|
||||
*
|
||||
* Description:
|
||||
* Send ASCII frame over serial port.
|
||||
*
|
||||
* Input Parameters:
|
||||
* ctx - Instance context
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static int nxmb_ascii_send(nxmb_handle_t ctx)
|
||||
{
|
||||
FAR struct nxmb_ascii_state_s *state;
|
||||
uint8_t bin[NXMB_ASCII_BIN_MAX];
|
||||
char tx_buf[NXMB_ASCII_BUFLEN];
|
||||
uint16_t data_len;
|
||||
uint16_t pos = 0;
|
||||
uint8_t lrc;
|
||||
ssize_t written;
|
||||
|
||||
DEBUGASSERT(ctx && ctx->transport_state);
|
||||
|
||||
state = ctx->transport_state;
|
||||
|
||||
if (ctx->adu.length < 2)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
data_len = ctx->adu.length - 2;
|
||||
|
||||
/* Linearize the ADU into the wire byte order: unit_id, fc, data... */
|
||||
|
||||
bin[0] = ctx->adu.unit_id;
|
||||
bin[1] = ctx->adu.fc;
|
||||
memcpy(&bin[2], ctx->adu.data, data_len);
|
||||
|
||||
lrc = nxmb_lrc(bin, ctx->adu.length);
|
||||
ctx->adu.crc = lrc;
|
||||
|
||||
tx_buf[pos++] = NXMB_ASCII_START_CHAR;
|
||||
pos += nxmb_ascii_encode(bin, ctx->adu.length, &tx_buf[pos]);
|
||||
tx_buf[pos++] = nxmb_ascii_hex_to_char(lrc >> 4);
|
||||
tx_buf[pos++] = nxmb_ascii_hex_to_char(lrc & 0x0f);
|
||||
tx_buf[pos++] = NXMB_ASCII_CR;
|
||||
tx_buf[pos++] = NXMB_ASCII_LF;
|
||||
|
||||
written = write(state->fd, tx_buf, pos);
|
||||
|
||||
if (written < 0)
|
||||
{
|
||||
return -errno;
|
||||
}
|
||||
|
||||
if (written != pos)
|
||||
{
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_ascii_receive
|
||||
*
|
||||
* Description:
|
||||
* Receive ASCII frame from serial port.
|
||||
*
|
||||
* Input Parameters:
|
||||
* ctx - Instance context
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static int nxmb_ascii_receive(nxmb_handle_t ctx)
|
||||
{
|
||||
FAR struct nxmb_ascii_state_s *state;
|
||||
uint8_t bin[NXMB_ASCII_BIN_MAX];
|
||||
struct timeval timeout;
|
||||
fd_set readfds;
|
||||
ssize_t nread;
|
||||
uint8_t c;
|
||||
uint8_t lrc_calc;
|
||||
uint8_t lrc_recv;
|
||||
int ret;
|
||||
int bin_len;
|
||||
|
||||
DEBUGASSERT(ctx && ctx->transport_state);
|
||||
|
||||
state = ctx->transport_state;
|
||||
|
||||
FD_ZERO(&readfds);
|
||||
FD_SET(state->fd, &readfds);
|
||||
|
||||
timeout.tv_sec = CONFIG_NXMODBUS_ASCII_TIMEOUT_SEC;
|
||||
timeout.tv_usec = 0;
|
||||
|
||||
ret = select(state->fd + 1, &readfds, NULL, NULL, &timeout);
|
||||
|
||||
if (ret < 0)
|
||||
{
|
||||
return -errno;
|
||||
}
|
||||
|
||||
if (ret == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!FD_ISSET(state->fd, &readfds))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
nread = read(state->fd, &c, 1);
|
||||
if (nread < 0)
|
||||
{
|
||||
return -errno;
|
||||
}
|
||||
|
||||
if (nread == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (c == NXMB_ASCII_START_CHAR)
|
||||
{
|
||||
state->rx_pos = 0;
|
||||
state->in_frame = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!state->in_frame)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (c == NXMB_ASCII_LF)
|
||||
{
|
||||
state->in_frame = false;
|
||||
|
||||
/* Minimum valid frame: addr(2) + func(2) + lrc(2) + CR = 7 chars.
|
||||
* Maximum valid frame: NXMB_ASCII_BIN_MAX hex pairs + CR. Reject
|
||||
* anything larger so the decoded length fits bin[].
|
||||
*/
|
||||
|
||||
if (state->rx_pos < 7 ||
|
||||
state->rx_pos > NXMB_ASCII_BIN_MAX * 2 + 1 ||
|
||||
state->rx_buf[state->rx_pos - 1] != NXMB_ASCII_CR)
|
||||
{
|
||||
state->rx_pos = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Decode all hex chars (exclude CR) into a temp binary buffer.
|
||||
* Layout: unit_id + fc + data... + LRC.
|
||||
*/
|
||||
|
||||
bin_len = nxmb_ascii_decode((FAR const char *)state->rx_buf,
|
||||
state->rx_pos - 1, bin);
|
||||
|
||||
if (bin_len < 3)
|
||||
{
|
||||
state->rx_pos = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
lrc_calc = nxmb_lrc(bin, bin_len - 1);
|
||||
lrc_recv = bin[bin_len - 1];
|
||||
|
||||
if (lrc_calc != lrc_recv)
|
||||
{
|
||||
state->rx_pos = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Unpack into ctx->adu. Length excludes LRC. */
|
||||
|
||||
ctx->adu.unit_id = bin[0];
|
||||
ctx->adu.fc = bin[1];
|
||||
memcpy(ctx->adu.data, &bin[2], (bin_len - 3));
|
||||
ctx->adu.crc = lrc_recv;
|
||||
ctx->adu.length = (bin_len - 1);
|
||||
|
||||
state->rx_pos = 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (state->rx_pos < sizeof(state->rx_buf))
|
||||
{
|
||||
state->rx_buf[state->rx_pos++] = c;
|
||||
}
|
||||
else
|
||||
{
|
||||
state->in_frame = false;
|
||||
state->rx_pos = 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
108
industry/nxmodbus/transport/nxmb_crc.c
Normal file
108
industry/nxmodbus/transport/nxmb_crc.c
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
/****************************************************************************
|
||||
* apps/industry/nxmodbus/transport/nxmb_crc.c
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership. The
|
||||
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Included Files
|
||||
****************************************************************************/
|
||||
|
||||
#include <nuttx/config.h>
|
||||
|
||||
#include <nuttx/compiler.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "nxmb_internal.h"
|
||||
|
||||
/****************************************************************************
|
||||
* Private Data
|
||||
****************************************************************************/
|
||||
|
||||
static const uint16_t g_crc16_table[256] =
|
||||
{
|
||||
0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241, 0xc601,
|
||||
0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440, 0xcc01, 0x0cc0,
|
||||
0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40, 0x0a00, 0xcac1, 0xcb81,
|
||||
0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841, 0xd801, 0x18c0, 0x1980, 0xd941,
|
||||
0x1b00, 0xdbc1, 0xda81, 0x1a40, 0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01,
|
||||
0x1dc0, 0x1c80, 0xdc41, 0x1400, 0xd4c1, 0xd581, 0x1540, 0xd701, 0x17c0,
|
||||
0x1680, 0xd641, 0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081,
|
||||
0x1040, 0xf001, 0x30c0, 0x3180, 0xf141, 0x3300, 0xf3c1, 0xf281, 0x3240,
|
||||
0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441, 0x3c00,
|
||||
0xfcc1, 0xfd81, 0x3d40, 0xff01, 0x3fc0, 0x3e80, 0xfe41, 0xfa01, 0x3ac0,
|
||||
0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840, 0x2800, 0xe8c1, 0xe981,
|
||||
0x2940, 0xeb01, 0x2bc0, 0x2a80, 0xea41, 0xee01, 0x2ec0, 0x2f80, 0xef41,
|
||||
0x2d00, 0xedc1, 0xec81, 0x2c40, 0xe401, 0x24c0, 0x2580, 0xe541, 0x2700,
|
||||
0xe7c1, 0xe681, 0x2640, 0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0,
|
||||
0x2080, 0xe041, 0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281,
|
||||
0x6240, 0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441,
|
||||
0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41, 0xaa01,
|
||||
0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840, 0x7800, 0xb8c1,
|
||||
0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41, 0xbe01, 0x7ec0, 0x7f80,
|
||||
0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40, 0xb401, 0x74c0, 0x7580, 0xb541,
|
||||
0x7700, 0xb7c1, 0xb681, 0x7640, 0x7200, 0xb2c1, 0xb381, 0x7340, 0xb101,
|
||||
0x71c0, 0x7080, 0xb041, 0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0,
|
||||
0x5280, 0x9241, 0x9601, 0x56c0, 0x5780, 0x9741, 0x5500, 0x95c1, 0x9481,
|
||||
0x5440, 0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40,
|
||||
0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901, 0x59c0, 0x5880, 0x9841, 0x8801,
|
||||
0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40, 0x4e00, 0x8ec1,
|
||||
0x8f81, 0x4f40, 0x8d01, 0x4dc0, 0x4c80, 0x8c41, 0x4400, 0x84c1, 0x8581,
|
||||
0x4540, 0x8701, 0x47c0, 0x4680, 0x8641, 0x8201, 0x42c0, 0x4380, 0x8341,
|
||||
0x4100, 0x81c1, 0x8081, 0x4040
|
||||
};
|
||||
|
||||
/****************************************************************************
|
||||
* Public Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_crc16
|
||||
*
|
||||
* Description:
|
||||
* Calculate Modbus RTU CRC16 checksum.
|
||||
*
|
||||
* Input Parameters:
|
||||
* buf - Pointer to data buffer
|
||||
* len - Length of data in bytes
|
||||
*
|
||||
* Returned Value:
|
||||
* CRC16 value
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
uint16_t nxmb_crc16(FAR const uint8_t *buf, uint16_t len)
|
||||
{
|
||||
uint16_t crc = 0xffff;
|
||||
uint8_t index;
|
||||
|
||||
if (buf == NULL || len == 0)
|
||||
{
|
||||
return crc;
|
||||
}
|
||||
|
||||
while (len--)
|
||||
{
|
||||
index = (crc ^ *buf++);
|
||||
crc = (crc >> 8) ^ g_crc16_table[index];
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
70
industry/nxmodbus/transport/nxmb_lrc.c
Normal file
70
industry/nxmodbus/transport/nxmb_lrc.c
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
/****************************************************************************
|
||||
* apps/industry/nxmodbus/transport/nxmb_lrc.c
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership. The
|
||||
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Included Files
|
||||
****************************************************************************/
|
||||
|
||||
#include <nuttx/config.h>
|
||||
|
||||
#include <nuttx/compiler.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "nxmb_internal.h"
|
||||
|
||||
/****************************************************************************
|
||||
* Public Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_lrc
|
||||
*
|
||||
* Description:
|
||||
* Calculate Modbus ASCII LRC checksum.
|
||||
*
|
||||
* Input Parameters:
|
||||
* buf - Pointer to data buffer
|
||||
* len - Length of data in bytes
|
||||
*
|
||||
* Returned Value:
|
||||
* LRC value (8-bit)
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
uint8_t nxmb_lrc(FAR const uint8_t *buf, uint16_t len)
|
||||
{
|
||||
uint8_t lrc = 0;
|
||||
uint16_t i;
|
||||
|
||||
if (buf == NULL || len == 0)
|
||||
{
|
||||
return lrc;
|
||||
}
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
lrc += buf[i];
|
||||
}
|
||||
|
||||
return (uint8_t)(-((int8_t)lrc));
|
||||
}
|
||||
303
industry/nxmodbus/transport/nxmb_raw.c
Normal file
303
industry/nxmodbus/transport/nxmb_raw.c
Normal file
|
|
@ -0,0 +1,303 @@
|
|||
/****************************************************************************
|
||||
* apps/industry/nxmodbus/transport/nxmb_raw.c
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership. The
|
||||
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Included Files
|
||||
****************************************************************************/
|
||||
|
||||
#include <nuttx/config.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <nxmodbus/nxmb_raw.h>
|
||||
#include <nxmodbus/nxmodbus.h>
|
||||
|
||||
#include "nxmb_internal.h"
|
||||
|
||||
/****************************************************************************
|
||||
* Pre-processor Definitions
|
||||
****************************************************************************/
|
||||
|
||||
#define MBAP_PROTO_ID 0x0000
|
||||
|
||||
/****************************************************************************
|
||||
* Private Types
|
||||
****************************************************************************/
|
||||
|
||||
/* Raw ADU transport state */
|
||||
|
||||
struct nxmb_raw_state_s
|
||||
{
|
||||
FAR void *user_data;
|
||||
nxmb_raw_tx_cb_t tx_callback;
|
||||
};
|
||||
|
||||
/****************************************************************************
|
||||
* Private Function Prototypes
|
||||
****************************************************************************/
|
||||
|
||||
static int nxmb_raw_init(nxmb_handle_t ctx);
|
||||
static int nxmb_raw_deinit(nxmb_handle_t ctx);
|
||||
static int nxmb_raw_send(nxmb_handle_t ctx);
|
||||
static int nxmb_raw_receive(nxmb_handle_t ctx);
|
||||
|
||||
/****************************************************************************
|
||||
* Public Data
|
||||
****************************************************************************/
|
||||
|
||||
const struct nxmb_transport_ops_s g_nxmb_raw_ops =
|
||||
{
|
||||
.init = nxmb_raw_init,
|
||||
.deinit = nxmb_raw_deinit,
|
||||
.send = nxmb_raw_send,
|
||||
.receive = nxmb_raw_receive,
|
||||
};
|
||||
|
||||
/****************************************************************************
|
||||
* Private Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_raw_init
|
||||
*
|
||||
* Description:
|
||||
* Initialize raw ADU transport.
|
||||
*
|
||||
* Input Parameters:
|
||||
* ctx - Instance context
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static int nxmb_raw_init(nxmb_handle_t ctx)
|
||||
{
|
||||
FAR const struct nxmb_raw_config_s *cfg;
|
||||
FAR struct nxmb_raw_state_s *state;
|
||||
|
||||
DEBUGASSERT(ctx);
|
||||
|
||||
cfg = &ctx->transport_cfg.raw;
|
||||
|
||||
if (cfg->tx_cb == NULL)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
state = malloc(sizeof(struct nxmb_raw_state_s));
|
||||
if (state == NULL)
|
||||
{
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
state->tx_callback = cfg->tx_cb;
|
||||
state->user_data = cfg->user_data;
|
||||
|
||||
ctx->transport_state = state;
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_raw_deinit
|
||||
*
|
||||
* Description:
|
||||
* Deinitialize raw ADU transport.
|
||||
*
|
||||
* Input Parameters:
|
||||
* ctx - Instance context
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static int nxmb_raw_deinit(nxmb_handle_t ctx)
|
||||
{
|
||||
DEBUGASSERT(ctx && ctx->transport_state);
|
||||
|
||||
free(ctx->transport_state);
|
||||
ctx->transport_state = NULL;
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_raw_send
|
||||
*
|
||||
* Description:
|
||||
* Send raw ADU via user callback.
|
||||
*
|
||||
* Input Parameters:
|
||||
* ctx - Instance context
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static int nxmb_raw_send(nxmb_handle_t ctx)
|
||||
{
|
||||
FAR struct nxmb_raw_state_s *state;
|
||||
int ret;
|
||||
|
||||
DEBUGASSERT(ctx && ctx->transport_state);
|
||||
|
||||
state = ctx->transport_state;
|
||||
|
||||
if (state->tx_callback == NULL)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ctx->adu.proto_id = MBAP_PROTO_ID;
|
||||
|
||||
ret = state->tx_callback(&ctx->adu, state->user_data);
|
||||
if (ret < 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_raw_receive
|
||||
*
|
||||
* Description:
|
||||
* Receive raw ADU (not used in raw mode, frames are submitted via
|
||||
* nxmb_raw_submit_rx).
|
||||
*
|
||||
* Input Parameters:
|
||||
* ctx - Instance context
|
||||
*
|
||||
* Returned Value:
|
||||
* -ENOTSUP (raw mode uses submit pattern, not polling)
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static int nxmb_raw_receive(nxmb_handle_t ctx)
|
||||
{
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Public Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_raw_submit_rx
|
||||
*
|
||||
* Description:
|
||||
* Submit a received raw ADU into the NxModbus protocol core.
|
||||
*
|
||||
* Input Parameters:
|
||||
* h - The NxModbus instance receiving the frame
|
||||
* adu - The received raw ADU container
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_raw_submit_rx(nxmb_handle_t ctx, FAR const struct nxmb_adu_s *adu)
|
||||
{
|
||||
DEBUGASSERT(ctx && adu);
|
||||
|
||||
if (adu->proto_id != MBAP_PROTO_ID)
|
||||
{
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
if (adu->length < 2)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if ((adu->length - 2) > NXMB_ADU_DATA_MAX)
|
||||
{
|
||||
return -EMSGSIZE;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&ctx->lock);
|
||||
ctx->adu = *adu;
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_raw_put_header
|
||||
*
|
||||
* Description:
|
||||
* Serialize the 8-byte MBAP+FC header for an ADU.
|
||||
*
|
||||
* Input Parameters:
|
||||
* adu - The ADU providing the header fields
|
||||
* hdr - The destination buffer for the serialized header
|
||||
*
|
||||
* Returned Value:
|
||||
* None
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
void nxmb_raw_put_header(FAR const struct nxmb_adu_s *adu, FAR uint8_t *hdr)
|
||||
{
|
||||
DEBUGASSERT(adu && hdr);
|
||||
|
||||
nxmb_util_put_u16_be(&hdr[0], adu->trans_id);
|
||||
nxmb_util_put_u16_be(&hdr[2], adu->proto_id);
|
||||
nxmb_util_put_u16_be(&hdr[4], adu->length);
|
||||
hdr[6] = adu->unit_id;
|
||||
hdr[7] = adu->fc;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_raw_get_header
|
||||
*
|
||||
* Description:
|
||||
* Parse the 8-byte MBAP+FC header into an ADU structure.
|
||||
*
|
||||
* Input Parameters:
|
||||
* adu - The destination ADU structure
|
||||
* hdr - The source buffer containing the serialized header
|
||||
*
|
||||
* Returned Value:
|
||||
* None
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
void nxmb_raw_get_header(FAR struct nxmb_adu_s *adu, FAR const uint8_t *hdr)
|
||||
{
|
||||
DEBUGASSERT(adu && hdr);
|
||||
|
||||
adu->trans_id = nxmb_util_get_u16_be(&hdr[0]);
|
||||
adu->proto_id = nxmb_util_get_u16_be(&hdr[2]);
|
||||
adu->length = nxmb_util_get_u16_be(&hdr[4]);
|
||||
adu->unit_id = hdr[6];
|
||||
adu->fc = hdr[7];
|
||||
}
|
||||
423
industry/nxmodbus/transport/nxmb_serial.c
Normal file
423
industry/nxmodbus/transport/nxmb_serial.c
Normal file
|
|
@ -0,0 +1,423 @@
|
|||
/****************************************************************************
|
||||
* apps/industry/nxmodbus/transport/nxmb_serial.c
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership. The
|
||||
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Included Files
|
||||
****************************************************************************/
|
||||
|
||||
#include <nuttx/config.h>
|
||||
|
||||
#include <nuttx/compiler.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/select.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifdef CONFIG_SERIAL_TERMIOS
|
||||
# include <termios.h>
|
||||
#endif
|
||||
|
||||
#include <nxmodbus/nxmodbus.h>
|
||||
|
||||
#include "nxmb_internal.h"
|
||||
|
||||
/****************************************************************************
|
||||
* Pre-processor Definitions
|
||||
****************************************************************************/
|
||||
|
||||
#define NXMB_RTU_MIN_FRAME_SIZE 5
|
||||
|
||||
/* Wire frame: unit_id + fc + data + 2 CRC bytes */
|
||||
|
||||
#define NXMB_RTU_FRAME_MAX (NXMB_ADU_DATA_MAX + 4)
|
||||
|
||||
/****************************************************************************
|
||||
* Private Types
|
||||
****************************************************************************/
|
||||
|
||||
struct nxmb_serial_state_s
|
||||
{
|
||||
int fd;
|
||||
uint32_t t35_usec;
|
||||
bool rx_frame_now;
|
||||
uint16_t rx_pos;
|
||||
uint8_t rx_buf[NXMB_RTU_FRAME_MAX];
|
||||
#ifdef CONFIG_SERIAL_TERMIOS
|
||||
struct termios old_tio;
|
||||
#endif
|
||||
};
|
||||
|
||||
/****************************************************************************
|
||||
* Private Function Prototypes
|
||||
****************************************************************************/
|
||||
|
||||
static uint32_t nxmb_serial_calculate_t35(uint32_t baudrate);
|
||||
static int nxmb_serial_init(nxmb_handle_t ctx);
|
||||
static int nxmb_serial_deinit(nxmb_handle_t ctx);
|
||||
static int nxmb_serial_send(nxmb_handle_t ctx);
|
||||
static int nxmb_serial_receive(nxmb_handle_t ctx);
|
||||
|
||||
/****************************************************************************
|
||||
* Public Data
|
||||
****************************************************************************/
|
||||
|
||||
const struct nxmb_transport_ops_s g_nxmb_serial_ops =
|
||||
{
|
||||
.init = nxmb_serial_init,
|
||||
.deinit = nxmb_serial_deinit,
|
||||
.send = nxmb_serial_send,
|
||||
.receive = nxmb_serial_receive
|
||||
};
|
||||
|
||||
/****************************************************************************
|
||||
* Private Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_serial_calculate_t35
|
||||
*
|
||||
* Description:
|
||||
* Calculate T3.5 character timeout in microseconds based on baud rate.
|
||||
*
|
||||
* Input Parameters:
|
||||
* baudrate - Serial baud rate
|
||||
*
|
||||
* Returned Value:
|
||||
* T3.5 timeout in microseconds
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static uint32_t nxmb_serial_calculate_t35(uint32_t baudrate)
|
||||
{
|
||||
if (baudrate <= 19200)
|
||||
{
|
||||
/* T3.5 = 3.5 * 11 bits/char * 1e6 us/s / baudrate
|
||||
* = 38500000 / baudrate (in microseconds).
|
||||
* Use integer arithmetic to avoid floating-point, which may
|
||||
* require FPU context save/restore on embedded targets.
|
||||
*/
|
||||
|
||||
return 38500000u / baudrate;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Per Modbus spec, fixed 1750 us for baud rates above 19200 */
|
||||
|
||||
return 1750;
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_serial_init
|
||||
*
|
||||
* Description:
|
||||
* Initialize serial RTU transport.
|
||||
*
|
||||
* Input Parameters:
|
||||
* ctx - Instance context
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static int nxmb_serial_init(nxmb_handle_t ctx)
|
||||
{
|
||||
FAR struct nxmb_serial_state_s *state;
|
||||
FAR const struct nxmb_serial_config_s *cfg;
|
||||
int ret;
|
||||
|
||||
DEBUGASSERT(ctx);
|
||||
|
||||
cfg = &ctx->transport_cfg.serial;
|
||||
|
||||
if (cfg->devpath == NULL)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
state = calloc(1, sizeof(struct nxmb_serial_state_s));
|
||||
if (state == NULL)
|
||||
{
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
state->fd = open(cfg->devpath, O_RDWR | O_NOCTTY);
|
||||
if (state->fd < 0)
|
||||
{
|
||||
ret = -errno;
|
||||
free(state);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SERIAL_TERMIOS
|
||||
if (tcgetattr(state->fd, &state->old_tio) < 0)
|
||||
{
|
||||
ret = -errno;
|
||||
close(state->fd);
|
||||
free(state);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = nxmb_serial_configure(state->fd, cfg->baudrate, cfg->parity);
|
||||
|
||||
if (ret < 0)
|
||||
{
|
||||
close(state->fd);
|
||||
free(state);
|
||||
return ret;
|
||||
}
|
||||
|
||||
tcflush(state->fd, TCIOFLUSH);
|
||||
#endif
|
||||
|
||||
state->t35_usec = nxmb_serial_calculate_t35(cfg->baudrate);
|
||||
state->rx_frame_now = false;
|
||||
|
||||
ctx->transport_state = state;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_serial_deinit
|
||||
*
|
||||
* Description:
|
||||
* Deinitialize serial RTU transport.
|
||||
*
|
||||
* Input Parameters:
|
||||
* ctx - Instance context
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static int nxmb_serial_deinit(nxmb_handle_t ctx)
|
||||
{
|
||||
FAR struct nxmb_serial_state_s *state;
|
||||
|
||||
DEBUGASSERT(ctx && ctx->transport_state);
|
||||
|
||||
state = ctx->transport_state;
|
||||
|
||||
#ifdef CONFIG_SERIAL_TERMIOS
|
||||
tcsetattr(state->fd, TCSANOW, &state->old_tio);
|
||||
#endif
|
||||
close(state->fd);
|
||||
free(state);
|
||||
|
||||
ctx->transport_state = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_serial_send
|
||||
*
|
||||
* Description:
|
||||
* Send RTU frame over serial port.
|
||||
*
|
||||
* Input Parameters:
|
||||
* ctx - Instance context
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static int nxmb_serial_send(nxmb_handle_t ctx)
|
||||
{
|
||||
FAR struct nxmb_serial_state_s *state;
|
||||
uint8_t frame[NXMB_RTU_FRAME_MAX];
|
||||
uint16_t data_len;
|
||||
uint16_t total;
|
||||
uint16_t crc;
|
||||
ssize_t written;
|
||||
|
||||
DEBUGASSERT(ctx && ctx->transport_state);
|
||||
|
||||
state = ctx->transport_state;
|
||||
|
||||
if (ctx->adu.length < 2)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
data_len = ctx->adu.length - 2;
|
||||
|
||||
/* Linearize the ADU and append CRC: unit_id, fc, data..., crc_lo, crc_hi */
|
||||
|
||||
frame[0] = ctx->adu.unit_id;
|
||||
frame[1] = ctx->adu.fc;
|
||||
memcpy(&frame[2], ctx->adu.data, data_len);
|
||||
|
||||
crc = nxmb_crc16(frame, ctx->adu.length);
|
||||
|
||||
frame[ctx->adu.length] = (crc & 0xff);
|
||||
frame[ctx->adu.length + 1] = (crc >> 8);
|
||||
|
||||
ctx->adu.crc = crc;
|
||||
|
||||
total = ctx->adu.length + 2;
|
||||
written = write(state->fd, frame, total);
|
||||
|
||||
if (written < 0)
|
||||
{
|
||||
return -errno;
|
||||
}
|
||||
|
||||
if (written != total)
|
||||
{
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_serial_receive
|
||||
*
|
||||
* Description:
|
||||
* Receive RTU frame from serial port with dynamic timeout.
|
||||
*
|
||||
* Input Parameters:
|
||||
* ctx - Instance context
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static int nxmb_serial_receive(nxmb_handle_t ctx)
|
||||
{
|
||||
FAR struct nxmb_serial_state_s *state;
|
||||
struct timeval timeout;
|
||||
fd_set readfds;
|
||||
ssize_t nread;
|
||||
uint16_t crc_calc;
|
||||
uint16_t crc_recv;
|
||||
uint16_t data_len;
|
||||
int ret;
|
||||
|
||||
DEBUGASSERT(ctx && ctx->transport_state);
|
||||
|
||||
state = ctx->transport_state;
|
||||
|
||||
FD_ZERO(&readfds);
|
||||
FD_SET(state->fd, &readfds);
|
||||
|
||||
if (state->rx_frame_now)
|
||||
{
|
||||
timeout.tv_sec = 0;
|
||||
timeout.tv_usec = state->t35_usec;
|
||||
}
|
||||
else
|
||||
{
|
||||
timeout.tv_sec = 0;
|
||||
timeout.tv_usec = CONFIG_NXMODBUS_RTU_IDLE_TIMEOUT_MS * 1000;
|
||||
}
|
||||
|
||||
ret = select(state->fd + 1, &readfds, NULL, NULL, &timeout);
|
||||
|
||||
if (ret < 0)
|
||||
{
|
||||
return -errno;
|
||||
}
|
||||
|
||||
if (ret == 0)
|
||||
{
|
||||
if (state->rx_frame_now)
|
||||
{
|
||||
state->rx_frame_now = false;
|
||||
|
||||
if (state->rx_pos < NXMB_RTU_MIN_FRAME_SIZE)
|
||||
{
|
||||
state->rx_pos = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
crc_calc = nxmb_crc16(state->rx_buf, state->rx_pos - 2);
|
||||
crc_recv = state->rx_buf[state->rx_pos - 2] |
|
||||
(state->rx_buf[state->rx_pos - 1] << 8);
|
||||
|
||||
if (crc_calc != crc_recv)
|
||||
{
|
||||
state->rx_pos = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Unpack frame into ctx->adu (excluding the trailing CRC). */
|
||||
|
||||
data_len = (state->rx_pos - 4);
|
||||
ctx->adu.unit_id = state->rx_buf[0];
|
||||
ctx->adu.fc = state->rx_buf[1];
|
||||
memcpy(ctx->adu.data, &state->rx_buf[2], data_len);
|
||||
ctx->adu.crc = crc_recv;
|
||||
ctx->adu.length = (state->rx_pos - 2);
|
||||
|
||||
state->rx_pos = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!FD_ISSET(state->fd, &readfds))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!state->rx_frame_now)
|
||||
{
|
||||
state->rx_pos = 0;
|
||||
state->rx_frame_now = true;
|
||||
}
|
||||
|
||||
nread = read(state->fd,
|
||||
&state->rx_buf[state->rx_pos],
|
||||
sizeof(state->rx_buf) - state->rx_pos);
|
||||
if (nread < 0)
|
||||
{
|
||||
return -errno;
|
||||
}
|
||||
|
||||
if (nread == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
state->rx_pos += nread;
|
||||
|
||||
if (state->rx_pos >= sizeof(state->rx_buf))
|
||||
{
|
||||
state->rx_pos = 0;
|
||||
state->rx_frame_now = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
138
industry/nxmodbus/transport/nxmb_serial_common.c
Normal file
138
industry/nxmodbus/transport/nxmb_serial_common.c
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
/****************************************************************************
|
||||
* apps/industry/nxmodbus/transport/nxmb_serial_common.c
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership. The
|
||||
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Included Files
|
||||
****************************************************************************/
|
||||
|
||||
#include <nuttx/config.h>
|
||||
|
||||
#include <nuttx/compiler.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef CONFIG_SERIAL_TERMIOS
|
||||
# include <termios.h>
|
||||
#endif
|
||||
|
||||
#include <nxmodbus/nxmodbus.h>
|
||||
|
||||
#include "nxmb_internal.h"
|
||||
|
||||
/****************************************************************************
|
||||
* Public Functions
|
||||
****************************************************************************/
|
||||
|
||||
#ifdef CONFIG_SERIAL_TERMIOS
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_serial_configure
|
||||
*
|
||||
* Description:
|
||||
* Configure serial port with termios settings for Modbus communication.
|
||||
*
|
||||
* Input Parameters:
|
||||
* fd - File descriptor
|
||||
* baudrate - Baud rate
|
||||
* parity - Parity setting
|
||||
*
|
||||
* Returned Value:
|
||||
* Zero on success; a negated errno value on failure
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int nxmb_serial_configure(int fd, uint32_t baudrate,
|
||||
enum nxmb_parity_e parity)
|
||||
{
|
||||
struct termios tio;
|
||||
speed_t speed;
|
||||
|
||||
if (tcgetattr(fd, &tio) < 0)
|
||||
{
|
||||
return -errno;
|
||||
}
|
||||
|
||||
switch (baudrate)
|
||||
{
|
||||
case 9600:
|
||||
speed = B9600;
|
||||
break;
|
||||
case 19200:
|
||||
speed = B19200;
|
||||
break;
|
||||
case 38400:
|
||||
speed = B38400;
|
||||
break;
|
||||
case 57600:
|
||||
speed = B57600;
|
||||
break;
|
||||
case 115200:
|
||||
speed = B115200;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
cfsetispeed(&tio, speed);
|
||||
cfsetospeed(&tio, speed);
|
||||
|
||||
tio.c_cflag &= ~(CSIZE | PARENB | PARODD | CSTOPB);
|
||||
tio.c_cflag |= CS8 | CLOCAL | CREAD;
|
||||
|
||||
switch (parity)
|
||||
{
|
||||
case NXMB_PAR_NONE:
|
||||
|
||||
/* Modbus spec: no parity requires 2 stop bits so the
|
||||
* character frame remains 11 bits.
|
||||
*/
|
||||
|
||||
tio.c_cflag |= CSTOPB;
|
||||
break;
|
||||
case NXMB_PAR_EVEN:
|
||||
tio.c_cflag |= PARENB;
|
||||
break;
|
||||
case NXMB_PAR_ODD:
|
||||
tio.c_cflag |= PARENB | PARODD;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
tio.c_iflag &=
|
||||
~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
|
||||
tio.c_oflag &= ~OPOST;
|
||||
tio.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
|
||||
|
||||
tio.c_cc[VMIN] = 0;
|
||||
tio.c_cc[VTIME] = 0;
|
||||
|
||||
if (tcsetattr(fd, TCSANOW, &tio) < 0)
|
||||
{
|
||||
return -errno;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_SERIAL_TERMIOS */
|
||||
684
industry/nxmodbus/transport/nxmb_tcp.c
Normal file
684
industry/nxmodbus/transport/nxmb_tcp.c
Normal file
|
|
@ -0,0 +1,684 @@
|
|||
/****************************************************************************
|
||||
* apps/industry/nxmodbus/transport/nxmb_tcp.c
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership. The
|
||||
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Included Files
|
||||
****************************************************************************/
|
||||
|
||||
#include <nuttx/config.h>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <netinet/in.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/uio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <nxmodbus/nxmb_raw.h>
|
||||
#include <nxmodbus/nxmodbus.h>
|
||||
|
||||
#include "nxmb_internal.h"
|
||||
|
||||
/****************************************************************************
|
||||
* Pre-processor Definitions
|
||||
****************************************************************************/
|
||||
|
||||
#define MBAP_HEADER_SIZE 7
|
||||
#define MBAP_FC_SIZE 8
|
||||
#define NXMB_TCP_RECV_MS 1000
|
||||
|
||||
/****************************************************************************
|
||||
* Private Types
|
||||
****************************************************************************/
|
||||
|
||||
struct nxmb_tcp_client_s
|
||||
{
|
||||
int fd;
|
||||
uint16_t trans_id;
|
||||
time_t last_activity;
|
||||
};
|
||||
|
||||
struct nxmb_tcp_state_s
|
||||
{
|
||||
struct nxmb_tcp_client_s clients[CONFIG_NXMODBUS_TCP_MAX_CLIENTS];
|
||||
int listen_fd;
|
||||
int active_idx;
|
||||
};
|
||||
|
||||
/****************************************************************************
|
||||
* Private Function Prototypes
|
||||
****************************************************************************/
|
||||
|
||||
static int nxmb_tcp_init(nxmb_handle_t ctx);
|
||||
static int nxmb_tcp_deinit(nxmb_handle_t ctx);
|
||||
static int nxmb_tcp_send(nxmb_handle_t ctx);
|
||||
static int nxmb_tcp_receive(nxmb_handle_t ctx);
|
||||
|
||||
/****************************************************************************
|
||||
* Public Data
|
||||
****************************************************************************/
|
||||
|
||||
const struct nxmb_transport_ops_s g_nxmb_tcp_ops =
|
||||
{
|
||||
.init = nxmb_tcp_init,
|
||||
.deinit = nxmb_tcp_deinit,
|
||||
.send = nxmb_tcp_send,
|
||||
.receive = nxmb_tcp_receive,
|
||||
};
|
||||
|
||||
/****************************************************************************
|
||||
* Private Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_tcp_put_header
|
||||
****************************************************************************/
|
||||
|
||||
static void nxmb_tcp_put_header(FAR const struct nxmb_adu_s *adu,
|
||||
FAR uint8_t *hdr)
|
||||
{
|
||||
nxmb_util_put_u16_be(&hdr[0], adu->trans_id);
|
||||
nxmb_util_put_u16_be(&hdr[2], adu->proto_id);
|
||||
nxmb_util_put_u16_be(&hdr[4], adu->length);
|
||||
hdr[6] = adu->unit_id;
|
||||
hdr[7] = adu->fc;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_tcp_get_header
|
||||
****************************************************************************/
|
||||
|
||||
static void nxmb_tcp_get_header(FAR struct nxmb_adu_s *adu,
|
||||
FAR const uint8_t *hdr)
|
||||
{
|
||||
adu->trans_id = nxmb_util_get_u16_be(&hdr[0]);
|
||||
adu->proto_id = nxmb_util_get_u16_be(&hdr[2]);
|
||||
adu->length = nxmb_util_get_u16_be(&hdr[4]);
|
||||
adu->unit_id = hdr[6];
|
||||
adu->fc = hdr[7];
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_tcp_create_server
|
||||
****************************************************************************/
|
||||
|
||||
static int nxmb_tcp_create_server(FAR const struct nxmb_tcp_config_s *cfg)
|
||||
{
|
||||
struct sockaddr_in addr;
|
||||
int listen_fd;
|
||||
int ret;
|
||||
#ifdef CONFIG_NET_SOCKOPTS
|
||||
int opt = 1;
|
||||
#endif
|
||||
|
||||
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (listen_fd < 0)
|
||||
{
|
||||
return -errno;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_NET_SOCKOPTS
|
||||
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
||||
#endif
|
||||
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(cfg->port);
|
||||
|
||||
if (cfg->bindaddr != NULL)
|
||||
{
|
||||
ret = inet_pton(AF_INET, cfg->bindaddr, &addr.sin_addr);
|
||||
if (ret != 1)
|
||||
{
|
||||
close(listen_fd);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
}
|
||||
|
||||
ret = bind(listen_fd, (FAR struct sockaddr *)&addr, sizeof(addr));
|
||||
if (ret < 0)
|
||||
{
|
||||
close(listen_fd);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
ret = listen(listen_fd, CONFIG_NXMODBUS_TCP_MAX_CLIENTS);
|
||||
if (ret < 0)
|
||||
{
|
||||
close(listen_fd);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
return listen_fd;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_tcp_connect_client
|
||||
****************************************************************************/
|
||||
|
||||
static int nxmb_tcp_connect_client(FAR const struct nxmb_tcp_config_s *cfg)
|
||||
{
|
||||
struct sockaddr_in addr;
|
||||
int client_fd;
|
||||
int ret;
|
||||
|
||||
if (cfg->host == NULL)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
client_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (client_fd < 0)
|
||||
{
|
||||
return -errno;
|
||||
}
|
||||
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(cfg->port);
|
||||
|
||||
ret = inet_pton(AF_INET, cfg->host, &addr.sin_addr);
|
||||
if (ret != 1)
|
||||
{
|
||||
close(client_fd);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = connect(client_fd, (FAR struct sockaddr *)&addr, sizeof(addr));
|
||||
if (ret < 0)
|
||||
{
|
||||
close(client_fd);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
return client_fd;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_tcp_recv_full
|
||||
****************************************************************************/
|
||||
|
||||
static int nxmb_tcp_recv_full(int fd, FAR uint8_t *buf, size_t len,
|
||||
int timeout_ms)
|
||||
{
|
||||
struct timeval tv;
|
||||
fd_set readfds;
|
||||
size_t received;
|
||||
int ret;
|
||||
|
||||
received = 0;
|
||||
|
||||
while (received < len)
|
||||
{
|
||||
FD_ZERO(&readfds);
|
||||
FD_SET(fd, &readfds);
|
||||
|
||||
tv.tv_sec = timeout_ms / 1000;
|
||||
tv.tv_usec = (timeout_ms % 1000) * 1000;
|
||||
|
||||
ret = select(fd + 1, &readfds, NULL, NULL, &tv);
|
||||
if (ret < 0)
|
||||
{
|
||||
return -errno;
|
||||
}
|
||||
else if (ret == 0)
|
||||
{
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
ret = recv(fd, &buf[received], len - received, 0);
|
||||
if (ret < 0)
|
||||
{
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return -errno;
|
||||
}
|
||||
else if (ret == 0)
|
||||
{
|
||||
return -ECONNRESET;
|
||||
}
|
||||
|
||||
received += ret;
|
||||
}
|
||||
|
||||
return received;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_tcp_init
|
||||
****************************************************************************/
|
||||
|
||||
static int nxmb_tcp_init(nxmb_handle_t ctx)
|
||||
{
|
||||
FAR const struct nxmb_tcp_config_s *cfg;
|
||||
FAR struct nxmb_tcp_state_s *state;
|
||||
int fd;
|
||||
int i;
|
||||
|
||||
DEBUGASSERT(ctx);
|
||||
|
||||
cfg = &ctx->transport_cfg.tcp;
|
||||
|
||||
state = calloc(1, sizeof(struct nxmb_tcp_state_s));
|
||||
if (state == NULL)
|
||||
{
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
state->listen_fd = -1;
|
||||
state->active_idx = -1;
|
||||
|
||||
for (i = 0; i < CONFIG_NXMODBUS_TCP_MAX_CLIENTS; i++)
|
||||
{
|
||||
state->clients[i].fd = -1;
|
||||
}
|
||||
|
||||
if (ctx->is_client)
|
||||
{
|
||||
fd = nxmb_tcp_connect_client(cfg);
|
||||
if (fd < 0)
|
||||
{
|
||||
free(state);
|
||||
return fd;
|
||||
}
|
||||
|
||||
state->clients[0].fd = fd;
|
||||
state->clients[0].last_activity = time(NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
fd = nxmb_tcp_create_server(cfg);
|
||||
if (fd < 0)
|
||||
{
|
||||
free(state);
|
||||
return fd;
|
||||
}
|
||||
|
||||
state->listen_fd = fd;
|
||||
}
|
||||
|
||||
ctx->transport_state = state;
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_tcp_deinit
|
||||
****************************************************************************/
|
||||
|
||||
static int nxmb_tcp_deinit(nxmb_handle_t ctx)
|
||||
{
|
||||
FAR struct nxmb_tcp_state_s *state;
|
||||
int i;
|
||||
|
||||
DEBUGASSERT(ctx && ctx->transport_state);
|
||||
|
||||
state = ctx->transport_state;
|
||||
|
||||
for (i = 0; i < CONFIG_NXMODBUS_TCP_MAX_CLIENTS; i++)
|
||||
{
|
||||
if (state->clients[i].fd >= 0)
|
||||
{
|
||||
close(state->clients[i].fd);
|
||||
}
|
||||
}
|
||||
|
||||
if (state->listen_fd >= 0)
|
||||
{
|
||||
close(state->listen_fd);
|
||||
}
|
||||
|
||||
free(state);
|
||||
ctx->transport_state = NULL;
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_tcp_send
|
||||
****************************************************************************/
|
||||
|
||||
static int nxmb_tcp_send(nxmb_handle_t ctx)
|
||||
{
|
||||
FAR struct nxmb_tcp_state_s *state;
|
||||
FAR struct nxmb_tcp_client_s *client;
|
||||
uint8_t header[MBAP_FC_SIZE];
|
||||
struct iovec iov[2];
|
||||
uint16_t data_len;
|
||||
uint16_t total;
|
||||
ssize_t sent;
|
||||
int fd;
|
||||
|
||||
DEBUGASSERT(ctx && ctx->transport_state);
|
||||
|
||||
state = ctx->transport_state;
|
||||
|
||||
/* For server mode, respond to the client that sent the request.
|
||||
* For client mode, use the single connection (index 0).
|
||||
*/
|
||||
|
||||
if (ctx->is_client)
|
||||
{
|
||||
client = &state->clients[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (state->active_idx < 0)
|
||||
{
|
||||
return -ENOTCONN;
|
||||
}
|
||||
|
||||
client = &state->clients[state->active_idx];
|
||||
}
|
||||
|
||||
fd = client->fd;
|
||||
if (fd < 0)
|
||||
{
|
||||
return -ENOTCONN;
|
||||
}
|
||||
|
||||
if (ctx->adu.length < 2)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Populate the MBAP fields before serializing the header */
|
||||
|
||||
ctx->adu.trans_id = client->trans_id;
|
||||
ctx->adu.proto_id = 0x0000;
|
||||
|
||||
data_len = ctx->adu.length - 2;
|
||||
|
||||
/* Emit MBAP header + PDU data via writev() to avoid a stack-side
|
||||
* copy. The kernel gathers both iovecs into a single TCP segment.
|
||||
*/
|
||||
|
||||
nxmb_tcp_put_header(&ctx->adu, header);
|
||||
|
||||
iov[0].iov_base = header;
|
||||
iov[0].iov_len = MBAP_FC_SIZE;
|
||||
iov[1].iov_base = ctx->adu.data;
|
||||
iov[1].iov_len = data_len;
|
||||
|
||||
total = MBAP_FC_SIZE + data_len;
|
||||
sent = writev(fd, iov, (data_len > 0) ? 2 : 1);
|
||||
if (sent != total)
|
||||
{
|
||||
return (sent < 0) ? -errno : -EIO;
|
||||
}
|
||||
|
||||
client->last_activity = time(NULL);
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_tcp_evict_idle
|
||||
*
|
||||
* Description:
|
||||
* Close idle client connections that exceed the configured timeout.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static void nxmb_tcp_evict_idle(FAR struct nxmb_tcp_state_s *state,
|
||||
time_t now)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < CONFIG_NXMODBUS_TCP_MAX_CLIENTS; i++)
|
||||
{
|
||||
if (state->clients[i].fd >= 0 &&
|
||||
(now - state->clients[i].last_activity) >
|
||||
CONFIG_NXMODBUS_TCP_TIMEOUT_SEC)
|
||||
{
|
||||
close(state->clients[i].fd);
|
||||
state->clients[i].fd = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_tcp_accept_new
|
||||
*
|
||||
* Description:
|
||||
* Accept a pending connection into the first free client slot.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static void nxmb_tcp_accept_new(FAR struct nxmb_tcp_state_s *state)
|
||||
{
|
||||
int fd;
|
||||
int i;
|
||||
|
||||
fd = accept(state->listen_fd, NULL, NULL);
|
||||
if (fd < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < CONFIG_NXMODBUS_TCP_MAX_CLIENTS; i++)
|
||||
{
|
||||
if (state->clients[i].fd < 0)
|
||||
{
|
||||
state->clients[i].fd = fd;
|
||||
state->clients[i].last_activity = time(NULL);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* No free slot -- reject the connection */
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmb_tcp_read_frame
|
||||
*
|
||||
* Description:
|
||||
* Read a complete Modbus TCP frame from a client connection.
|
||||
* On error (except timeout), the connection is closed.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static int nxmb_tcp_read_frame(nxmb_handle_t ctx,
|
||||
FAR struct nxmb_tcp_client_s *client)
|
||||
{
|
||||
uint8_t header[MBAP_FC_SIZE];
|
||||
uint16_t data_len;
|
||||
int ret;
|
||||
|
||||
ret = nxmb_tcp_recv_full(client->fd, header, MBAP_FC_SIZE,
|
||||
NXMB_TCP_RECV_MS);
|
||||
if (ret < 0)
|
||||
{
|
||||
if (ret == -ETIMEDOUT || ret == -EAGAIN)
|
||||
{
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
close(client->fd);
|
||||
client->fd = -1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
nxmb_tcp_get_header(&ctx->adu, header);
|
||||
|
||||
if (ctx->adu.proto_id != 0x0000)
|
||||
{
|
||||
close(client->fd);
|
||||
client->fd = -1;
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
if (ctx->adu.length < 2)
|
||||
{
|
||||
close(client->fd);
|
||||
client->fd = -1;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
data_len = ctx->adu.length - 2;
|
||||
|
||||
if (data_len > NXMB_ADU_DATA_MAX)
|
||||
{
|
||||
close(client->fd);
|
||||
client->fd = -1;
|
||||
return -EMSGSIZE;
|
||||
}
|
||||
|
||||
if (data_len > 0)
|
||||
{
|
||||
ret = nxmb_tcp_recv_full(client->fd, ctx->adu.data, data_len,
|
||||
NXMB_TCP_RECV_MS);
|
||||
if (ret < 0)
|
||||
{
|
||||
close(client->fd);
|
||||
client->fd = -1;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
client->trans_id = ctx->adu.trans_id;
|
||||
client->last_activity = time(NULL);
|
||||
|
||||
return ctx->adu.length;
|
||||
}
|
||||
|
||||
static int nxmb_tcp_receive(nxmb_handle_t ctx)
|
||||
{
|
||||
FAR struct nxmb_tcp_state_s *state;
|
||||
struct timeval tv;
|
||||
fd_set readfds;
|
||||
time_t now;
|
||||
int maxfd;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
DEBUGASSERT(ctx && ctx->transport_state);
|
||||
|
||||
state = ctx->transport_state;
|
||||
state->active_idx = -1;
|
||||
|
||||
/* Client mode: single connection, read directly */
|
||||
|
||||
if (ctx->is_client)
|
||||
{
|
||||
if (state->clients[0].fd < 0)
|
||||
{
|
||||
return -ENOTCONN;
|
||||
}
|
||||
|
||||
state->active_idx = 0;
|
||||
return nxmb_tcp_read_frame(ctx, &state->clients[0]);
|
||||
}
|
||||
|
||||
/* Server mode: multiplex listen socket + all client connections */
|
||||
|
||||
now = time(NULL);
|
||||
nxmb_tcp_evict_idle(state, now);
|
||||
|
||||
FD_ZERO(&readfds);
|
||||
maxfd = -1;
|
||||
|
||||
if (state->listen_fd >= 0)
|
||||
{
|
||||
FD_SET(state->listen_fd, &readfds);
|
||||
maxfd = state->listen_fd;
|
||||
}
|
||||
|
||||
for (i = 0; i < CONFIG_NXMODBUS_TCP_MAX_CLIENTS; i++)
|
||||
{
|
||||
if (state->clients[i].fd >= 0)
|
||||
{
|
||||
FD_SET(state->clients[i].fd, &readfds);
|
||||
if (state->clients[i].fd > maxfd)
|
||||
{
|
||||
maxfd = state->clients[i].fd;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (maxfd < 0)
|
||||
{
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 50000;
|
||||
|
||||
ret = select(maxfd + 1, &readfds, NULL, NULL, &tv);
|
||||
if (ret < 0)
|
||||
{
|
||||
return -errno;
|
||||
}
|
||||
|
||||
if (ret == 0)
|
||||
{
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
/* Accept new connections if listen socket is ready */
|
||||
|
||||
if (state->listen_fd >= 0 && FD_ISSET(state->listen_fd, &readfds))
|
||||
{
|
||||
nxmb_tcp_accept_new(state);
|
||||
}
|
||||
|
||||
/* Check each client for incoming data (first ready wins).
|
||||
* In server mode, a per-client read failure (disconnect, protocol
|
||||
* error, etc.) must not be fatal - nxmb_tcp_read_frame has already
|
||||
* closed the offending fd, and the listen socket remains open for new
|
||||
* connections. Translate any such error to -EAGAIN so the caller
|
||||
* keeps polling.
|
||||
*/
|
||||
|
||||
for (i = 0; i < CONFIG_NXMODBUS_TCP_MAX_CLIENTS; i++)
|
||||
{
|
||||
if (state->clients[i].fd >= 0 &&
|
||||
FD_ISSET(state->clients[i].fd, &readfds))
|
||||
{
|
||||
state->active_idx = i;
|
||||
ret = nxmb_tcp_read_frame(ctx, &state->clients[i]);
|
||||
if (ret < 0 && ret != -EAGAIN)
|
||||
{
|
||||
state->active_idx = -1;
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return -EAGAIN;
|
||||
}
|
||||
35
system/nxmbclient/CMakeLists.txt
Normal file
35
system/nxmbclient/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# ##############################################################################
|
||||
# apps/system/nxmbclient/CMakeLists.txt
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more contributor
|
||||
# license agreements. See the NOTICE file distributed with this work for
|
||||
# additional information regarding copyright ownership. The ASF licenses this
|
||||
# file to you under the Apache License, Version 2.0 (the "License"); you may not
|
||||
# use this file except in compliance with the License. You may obtain a copy of
|
||||
# the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations under
|
||||
# the License.
|
||||
#
|
||||
# ##############################################################################
|
||||
|
||||
if(CONFIG_SYSTEM_NXMBCLIENT)
|
||||
nuttx_add_application(
|
||||
NAME
|
||||
${CONFIG_NXMBCLIENT_PROGNAME}
|
||||
PRIORITY
|
||||
${CONFIG_NXMBCLIENT_PRIORITY}
|
||||
STACKSIZE
|
||||
${CONFIG_NXMBCLIENT_STACKSIZE}
|
||||
MODULE
|
||||
${CONFIG_SYSTEM_NXMBCLIENT}
|
||||
SRCS
|
||||
nxmbclient_main.c)
|
||||
endif()
|
||||
32
system/nxmbclient/Kconfig
Normal file
32
system/nxmbclient/Kconfig
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#
|
||||
# For a description of the syntax of this configuration file,
|
||||
# see the file kconfig-language.txt in the NuttX tools repository.
|
||||
#
|
||||
|
||||
config SYSTEM_NXMBCLIENT
|
||||
tristate "NxModbus Client Tool"
|
||||
default n
|
||||
depends on NXMODBUS_CLIENT
|
||||
---help---
|
||||
Enable the NxModbus client command-line tool. This utility allows
|
||||
you to perform Modbus client operations from the command line,
|
||||
supporting RTU, ASCII, and TCP transports.
|
||||
|
||||
if SYSTEM_NXMBCLIENT
|
||||
|
||||
config NXMBCLIENT_PROGNAME
|
||||
string "Program name"
|
||||
default "nxmbclient"
|
||||
---help---
|
||||
This is the name of the program that will be used when the NSH ELF
|
||||
program is installed.
|
||||
|
||||
config NXMBCLIENT_PRIORITY
|
||||
int "NxModbus client task priority"
|
||||
default 100
|
||||
|
||||
config NXMBCLIENT_STACKSIZE
|
||||
int "NxModbus client stack size"
|
||||
default DEFAULT_TASK_STACKSIZE
|
||||
|
||||
endif # SYSTEM_NXMBCLIENT
|
||||
25
system/nxmbclient/Make.defs
Normal file
25
system/nxmbclient/Make.defs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
############################################################################
|
||||
# apps/system/nxmbclient/Make.defs
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership. The
|
||||
# ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance with the
|
||||
# License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
############################################################################
|
||||
|
||||
ifneq ($(CONFIG_SYSTEM_NXMBCLIENT),)
|
||||
CONFIGURED_APPS += $(APPDIR)/system/nxmbclient
|
||||
endif
|
||||
32
system/nxmbclient/Makefile
Normal file
32
system/nxmbclient/Makefile
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
############################################################################
|
||||
# apps/system/nxmbclient/Makefile
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership. The
|
||||
# ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance with the
|
||||
# License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
############################################################################
|
||||
|
||||
include $(APPDIR)/Make.defs
|
||||
|
||||
MAINSRC = nxmbclient_main.c
|
||||
|
||||
PROGNAME = $(CONFIG_NXMBCLIENT_PROGNAME)
|
||||
PRIORITY = $(CONFIG_NXMBCLIENT_PRIORITY)
|
||||
STACKSIZE = $(CONFIG_NXMBCLIENT_STACKSIZE)
|
||||
MODULE = $(CONFIG_SYSTEM_NXMBCLIENT)
|
||||
|
||||
include $(APPDIR)/Application.mk
|
||||
799
system/nxmbclient/nxmbclient_main.c
Normal file
799
system/nxmbclient/nxmbclient_main.c
Normal file
|
|
@ -0,0 +1,799 @@
|
|||
/****************************************************************************
|
||||
* apps/system/nxmbclient/nxmbclient_main.c
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership. The
|
||||
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Included Files
|
||||
****************************************************************************/
|
||||
|
||||
#include <nuttx/config.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <nxmodbus/nxmb_client.h>
|
||||
#include <nxmodbus/nxmodbus.h>
|
||||
|
||||
/****************************************************************************
|
||||
* Pre-processor Definitions
|
||||
****************************************************************************/
|
||||
|
||||
#define DEFAULT_TIMEOUT_MS 1000
|
||||
#define DEFAULT_UNIT_ID 1
|
||||
#define DEFAULT_TCP_PORT 502
|
||||
#define DEFAULT_BAUDRATE 115200
|
||||
|
||||
/****************************************************************************
|
||||
* Private Types
|
||||
****************************************************************************/
|
||||
|
||||
/* Modbus client type */
|
||||
|
||||
enum transport_type_e
|
||||
{
|
||||
TRANSPORT_RTU = 0,
|
||||
TRANSPORT_ASCII,
|
||||
TRANSPORT_TCP
|
||||
};
|
||||
|
||||
/* Modbus client configuration */
|
||||
|
||||
struct nxmbclient_config_s
|
||||
{
|
||||
enum transport_type_e transport;
|
||||
enum nxmb_parity_e parity;
|
||||
FAR const char *device;
|
||||
FAR const char *host;
|
||||
uint32_t baudrate;
|
||||
uint16_t port;
|
||||
uint8_t unit_id;
|
||||
uint32_t timeout_ms;
|
||||
uint32_t poll_ms;
|
||||
};
|
||||
|
||||
/****************************************************************************
|
||||
* Private Data
|
||||
****************************************************************************/
|
||||
|
||||
static volatile bool g_running = true;
|
||||
|
||||
/****************************************************************************
|
||||
* Private Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: signal_handler
|
||||
****************************************************************************/
|
||||
|
||||
static void signal_handler(int signo)
|
||||
{
|
||||
g_running = false;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: show_usage
|
||||
****************************************************************************/
|
||||
|
||||
static void show_usage(FAR const char *progname)
|
||||
{
|
||||
printf("Usage: %s [OPTIONS] COMMAND [ARGS...]\n\n", progname);
|
||||
printf("Transport Options:\n");
|
||||
printf(" -t TYPE Transport type: rtu, ascii, tcp (required)\n");
|
||||
printf(" -d DEVICE Serial device path (for RTU/ASCII)\n");
|
||||
printf(" -b BAUD Baud rate (default: %u)\n", DEFAULT_BAUDRATE);
|
||||
printf(" -p PARITY Parity: none, even, odd (default: none)\n");
|
||||
printf(" -h HOST TCP host address (for TCP)\n");
|
||||
printf(" -P PORT TCP port (default: %u)\n", DEFAULT_TCP_PORT);
|
||||
printf("\nModbus Options:\n");
|
||||
printf(" -u UNIT Unit ID (default: %u)\n", DEFAULT_UNIT_ID);
|
||||
printf(" -T TIMEOUT Timeout in ms (default: %u)\n", DEFAULT_TIMEOUT_MS);
|
||||
printf(" --poll MS Polling interval in ms (0 = one-shot)\n");
|
||||
printf("\nCommands:\n");
|
||||
printf(" read-coils ADDR COUNT\n");
|
||||
printf(" read-discrete ADDR COUNT\n");
|
||||
printf(" read-input ADDR COUNT\n");
|
||||
printf(" read-holding ADDR COUNT\n");
|
||||
printf(" write-coil ADDR VALUE\n");
|
||||
printf(" write-holding ADDR VALUE\n");
|
||||
printf(" write-coils ADDR VALUE...\n");
|
||||
printf(" write-holdings ADDR VALUE...\n");
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: parse_parity
|
||||
****************************************************************************/
|
||||
|
||||
static int parse_parity(FAR const char *str, FAR enum nxmb_parity_e *parity)
|
||||
{
|
||||
if (strcmp(str, "none") == 0)
|
||||
{
|
||||
*parity = NXMB_PAR_NONE;
|
||||
}
|
||||
else if (strcmp(str, "even") == 0)
|
||||
{
|
||||
*parity = NXMB_PAR_EVEN;
|
||||
}
|
||||
else if (strcmp(str, "odd") == 0)
|
||||
{
|
||||
*parity = NXMB_PAR_ODD;
|
||||
}
|
||||
else
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: parse_transport
|
||||
****************************************************************************/
|
||||
|
||||
static int parse_transport(FAR const char *str,
|
||||
FAR enum transport_type_e *transport)
|
||||
{
|
||||
if (strcmp(str, "rtu") == 0)
|
||||
{
|
||||
*transport = TRANSPORT_RTU;
|
||||
}
|
||||
else if (strcmp(str, "ascii") == 0)
|
||||
{
|
||||
*transport = TRANSPORT_ASCII;
|
||||
}
|
||||
else if (strcmp(str, "tcp") == 0)
|
||||
{
|
||||
*transport = TRANSPORT_TCP;
|
||||
}
|
||||
else
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: cmd_read_coils
|
||||
****************************************************************************/
|
||||
|
||||
static int cmd_read_coils(nxmb_handle_t handle, uint8_t unit_id,
|
||||
int argc, FAR char **argv)
|
||||
{
|
||||
FAR uint8_t *values;
|
||||
uint16_t addr;
|
||||
uint16_t count;
|
||||
uint16_t nbytes;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
if (argc < 2)
|
||||
{
|
||||
fprintf(stderr, "Error: read-coils requires ADDR COUNT\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
addr = (uint16_t)strtoul(argv[0], NULL, 0);
|
||||
count = (uint16_t)strtoul(argv[1], NULL, 0);
|
||||
nbytes = (count + 7) / 8;
|
||||
|
||||
values = malloc(nbytes);
|
||||
if (values == NULL)
|
||||
{
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
memset(values, 0, nbytes);
|
||||
|
||||
ret = nxmb_read_coils(handle, unit_id, addr, count, values);
|
||||
if (ret < 0)
|
||||
{
|
||||
fprintf(stderr, "Error: read-coils failed: %d\n", ret);
|
||||
free(values);
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
printf("%u\t%u\n", addr + i,
|
||||
(values[i / 8] >> (i % 8)) & 1);
|
||||
}
|
||||
|
||||
free(values);
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: cmd_read_discrete
|
||||
****************************************************************************/
|
||||
|
||||
static int cmd_read_discrete(nxmb_handle_t handle, uint8_t unit_id,
|
||||
int argc, FAR char **argv)
|
||||
{
|
||||
FAR uint8_t *values;
|
||||
uint16_t addr;
|
||||
uint16_t count;
|
||||
uint16_t nbytes;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
if (argc < 2)
|
||||
{
|
||||
fprintf(stderr, "Error: read-discrete requires ADDR COUNT\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
addr = (uint16_t)strtoul(argv[0], NULL, 0);
|
||||
count = (uint16_t)strtoul(argv[1], NULL, 0);
|
||||
nbytes = (count + 7) / 8;
|
||||
|
||||
values = malloc(nbytes);
|
||||
if (values == NULL)
|
||||
{
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
memset(values, 0, nbytes);
|
||||
|
||||
ret = nxmb_read_discrete(handle, unit_id, addr, count, values);
|
||||
if (ret < 0)
|
||||
{
|
||||
fprintf(stderr, "Error: read-discrete failed: %d\n", ret);
|
||||
free(values);
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
printf("%u\t%u\n", addr + i,
|
||||
(values[i / 8] >> (i % 8)) & 1);
|
||||
}
|
||||
|
||||
free(values);
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: cmd_read_input
|
||||
****************************************************************************/
|
||||
|
||||
static int cmd_read_input(nxmb_handle_t handle, uint8_t unit_id,
|
||||
int argc, FAR char **argv)
|
||||
{
|
||||
FAR uint16_t *values;
|
||||
uint16_t addr;
|
||||
uint16_t count;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
if (argc < 2)
|
||||
{
|
||||
fprintf(stderr, "Error: read-input requires ADDR COUNT\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
addr = (uint16_t)strtoul(argv[0], NULL, 0);
|
||||
count = (uint16_t)strtoul(argv[1], NULL, 0);
|
||||
|
||||
values = malloc(count * sizeof(uint16_t));
|
||||
if (values == NULL)
|
||||
{
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ret = nxmb_read_input(handle, unit_id, addr, count, values);
|
||||
if (ret < 0)
|
||||
{
|
||||
fprintf(stderr, "Error: read-input failed: %d\n", ret);
|
||||
free(values);
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
printf("%u\t%u\n", addr + i, values[i]);
|
||||
}
|
||||
|
||||
free(values);
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: cmd_read_holding
|
||||
****************************************************************************/
|
||||
|
||||
static int cmd_read_holding(nxmb_handle_t handle, uint8_t unit_id,
|
||||
int argc, FAR char **argv)
|
||||
{
|
||||
FAR uint16_t *values;
|
||||
uint16_t addr;
|
||||
uint16_t count;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
if (argc < 2)
|
||||
{
|
||||
fprintf(stderr, "Error: read-holding requires ADDR COUNT\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
addr = (uint16_t)strtoul(argv[0], NULL, 0);
|
||||
count = (uint16_t)strtoul(argv[1], NULL, 0);
|
||||
|
||||
values = malloc(count * sizeof(uint16_t));
|
||||
if (values == NULL)
|
||||
{
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ret = nxmb_read_holding(handle, unit_id, addr, count, values);
|
||||
if (ret < 0)
|
||||
{
|
||||
fprintf(stderr, "Error: read-holding failed: %d\n", ret);
|
||||
free(values);
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
printf("%u\t%u\n", addr + i, values[i]);
|
||||
}
|
||||
|
||||
free(values);
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: cmd_write_coil
|
||||
****************************************************************************/
|
||||
|
||||
static int cmd_write_coil(nxmb_handle_t handle, uint8_t unit_id,
|
||||
int argc, FAR char **argv)
|
||||
{
|
||||
uint16_t addr;
|
||||
uint8_t value;
|
||||
int ret;
|
||||
|
||||
if (argc < 2)
|
||||
{
|
||||
fprintf(stderr, "Error: write-coil requires ADDR VALUE\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
addr = (uint16_t)strtoul(argv[0], NULL, 0);
|
||||
value = (uint8_t)strtoul(argv[1], NULL, 0);
|
||||
|
||||
ret = nxmb_write_coil(handle, unit_id, addr, value);
|
||||
if (ret < 0)
|
||||
{
|
||||
fprintf(stderr, "Error: write-coil failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
printf("OK\n");
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: cmd_write_holding
|
||||
****************************************************************************/
|
||||
|
||||
static int cmd_write_holding(nxmb_handle_t handle, uint8_t unit_id,
|
||||
int argc, FAR char **argv)
|
||||
{
|
||||
uint16_t addr;
|
||||
uint16_t value;
|
||||
int ret;
|
||||
|
||||
if (argc < 2)
|
||||
{
|
||||
fprintf(stderr, "Error: write-holding requires ADDR VALUE\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
addr = (uint16_t)strtoul(argv[0], NULL, 0);
|
||||
value = (uint16_t)strtoul(argv[1], NULL, 0);
|
||||
|
||||
ret = nxmb_write_holding(handle, unit_id, addr, value);
|
||||
if (ret < 0)
|
||||
{
|
||||
fprintf(stderr, "Error: write-holding failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
printf("OK\n");
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: cmd_write_coils
|
||||
****************************************************************************/
|
||||
|
||||
static int cmd_write_coils(nxmb_handle_t handle, uint8_t unit_id,
|
||||
int argc, FAR char **argv)
|
||||
{
|
||||
FAR uint8_t *values;
|
||||
uint16_t addr;
|
||||
uint16_t count;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
if (argc < 2)
|
||||
{
|
||||
fprintf(stderr, "Error: write-coils requires ADDR VALUE...\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
addr = (uint16_t)strtoul(argv[0], NULL, 0);
|
||||
count = argc - 1;
|
||||
|
||||
values = malloc(count);
|
||||
if (values == NULL)
|
||||
{
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
values[i] = (uint8_t)strtoul(argv[i + 1], NULL, 0);
|
||||
}
|
||||
|
||||
ret = nxmb_write_coils(handle, unit_id, addr, count, values);
|
||||
if (ret < 0)
|
||||
{
|
||||
fprintf(stderr, "Error: write-coils failed: %d\n", ret);
|
||||
free(values);
|
||||
return ret;
|
||||
}
|
||||
|
||||
free(values);
|
||||
printf("OK\n");
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: cmd_write_holdings
|
||||
****************************************************************************/
|
||||
|
||||
static int cmd_write_holdings(nxmb_handle_t handle, uint8_t unit_id,
|
||||
int argc, FAR char **argv)
|
||||
{
|
||||
FAR uint16_t *values;
|
||||
uint16_t addr;
|
||||
uint16_t count;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
if (argc < 2)
|
||||
{
|
||||
fprintf(stderr, "Error: write-holdings requires ADDR VALUE...\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
addr = (uint16_t)strtoul(argv[0], NULL, 0);
|
||||
count = argc - 1;
|
||||
|
||||
values = malloc(count * sizeof(uint16_t));
|
||||
if (values == NULL)
|
||||
{
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
values[i] = (uint16_t)strtoul(argv[i + 1], NULL, 0);
|
||||
}
|
||||
|
||||
ret = nxmb_write_holdings(handle, unit_id, addr, count, values);
|
||||
if (ret < 0)
|
||||
{
|
||||
fprintf(stderr, "Error: write-holdings failed: %d\n", ret);
|
||||
free(values);
|
||||
return ret;
|
||||
}
|
||||
|
||||
free(values);
|
||||
printf("OK\n");
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: execute_command
|
||||
****************************************************************************/
|
||||
|
||||
static int execute_command(nxmb_handle_t handle,
|
||||
uint8_t unit_id,
|
||||
FAR const char *cmd,
|
||||
int argc,
|
||||
FAR char **argv)
|
||||
{
|
||||
if (strcmp(cmd, "read-coils") == 0)
|
||||
{
|
||||
return cmd_read_coils(handle, unit_id, argc, argv);
|
||||
}
|
||||
else if (strcmp(cmd, "read-discrete") == 0)
|
||||
{
|
||||
return cmd_read_discrete(handle, unit_id, argc, argv);
|
||||
}
|
||||
else if (strcmp(cmd, "read-input") == 0)
|
||||
{
|
||||
return cmd_read_input(handle, unit_id, argc, argv);
|
||||
}
|
||||
else if (strcmp(cmd, "read-holding") == 0)
|
||||
{
|
||||
return cmd_read_holding(handle, unit_id, argc, argv);
|
||||
}
|
||||
else if (strcmp(cmd, "write-coil") == 0)
|
||||
{
|
||||
return cmd_write_coil(handle, unit_id, argc, argv);
|
||||
}
|
||||
else if (strcmp(cmd, "write-holding") == 0)
|
||||
{
|
||||
return cmd_write_holding(handle, unit_id, argc, argv);
|
||||
}
|
||||
else if (strcmp(cmd, "write-coils") == 0)
|
||||
{
|
||||
return cmd_write_coils(handle, unit_id, argc, argv);
|
||||
}
|
||||
else if (strcmp(cmd, "write-holdings") == 0)
|
||||
{
|
||||
return cmd_write_holdings(handle, unit_id, argc, argv);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Error: unknown command '%s'\n", cmd);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Public Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: nxmbclient_main
|
||||
****************************************************************************/
|
||||
|
||||
int main(int argc, FAR char *argv[])
|
||||
{
|
||||
struct nxmbclient_config_s config;
|
||||
struct nxmb_config_s mb_config;
|
||||
nxmb_handle_t handle = NULL;
|
||||
struct sigaction sa;
|
||||
FAR const char *cmd;
|
||||
int option;
|
||||
int ret;
|
||||
int cmd_argc;
|
||||
FAR char **cmd_argv;
|
||||
|
||||
/* Initialize config with defaults */
|
||||
|
||||
memset(&config, 0, sizeof(config));
|
||||
config.baudrate = DEFAULT_BAUDRATE;
|
||||
config.parity = NXMB_PAR_NONE;
|
||||
config.port = DEFAULT_TCP_PORT;
|
||||
config.unit_id = DEFAULT_UNIT_ID;
|
||||
config.timeout_ms = DEFAULT_TIMEOUT_MS;
|
||||
config.poll_ms = 0;
|
||||
|
||||
/* Parse options */
|
||||
|
||||
while ((option = getopt(argc, argv, "t:d:b:p:h:P:u:T:-:")) != -1)
|
||||
{
|
||||
switch (option)
|
||||
{
|
||||
case 't':
|
||||
if (parse_transport(optarg, &config.transport) < 0)
|
||||
{
|
||||
fprintf(stderr, "Error: invalid transport '%s'\n", optarg);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
config.device = optarg;
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
config.baudrate = strtoul(optarg, NULL, 0);
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
if (parse_parity(optarg, &config.parity) < 0)
|
||||
{
|
||||
fprintf(stderr, "Error: invalid parity '%s'\n", optarg);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
config.host = optarg;
|
||||
break;
|
||||
|
||||
case 'P':
|
||||
config.port = (uint16_t)strtoul(optarg, NULL, 0);
|
||||
break;
|
||||
|
||||
case 'u':
|
||||
config.unit_id = (uint8_t)strtoul(optarg, NULL, 0);
|
||||
break;
|
||||
|
||||
case 'T':
|
||||
config.timeout_ms = strtoul(optarg, NULL, 0);
|
||||
break;
|
||||
|
||||
case '-':
|
||||
if (strcmp(optarg, "poll") == 0)
|
||||
{
|
||||
if (optind < argc)
|
||||
{
|
||||
config.poll_ms = strtoul(argv[optind++], NULL, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Error: --poll requires argument\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Error: unknown option --%s\n", optarg);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
show_usage(argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check for command */
|
||||
|
||||
if (optind >= argc)
|
||||
{
|
||||
fprintf(stderr, "Error: no command specified\n");
|
||||
show_usage(argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
cmd = argv[optind];
|
||||
cmd_argc = argc - optind - 1;
|
||||
cmd_argv = &argv[optind + 1];
|
||||
|
||||
/* Validate transport-specific options */
|
||||
|
||||
if (config.transport == TRANSPORT_RTU ||
|
||||
config.transport == TRANSPORT_ASCII)
|
||||
{
|
||||
if (config.device == NULL)
|
||||
{
|
||||
fprintf(stderr, "Error: -d DEVICE required for RTU/ASCII\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
else if (config.transport == TRANSPORT_TCP)
|
||||
{
|
||||
if (config.host == NULL)
|
||||
{
|
||||
fprintf(stderr, "Error: -h HOST required for TCP\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
/* Setup signal handler for clean shutdown */
|
||||
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sa_handler = signal_handler;
|
||||
sigaction(SIGINT, &sa, NULL);
|
||||
sigaction(SIGTERM, &sa, NULL);
|
||||
|
||||
/* Create Modbus context */
|
||||
|
||||
memset(&mb_config, 0, sizeof(mb_config));
|
||||
mb_config.unit_id = config.unit_id;
|
||||
mb_config.is_client = true;
|
||||
|
||||
if (config.transport == TRANSPORT_RTU)
|
||||
{
|
||||
mb_config.mode = NXMB_MODE_RTU;
|
||||
mb_config.transport.serial.devpath = config.device;
|
||||
mb_config.transport.serial.baudrate = config.baudrate;
|
||||
mb_config.transport.serial.parity = config.parity;
|
||||
}
|
||||
else if (config.transport == TRANSPORT_ASCII)
|
||||
{
|
||||
mb_config.mode = NXMB_MODE_ASCII;
|
||||
mb_config.transport.serial.devpath = config.device;
|
||||
mb_config.transport.serial.baudrate = config.baudrate;
|
||||
mb_config.transport.serial.parity = config.parity;
|
||||
}
|
||||
else if (config.transport == TRANSPORT_TCP)
|
||||
{
|
||||
mb_config.mode = NXMB_MODE_TCP;
|
||||
mb_config.transport.tcp.host = config.host;
|
||||
mb_config.transport.tcp.port = config.port;
|
||||
}
|
||||
|
||||
ret = nxmb_create(&handle, &mb_config);
|
||||
if (ret < 0)
|
||||
{
|
||||
fprintf(stderr, "Error: failed to create Modbus context: %d\n", ret);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* Enable context */
|
||||
|
||||
ret = nxmb_enable(handle);
|
||||
if (ret < 0)
|
||||
{
|
||||
fprintf(stderr, "Error: failed to enable context: %d\n", ret);
|
||||
nxmb_destroy(handle);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* Set timeout */
|
||||
|
||||
ret = nxmb_set_timeout(handle, config.timeout_ms);
|
||||
if (ret < 0)
|
||||
{
|
||||
fprintf(stderr, "Error: failed to set timeout: %d\n", ret);
|
||||
nxmb_disable(handle);
|
||||
nxmb_destroy(handle);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* Execute command (with optional polling) */
|
||||
|
||||
if (config.poll_ms > 0)
|
||||
{
|
||||
/* Polling mode */
|
||||
|
||||
while (g_running)
|
||||
{
|
||||
ret = execute_command(handle, config.unit_id, cmd, cmd_argc,
|
||||
cmd_argv);
|
||||
if (ret < 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
usleep(config.poll_ms * 1000);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* One-shot mode */
|
||||
|
||||
ret = execute_command(handle, config.unit_id, cmd, cmd_argc,
|
||||
cmd_argv);
|
||||
}
|
||||
|
||||
/* Cleanup */
|
||||
|
||||
nxmb_disable(handle);
|
||||
nxmb_destroy(handle);
|
||||
|
||||
return (ret < 0) ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue