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:
raiden00pl 2026-04-13 14:37:52 +02:00 committed by Alan C. Assis
parent 2016c0b6ae
commit 773f69d5d9
36 changed files with 8137 additions and 0 deletions

View 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()

View 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

View 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

View 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

View 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;
}

View 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
View 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
View 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 */

View 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
View 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

View 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

View 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

View 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;
}

View 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;
}

View 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 */

View 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;
}

View 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;
}
}
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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 */

View 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;
}

View 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;
}

View 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));
}

View 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];
}

View 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;
}

View 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 */

View 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;
}

View 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
View 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

View 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

View 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

View 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;
}