From 2f38ba6023663b7303db587dfea0801f199ebffd Mon Sep 17 00:00:00 2001 From: mostafa hk Date: Mon, 30 Aug 2021 12:40:32 +0430 Subject: [PATCH 01/10] ESP8266 ESP32 compatible --- library.properties | 2 +- src/ModbusRTUClient.cpp | 4 ++-- src/ModbusRTUClient.h | 4 ++-- src/ModbusRTUServer.cpp | 4 ++-- src/ModbusRTUServer.h | 4 ++-- src/libmodbus/modbus-rtu-private.h | 2 +- src/libmodbus/modbus-rtu.cpp | 2 +- src/libmodbus/modbus-rtu.h | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/library.properties b/library.properties index cc8c2df..4d2b8c6 100644 --- a/library.properties +++ b/library.properties @@ -3,7 +3,7 @@ version=1.0.6 author=Arduino maintainer=Arduino sentence=Use Modbus equipment with your Arduino. -paragraph=Using TCP or RS485 shields, like the MKR 485 Shield. This library depends on the ArduinoRS485 library. +paragraph=Using TCP or RS485 shields, like the MKR 485 Shield and ESP32/ESP8266. This library depends on the ArduinoRS485 library. category=Communication url=https://www.arduino.cc/en/ArduinoModbus/ArduinoModbus architectures=* diff --git a/src/ModbusRTUClient.cpp b/src/ModbusRTUClient.cpp index 2b35227..5a14e8a 100644 --- a/src/ModbusRTUClient.cpp +++ b/src/ModbusRTUClient.cpp @@ -40,7 +40,7 @@ ModbusRTUClientClass::~ModbusRTUClientClass() { } -int ModbusRTUClientClass::begin(unsigned long baudrate, uint16_t config) +int ModbusRTUClientClass::begin(unsigned long baudrate, RS485_SER_CONF_TYPE config) { modbus_t* mb = modbus_new_rtu(_rs485, baudrate, config); @@ -51,7 +51,7 @@ int ModbusRTUClientClass::begin(unsigned long baudrate, uint16_t config) return 1; } -int ModbusRTUClientClass::begin(RS485Class& rs485, unsigned long baudrate, uint16_t config) +int ModbusRTUClientClass::begin(RS485Class& rs485, unsigned long baudrate, RS485_SER_CONF_TYPE config) { _rs485 = &rs485; return begin(baudrate, config); diff --git a/src/ModbusRTUClient.h b/src/ModbusRTUClient.h index 2667594..c1e6512 100644 --- a/src/ModbusRTUClient.h +++ b/src/ModbusRTUClient.h @@ -37,8 +37,8 @@ class ModbusRTUClientClass : public ModbusClient { * * Return 1 on success, 0 on failure */ - int begin(unsigned long baudrate, uint16_t config = SERIAL_8N1); - int begin(RS485Class& rs485, unsigned long baudrate, uint16_t config = SERIAL_8N1); + int begin(unsigned long baudrate, RS485_SER_CONF_TYPE config = SERIAL_8N1); + int begin(RS485Class& rs485, unsigned long baudrate, RS485_SER_CONF_TYPE config = SERIAL_8N1); private: RS485Class* _rs485 = &RS485; diff --git a/src/ModbusRTUServer.cpp b/src/ModbusRTUServer.cpp index e609aae..391ed0c 100644 --- a/src/ModbusRTUServer.cpp +++ b/src/ModbusRTUServer.cpp @@ -38,7 +38,7 @@ ModbusRTUServerClass::~ModbusRTUServerClass() { } -int ModbusRTUServerClass::begin(int id, unsigned long baudrate, uint16_t config) +int ModbusRTUServerClass::begin(int id, unsigned long baudrate, RS485_SER_CONF_TYPE config) { modbus_t* mb = modbus_new_rtu(_rs485, baudrate, config); @@ -51,7 +51,7 @@ int ModbusRTUServerClass::begin(int id, unsigned long baudrate, uint16_t config) return 1; } -int ModbusRTUServerClass::begin(RS485Class& rs485, int id, unsigned long baudrate, uint16_t config) +int ModbusRTUServerClass::begin(RS485Class& rs485, int id, unsigned long baudrate, RS485_SER_CONF_TYPE config) { _rs485 = &rs485; return begin(id, baudrate, config); diff --git a/src/ModbusRTUServer.h b/src/ModbusRTUServer.h index 46902d8..a30dd72 100644 --- a/src/ModbusRTUServer.h +++ b/src/ModbusRTUServer.h @@ -38,8 +38,8 @@ class ModbusRTUServerClass : public ModbusServer { * * Return 1 on success, 0 on failure */ - int begin(int id, unsigned long baudrate, uint16_t config = SERIAL_8N1); - int begin(RS485Class& rs485, int id, unsigned long baudrate, uint16_t config = SERIAL_8N1); + int begin(int id, unsigned long baudrate, RS485_SER_CONF_TYPE config = SERIAL_8N1); + int begin(RS485Class& rs485, int id, unsigned long baudrate, RS485_SER_CONF_TYPE config = SERIAL_8N1); /** * Poll interface for requests diff --git a/src/libmodbus/modbus-rtu-private.h b/src/libmodbus/modbus-rtu-private.h index 5cd6a8b..38933ab 100644 --- a/src/libmodbus/modbus-rtu-private.h +++ b/src/libmodbus/modbus-rtu-private.h @@ -48,7 +48,7 @@ struct win32_ser { typedef struct _modbus_rtu { #if defined(ARDUINO) unsigned long baud; - uint16_t config; + RS485_SER_CONF_TYPE config; RS485Class* rs485; #else /* Device: "/dev/ttyS0", "/dev/ttyUSB0" or "/dev/tty.USA19*" on Mac OS X. */ diff --git a/src/libmodbus/modbus-rtu.cpp b/src/libmodbus/modbus-rtu.cpp index c5a8302..9975f1b 100644 --- a/src/libmodbus/modbus-rtu.cpp +++ b/src/libmodbus/modbus-rtu.cpp @@ -1330,7 +1330,7 @@ const modbus_backend_t _modbus_rtu_backend = { }; #ifdef ARDUINO -modbus_t* modbus_new_rtu(RS485Class *rs485, unsigned long baud, uint16_t config) +modbus_t* modbus_new_rtu(RS485Class *rs485, unsigned long baud, RS485_SER_CONF_TYPE config) #else modbus_t* modbus_new_rtu(const char *device, int baud, char parity, int data_bit, diff --git a/src/libmodbus/modbus-rtu.h b/src/libmodbus/modbus-rtu.h index 9521114..adc4aad 100644 --- a/src/libmodbus/modbus-rtu.h +++ b/src/libmodbus/modbus-rtu.h @@ -19,7 +19,7 @@ MODBUS_BEGIN_DECLS #ifdef ARDUINO class RS485Class; -MODBUS_API modbus_t* modbus_new_rtu(RS485Class *rs485, unsigned long baud, uint16_t config); +MODBUS_API modbus_t* modbus_new_rtu(RS485Class *rs485, unsigned long baud, RS485_SER_CONF_TYPE config); #else MODBUS_API modbus_t* modbus_new_rtu(const char *device, int baud, char parity, int data_bit, int stop_bit); From 25d6ac01ebf0fba66e6e751c986bb67d646f4ec2 Mon Sep 17 00:00:00 2001 From: mostafa hk Date: Mon, 30 Aug 2021 14:48:44 +0430 Subject: [PATCH 02/10] version 1.0.7 --- library.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library.properties b/library.properties index 4d2b8c6..e4f682d 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=ArduinoModbus -version=1.0.6 +version=1.0.7 author=Arduino maintainer=Arduino sentence=Use Modbus equipment with your Arduino. From b3f1d0ca11178b84df3da6148f4c6d71c19f234f Mon Sep 17 00:00:00 2001 From: mostafa hk Date: Mon, 30 Aug 2021 15:12:41 +0430 Subject: [PATCH 03/10] depend --- library.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library.properties b/library.properties index e4f682d..48b1214 100644 --- a/library.properties +++ b/library.properties @@ -8,4 +8,4 @@ category=Communication url=https://www.arduino.cc/en/ArduinoModbus/ArduinoModbus architectures=* includes=ArduinoModbus.h -depends=ArduinoRS485 +depends=https://github.com/mostafahk/ArduinoRS485 From 56f16dd2e1d5f273178258d78a87a7246b23a596 Mon Sep 17 00:00:00 2001 From: mostafa hk Date: Wed, 13 Oct 2021 18:01:48 +0330 Subject: [PATCH 04/10] fix in ArduinoRS485 --- library.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library.properties b/library.properties index 48b1214..6e43546 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=ArduinoModbus -version=1.0.7 +version=1.0.71 author=Arduino maintainer=Arduino sentence=Use Modbus equipment with your Arduino. From cffd690f15fd6c987b9e7b0605d7d27c25cd0513 Mon Sep 17 00:00:00 2001 From: mostafa hk Date: Sat, 16 Oct 2021 09:21:37 +0330 Subject: [PATCH 05/10] depend --- library.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library.properties b/library.properties index 6e43546..c21de50 100644 --- a/library.properties +++ b/library.properties @@ -8,4 +8,4 @@ category=Communication url=https://www.arduino.cc/en/ArduinoModbus/ArduinoModbus architectures=* includes=ArduinoModbus.h -depends=https://github.com/mostafahk/ArduinoRS485 +depends=mostafahk/ArduinoRS485 From ece0e831d8a8632396ab8cdc53ba96c83fb1c577 Mon Sep 17 00:00:00 2001 From: mostafa hk Date: Sat, 16 Oct 2021 09:27:31 +0330 Subject: [PATCH 06/10] fix --- library.properties | 2 +- src/libmodbus/modbus.h | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/library.properties b/library.properties index c21de50..c3c9953 100644 --- a/library.properties +++ b/library.properties @@ -8,4 +8,4 @@ category=Communication url=https://www.arduino.cc/en/ArduinoModbus/ArduinoModbus architectures=* includes=ArduinoModbus.h -depends=mostafahk/ArduinoRS485 +depends=ArduinoRS485 diff --git a/src/libmodbus/modbus.h b/src/libmodbus/modbus.h index c7b5e0b..2f49858 100644 --- a/src/libmodbus/modbus.h +++ b/src/libmodbus/modbus.h @@ -58,6 +58,15 @@ MODBUS_BEGIN_DECLS #define ON 1 #endif +/* ESP8266 / ESP32 compatibility */ +#if defined(ESP8266) +#define RS485_SER_CONF_TYPE SerialConfig +#elif defined(ESP32) +#define RS485_SER_CONF_TYPE uint32_t +#else +#define RS485_SER_CONF_TYPE uint16_t +#endif + /* Modbus function codes */ #define MODBUS_FC_READ_COILS 0x01 #define MODBUS_FC_READ_DISCRETE_INPUTS 0x02 From cd2120bba90b698a88ea703b51b65e4b114cf8d0 Mon Sep 17 00:00:00 2001 From: mostafa hk Date: Sat, 16 Oct 2021 09:36:45 +0330 Subject: [PATCH 07/10] better define --- src/libmodbus/modbus.h | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/libmodbus/modbus.h b/src/libmodbus/modbus.h index 2f49858..d0e2dd1 100644 --- a/src/libmodbus/modbus.h +++ b/src/libmodbus/modbus.h @@ -59,12 +59,14 @@ MODBUS_BEGIN_DECLS #endif /* ESP8266 / ESP32 compatibility */ -#if defined(ESP8266) -#define RS485_SER_CONF_TYPE SerialConfig -#elif defined(ESP32) -#define RS485_SER_CONF_TYPE uint32_t -#else -#define RS485_SER_CONF_TYPE uint16_t +#ifndef RS485_SER_CONF_TYPE + #if defined(ESP8266) + #define RS485_SER_CONF_TYPE SerialConfig + #elif defined(ESP32) + #define RS485_SER_CONF_TYPE uint32_t + #else + #define RS485_SER_CONF_TYPE uint16_t + #endif #endif /* Modbus function codes */ From 5c357f810cbd7e9cd3778efc94530da70000b716 Mon Sep 17 00:00:00 2001 From: mostafahk <30733372+mostafahk@users.noreply.github.com> Date: Tue, 25 Jan 2022 15:14:05 +0330 Subject: [PATCH 08/10] Modbus ASCII Client --- src/ArduinoModbus.h | 2 + src/ModbusASCIIClient.cpp | 60 + src/ModbusASCIIClient.h | 49 + src/libmodbus/modbus-ascii-private.h | 85 ++ src/libmodbus/modbus-ascii.cpp | 1536 ++++++++++++++++++++++++++ src/libmodbus/modbus-ascii.h | 48 + src/libmodbus/modbus-private.h | 8 +- 7 files changed, 1785 insertions(+), 3 deletions(-) create mode 100644 src/ModbusASCIIClient.cpp create mode 100644 src/ModbusASCIIClient.h create mode 100644 src/libmodbus/modbus-ascii-private.h create mode 100644 src/libmodbus/modbus-ascii.cpp create mode 100644 src/libmodbus/modbus-ascii.h diff --git a/src/ArduinoModbus.h b/src/ArduinoModbus.h index 41f1615..a7622c2 100644 --- a/src/ArduinoModbus.h +++ b/src/ArduinoModbus.h @@ -20,6 +20,8 @@ #ifndef _ARDUINO_MODBUS_H_INCLUDED #define _ARDUINO_MODBUS_H_INCLUDED +#include "ModbusASCIIClient.h" + #include "ModbusRTUClient.h" #include "ModbusRTUServer.h" diff --git a/src/ModbusASCIIClient.cpp b/src/ModbusASCIIClient.cpp new file mode 100644 index 0000000..2c200af --- /dev/null +++ b/src/ModbusASCIIClient.cpp @@ -0,0 +1,60 @@ +/* + This file is part of the ArduinoModbus library. + Copyright (c) 2018 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include + +extern "C" { +#include "libmodbus/modbus.h" +#include "libmodbus/modbus-ascii.h" +} + +#include "ModbusASCIIClient.h" + +ModbusASCIIClientClass::ModbusASCIIClientClass() : + ModbusClient(1000) +{ +} + +ModbusASCIIClientClass::ModbusASCIIClientClass(RS485Class& rs485) : + ModbusClient(1000), _rs485(&rs485) +{ +} + +ModbusASCIIClientClass::~ModbusASCIIClientClass() +{ +} + +int ModbusASCIIClientClass::begin(unsigned long baudrate, RS485_SER_CONF_TYPE config) +{ + modbus_t* mb = modbus_new_ascii(_rs485, baudrate, config); + + if (!ModbusClient::begin(mb, 0x00)) { + return 0; + } + + return 1; +} + +int ModbusASCIIClientClass::begin(RS485Class& rs485, unsigned long baudrate, RS485_SER_CONF_TYPE config) +{ + _rs485 = &rs485; + return begin(baudrate, config); +} + +ModbusASCIIClientClass ModbusASCIIClient; diff --git a/src/ModbusASCIIClient.h b/src/ModbusASCIIClient.h new file mode 100644 index 0000000..99148d6 --- /dev/null +++ b/src/ModbusASCIIClient.h @@ -0,0 +1,49 @@ +/* + This file is part of the ArduinoModbus library. + Copyright (c) 2018 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _MODBUS_ASCII_CLIENT_H_INCLUDED +#define _MODBUS_ASCII_CLIENT_H_INCLUDED + +#include "ModbusClient.h" +#include + +class ModbusASCIIClientClass : public ModbusClient { +public: + ModbusASCIIClientClass(); + ModbusASCIIClientClass(RS485Class& rs485); + virtual ~ModbusASCIIClientClass(); + + /** + * Start the Modbus RTU client with the specified parameters + * + * @param baudrate Baud rate to use + * @param config serial config. to use defaults to SERIAL_8N1 + * + * Return 1 on success, 0 on failure + */ + int begin(unsigned long baudrate, RS485_SER_CONF_TYPE config = SERIAL_8N1); + int begin(RS485Class& rs485, unsigned long baudrate, RS485_SER_CONF_TYPE config = SERIAL_8N1); + +private: + RS485Class* _rs485 = &RS485; +}; + +extern ModbusASCIIClientClass ModbusASCIIClient; + +#endif diff --git a/src/libmodbus/modbus-ascii-private.h b/src/libmodbus/modbus-ascii-private.h new file mode 100644 index 0000000..5110b1f --- /dev/null +++ b/src/libmodbus/modbus-ascii-private.h @@ -0,0 +1,85 @@ +/* + * Copyright © 2001-2011 Stéphane Raimbault + * Copyright © 2018 Arduino SA. All rights reserved. + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#ifndef MODBUS_ASCII_PRIVATE_H +#define MODBUS_ASCII_PRIVATE_H + +#ifndef _MSC_VER +#include +#else +#include "stdint.h" +#endif + +#if defined(_WIN32) +#include +#elif defined(ARDUINO) +#include +#else +#include +#endif + +#define _MODBUS_ASCII_HEADER_LENGTH 1 +#define _MODBUS_ASCII_PRESET_REQ_LENGTH 6 +#define _MODBUS_ASCII_PRESET_RSP_LENGTH 2 + +#define _MODBUS_ASCII_CHECKSUM_LENGTH 1 + +#if defined(_WIN32) +#if !defined(ENOTSUP) +#define ENOTSUP WSAEOPNOTSUPP +#endif + +/* WIN32: struct containing serial handle and a receive buffer */ +#define PY_BUF_SIZE 512 +struct win32_ser { + /* File handle */ + HANDLE fd; + /* Receive buffer */ + uint8_t buf[PY_BUF_SIZE]; + /* Received chars */ + DWORD n_bytes; +}; +#endif /* _WIN32 */ + +typedef struct _modbus_ascii { +#if defined(ARDUINO) + unsigned long baud; + RS485_SER_CONF_TYPE config; + RS485Class* rs485; +#else + /* Device: "/dev/ttyS0", "/dev/ttyUSB0" or "/dev/tty.USA19*" on Mac OS X. */ + char *device; + /* Bauds: 9600, 19200, 57600, 115200, etc */ + int baud; + /* Data bit */ + uint8_t data_bit; + /* Stop bit */ + uint8_t stop_bit; + /* Parity: 'N', 'O', 'E' */ + char parity; +#if defined(_WIN32) + struct win32_ser w_ser; + DCB old_dcb; +#else + /* Save old termios settings */ + struct termios old_tios; +#endif +#if HAVE_DECL_TIOCSRS485 + int serial_mode; +#endif +#if HAVE_DECL_TIOCM_RTS + int rts; + int rts_delay; + int onebyte_time; + void (*set_rts) (modbus_t *ctx, int on); +#endif +#endif + /* To handle many slaves on the same link */ + int confirmation_to_ignore; +} modbus_ascii_t; + +#endif /* MODBUS_ASCII_PRIVATE_H */ diff --git a/src/libmodbus/modbus-ascii.cpp b/src/libmodbus/modbus-ascii.cpp new file mode 100644 index 0000000..5cbe25e --- /dev/null +++ b/src/libmodbus/modbus-ascii.cpp @@ -0,0 +1,1536 @@ +/* + * Copyright © 2001-2011 Stéphane Raimbault + * Copyright © 2018 Arduino SA. All rights reserved. + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#ifdef ARDUINO +#include +#include +#ifndef DEBUG +#define printf(...) \ + { \ + } +#define fprintf(...) \ + { \ + } +#endif +#endif + +#include +#include +#include +#include +#include +#if !defined(_MSC_VER) && !defined(ARDUINO) +#include +#endif +#include + +#include "modbus-private.h" + +#include "modbus-ascii.h" +#include "modbus-ascii-private.h" + +#if HAVE_DECL_TIOCSRS485 || HAVE_DECL_TIOCM_RTS +#include +#endif + +#if HAVE_DECL_TIOCSRS485 +#include +#endif + +#if defined(ARDUINO) && defined(__AVR__) +#include + +#undef EIO +#define EIO 5 + +#undef EINVAL +#define EINVAL 22 + +#undef ENOPROTOOPT +#define ENOPROTOOPT 109 + +#undef ECONNREFUSED +#define ECONNREFUSED 111 + +#undef ETIMEDOUT +#define ETIMEDOUT 116 + +#undef ENOTSUP +#define ENOTSUP 134 +#endif + +/* Define the slave ID of the remote device to talk in master mode or set the + * internal slave ID in slave mode */ +static int _modbus_set_slave(modbus_t *ctx, int slave) +{ + /* Broadcast address is 0 (MODBUS_BROADCAST_ADDRESS) */ + if (slave >= 0 && slave <= 247) + { + ctx->slave = slave; + } + else + { + errno = EINVAL; + return -1; + } + + return 0; +} + +/* Builds a ASCII request header */ +static int _modbus_ascii_build_request_basis(modbus_t *ctx, int function, + int addr, int nb, + uint8_t *req) +{ + assert(ctx->slave != -1); + req[0] = ctx->slave; + req[1] = function; + req[2] = addr >> 8; + req[3] = addr & 0x00ff; + req[4] = nb >> 8; + req[5] = nb & 0x00ff; + + return _MODBUS_ASCII_PRESET_REQ_LENGTH; +} + +/* Builds a ASCII response header */ +static int _modbus_ascii_build_response_basis(sft_t *sft, uint8_t *rsp) +{ + /* In this case, the slave is certainly valid because a check is already + * done in _modbus_ascii_listen */ + rsp[0] = sft->slave; + rsp[1] = sft->function; + + return _MODBUS_ASCII_PRESET_RSP_LENGTH; +} + +static char nibble_to_hex_ascii(uint8_t nibble) +{ + char c; + if (nibble < 10) + { + c = nibble + '0'; + } + else + { + c = nibble - 10 + 'A'; + } + return c; +} + +static uint8_t hex_ascii_to_nibble(char digit) +{ + if (digit >= '0' && digit <= '9') + { + return digit - '0'; + } + else if (digit >= 'A' && digit <= 'F') + { + return digit - 'A' + 10; + } + else if (digit >= 'a' && digit <= 'f') + { + return digit - 'a' + 10; + } + return 0xff; +} + +static uint16_t crc16(uint8_t *buffer, uint16_t buffer_length) +{ + return 0; +} + +static uint8_t lcr8(uint8_t *buffer, uint16_t buffer_length) +{ + uint8_t lcr = 0; + + /* pass through message buffer */ + while (buffer_length--) + { + lcr += *buffer++; /* calculate the lcr */ + } + + return -lcr; /* The sume of all raw databytes is 0 */ +} + +static int _modbus_ascii_prepare_response_tid(const uint8_t *req, int *req_length) +{ +#ifdef ARDUINO + (void)req; +#endif + (*req_length) -= _MODBUS_ASCII_CHECKSUM_LENGTH; + /* No TID */ + return 0; +} + +static int _modbus_ascii_send_msg_pre(uint8_t *req, int req_length) +{ + uint8_t lcr = lcr8(req, req_length); + req[req_length++] = lcr; + + return req_length; +} + +#if defined(_WIN32) + +/* This simple implementation is sort of a substitute of the select() call, + * working this way: the win32_ser_select() call tries to read some data from + * the serial port, setting the timeout as the select() call would. Data read is + * stored into the receive buffer, that is then consumed by the win32_ser_read() + * call. So win32_ser_select() does both the event waiting and the reading, + * while win32_ser_read() only consumes the receive buffer. + */ + +static void win32_ser_init(struct win32_ser *ws) +{ + /* Clear everything */ + memset(ws, 0x00, sizeof(struct win32_ser)); + + /* Set file handle to invalid */ + ws->fd = INVALID_HANDLE_VALUE; +} + +/* FIXME Try to remove length_to_read -> max_len argument, only used by win32 */ +static int win32_ser_select(struct win32_ser *ws, int max_len, + const struct timeval *tv) +{ + COMMTIMEOUTS comm_to; + unsigned int msec = 0; + + /* Check if some data still in the buffer to be consumed */ + if (ws->n_bytes > 0) + { + return 1; + } + + /* Setup timeouts like select() would do. + FIXME Please someone on Windows can look at this? + Does it possible to use WaitCommEvent? + When tv is NULL, MAXDWORD isn't infinite! + */ + if (tv == NULL) + { + msec = MAXDWORD; + } + else + { + msec = tv->tv_sec * 1000 + tv->tv_usec / 1000; + if (msec < 1) + msec = 1; + } + + comm_to.ReadIntervalTimeout = msec; + comm_to.ReadTotalTimeoutMultiplier = 0; + comm_to.ReadTotalTimeoutConstant = msec; + comm_to.WriteTotalTimeoutMultiplier = 0; + comm_to.WriteTotalTimeoutConstant = 1000; + SetCommTimeouts(ws->fd, &comm_to); + + /* Read some bytes */ + if ((max_len > PY_BUF_SIZE) || (max_len < 0)) + { + max_len = PY_BUF_SIZE; + } + + if (ReadFile(ws->fd, &ws->buf, max_len, &ws->n_bytes, NULL)) + { + /* Check if some bytes available */ + if (ws->n_bytes > 0) + { + /* Some bytes read */ + return 1; + } + else + { + /* Just timed out */ + return 0; + } + } + else + { + /* Some kind of error */ + return -1; + } +} + +static int win32_ser_read(struct win32_ser *ws, uint8_t *p_msg, + unsigned int max_len) +{ + unsigned int n = ws->n_bytes; + + if (max_len < n) + { + n = max_len; + } + + if (n > 0) + { + memcpy(p_msg, ws->buf, n); + } + + ws->n_bytes -= n; + + return n; +} +#endif + +#if HAVE_DECL_TIOCM_RTS +static void _modbus_ascii_ioctl_rts(modbus_t *ctx, int on) +{ + int fd = ctx->s; + int flags; + + ioctl(fd, TIOCMGET, &flags); + if (on) + { + flags |= TIOCM_RTS; + } + else + { + flags &= ~TIOCM_RTS; + } + ioctl(fd, TIOCMSET, &flags); +} +#endif + +static ssize_t _modbus_ascii_send(modbus_t *ctx, const uint8_t *req, int req_length) +{ +#if defined(_WIN32) + modbus_ascii_t *ctx_ascii = ctx->backend_data; + DWORD n_bytes = 0; + return (WriteFile(ctx_ascii->w_ser.fd, req, req_length, &n_bytes, NULL)) ? (ssize_t)n_bytes : -1; +#elif defined(ARDUINO) + modbus_ascii_t *ctx_ascii = (modbus_ascii_t *)ctx->backend_data; + + ssize_t size = 0; + + ctx_ascii->rs485->noReceive(); + ctx_ascii->rs485->beginTransmission(); + ctx_ascii->rs485->write(":", 1); + for (uint16_t i = 0; i < req_length; i++) + { + char tmp[4]; + sprintf(tmp, "%02X", req[i]); + ctx_ascii->rs485->write(tmp, 2); + size++; + } + ctx_ascii->rs485->write("\r\n", 2); + ctx_ascii->rs485->endTransmission(); + ctx_ascii->rs485->receive(); + + return size; +#else +#if HAVE_DECL_TIOCM_RTS + modbus_ascii_t *ctx_ascii = ctx->backend_data; + if (ctx_ascii->rts != MODBUS_ASCII_RTS_NONE) + { + ssize_t size; + + if (ctx->debug) + { + fprintf(stderr, "Sending request using RTS signal\n"); + } + + ctx_ascii->set_rts(ctx, ctx_ascii->rts == MODBUS_ASCII_RTS_UP); + usleep(ctx_ascii->rts_delay); + + size = write(ctx->s, req, req_length); + + usleep(ctx_ascii->onebyte_time * req_length + ctx_ascii->rts_delay); + ctx_ascii->set_rts(ctx, ctx_ascii->rts != MODBUS_ASCII_RTS_UP); + + return size; + } + else + { +#endif + return write(ctx->s, req, req_length); +#if HAVE_DECL_TIOCM_RTS + } +#endif +#endif +} + +static int _modbus_ascii_receive(modbus_t *ctx, uint8_t *req) +{ + int rc; +#ifdef ARDUINO + modbus_ascii_t *ctx_ascii = (modbus_ascii_t *)ctx->backend_data; +#else + modbus_ascii_t *ctx_ascii = ctx->backend_data; +#endif + + if (ctx_ascii->confirmation_to_ignore) + { + _modbus_receive_msg(ctx, req, MSG_CONFIRMATION); + /* Ignore errors and reset the flag */ + ctx_ascii->confirmation_to_ignore = FALSE; + rc = 0; + if (ctx->debug) + { + printf("Confirmation to ignore\n"); + } + } + else + { + rc = _modbus_receive_msg(ctx, req, MSG_INDICATION); + if (rc == 0) + { + /* The next expected message is a confirmation to ignore */ + ctx_ascii->confirmation_to_ignore = TRUE; + } + } + return rc; +} + +static ssize_t _modbus_ascii_recv(modbus_t *ctx, uint8_t *rsp, int rsp_length) +{ + +#if defined(_WIN32) + return win32_ser_read(&((modbus_ascii_t *)ctx->backend_data)->w_ser, rsp, rsp_length); +#elif defined(ARDUINO) + modbus_ascii_t *ctx_ascii = (modbus_ascii_t *)ctx->backend_data; + uint8_t tbyte, tbyte2, rs = 0; + while (rsp_length > 0) + { + if (ctx_ascii->rs485->readBytes(&tbyte, 1) == 1) + { + if (tbyte == ':' || tbyte == '\r' || tbyte == '\n') + ; + else + { + rsp_length--; + if (ctx_ascii->rs485->readBytes(&tbyte2, 1) == 1) + { + *rsp = hex_ascii_to_nibble(tbyte) << 4; + *rsp |= hex_ascii_to_nibble(tbyte2); + } + else + { + break; + } + rsp++; + rs++; + } + } + else + { + break; + } + } + return rs; +#else + return read(ctx->s, rsp, rsp_length); +#endif +} + +static int _modbus_ascii_flush(modbus_t *); + +static int _modbus_ascii_pre_check_confirmation(modbus_t *ctx, const uint8_t *req, + const uint8_t *rsp, int rsp_length) +{ +#ifdef ARDUINO + (void)rsp_length; +#endif + /* Check responding slave is the slave we requested (except for broacast + * request) */ + if (req[0] != rsp[0] && req[0] != MODBUS_BROADCAST_ADDRESS) + { + if (ctx->debug) + { + fprintf(stderr, + "The responding slave %d isn't the requested slave %d\n", + rsp[0], req[0]); + } + errno = EMBBADSLAVE; + return -1; + } + else + { + return 0; + } +} + +/* The check_crc16 function shall return 0 is the message is ignored and the + message length if the CRC is valid. Otherwise it shall return -1 and set + errno to EMBADCRC. */ +static int _modbus_ascii_check_integrity(modbus_t *ctx, uint8_t *msg, + const int msg_length) +{ + uint16_t crc_calculated; + uint16_t crc_received; + int slave = msg[0]; + + /* Filter on the Modbus unit identifier (slave) in ASCII mode to avoid useless + * CRC computing. */ + if (slave != ctx->slave && slave != MODBUS_BROADCAST_ADDRESS) + { + if (ctx->debug) + { + printf("Request for slave %d ignored (not %d)\n", slave, ctx->slave); + } + /* Following call to check_confirmation handles this error */ + return 0; + } + + crc_calculated = lcr8(msg, msg_length - 1); + crc_received = msg[msg_length - 1]; + + /* Check CRC of msg */ + if (crc_calculated == crc_received) + { + return msg_length; + } + else + { + if (ctx->debug) + { + fprintf(stderr, "ERROR CRC received 0x%0X != CRC calculated 0x%0X\n", + crc_received, crc_calculated); + } + + if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_PROTOCOL) + { + _modbus_ascii_flush(ctx); + } + errno = EMBBADCRC; + return -1; + } +} + +/* Sets up a serial port for ASCII communications */ +static int _modbus_ascii_connect(modbus_t *ctx) +{ +#if defined(_WIN32) + DCB dcb; +#elif defined(ARDUINO) + // nothing extra needed +#else + struct termios tios; + speed_t speed; + int flags; +#endif +#ifdef ARDUINO + modbus_ascii_t *ctx_ascii = (modbus_ascii_t *)ctx->backend_data; +#else + modbus_ascii_t *ctx_ascii = ctx->backend_data; + + if (ctx->debug) + { + printf("Opening %s at %d bauds (%c, %d, %d)\n", + ctx_ascii->device, ctx_ascii->baud, ctx_ascii->parity, + ctx_ascii->data_bit, ctx_ascii->stop_bit); + } +#endif + +#if defined(_WIN32) + /* Some references here: + * http://msdn.microsoft.com/en-us/library/aa450602.aspx + */ + win32_ser_init(&ctx_ascii->w_ser); + + /* ctx_ascii->device should contain a string like "COMxx:" xx being a decimal + * number */ + ctx_ascii->w_ser.fd = CreateFileA(ctx_ascii->device, + GENERIC_READ | GENERIC_WRITE, + 0, + NULL, + OPEN_EXISTING, + 0, + NULL); + + /* Error checking */ + if (ctx_ascii->w_ser.fd == INVALID_HANDLE_VALUE) + { + if (ctx->debug) + { + fprintf(stderr, "ERROR Can't open the device %s (LastError %d)\n", + ctx_ascii->device, (int)GetLastError()); + } + return -1; + } + + /* Save params */ + ctx_ascii->old_dcb.DCBlength = sizeof(DCB); + if (!GetCommState(ctx_ascii->w_ser.fd, &ctx_ascii->old_dcb)) + { + if (ctx->debug) + { + fprintf(stderr, "ERROR Error getting configuration (LastError %d)\n", + (int)GetLastError()); + } + CloseHandle(ctx_ascii->w_ser.fd); + ctx_ascii->w_ser.fd = INVALID_HANDLE_VALUE; + return -1; + } + + /* Build new configuration (starting from current settings) */ + dcb = ctx_ascii->old_dcb; + + /* Speed setting */ + switch (ctx_ascii->baud) + { + case 110: + dcb.BaudRate = CBR_110; + break; + case 300: + dcb.BaudRate = CBR_300; + break; + case 600: + dcb.BaudRate = CBR_600; + break; + case 1200: + dcb.BaudRate = CBR_1200; + break; + case 2400: + dcb.BaudRate = CBR_2400; + break; + case 4800: + dcb.BaudRate = CBR_4800; + break; + case 9600: + dcb.BaudRate = CBR_9600; + break; + case 14400: + dcb.BaudRate = CBR_14400; + break; + case 19200: + dcb.BaudRate = CBR_19200; + break; + case 38400: + dcb.BaudRate = CBR_38400; + break; + case 57600: + dcb.BaudRate = CBR_57600; + break; + case 115200: + dcb.BaudRate = CBR_115200; + break; + case 230400: + /* CBR_230400 - not defined */ + dcb.BaudRate = 230400; + break; + case 250000: + dcb.BaudRate = 250000; + break; + case 460800: + dcb.BaudRate = 460800; + break; + case 500000: + dcb.BaudRate = 500000; + break; + case 921600: + dcb.BaudRate = 921600; + break; + case 1000000: + dcb.BaudRate = 1000000; + break; + default: + dcb.BaudRate = CBR_9600; + if (ctx->debug) + { + fprintf(stderr, "WARNING Unknown baud rate %d for %s (B9600 used)\n", + ctx_ascii->baud, ctx_ascii->device); + } + } + + /* Data bits */ + switch (ctx_ascii->data_bit) + { + case 5: + dcb.ByteSize = 5; + break; + case 6: + dcb.ByteSize = 6; + break; + case 7: + dcb.ByteSize = 7; + break; + case 8: + default: + dcb.ByteSize = 8; + break; + } + + /* Stop bits */ + if (ctx_ascii->stop_bit == 1) + dcb.StopBits = ONESTOPBIT; + else /* 2 */ + dcb.StopBits = TWOSTOPBITS; + + /* Parity */ + if (ctx_ascii->parity == 'N') + { + dcb.Parity = NOPARITY; + dcb.fParity = FALSE; + } + else if (ctx_ascii->parity == 'E') + { + dcb.Parity = EVENPARITY; + dcb.fParity = TRUE; + } + else + { + /* odd */ + dcb.Parity = ODDPARITY; + dcb.fParity = TRUE; + } + + /* Hardware handshaking left as default settings retrieved */ + + /* No software handshaking */ + dcb.fTXContinueOnXoff = TRUE; + dcb.fOutX = FALSE; + dcb.fInX = FALSE; + + /* Binary mode (it's the only supported on Windows anyway) */ + dcb.fBinary = TRUE; + + /* Don't want errors to be blocking */ + dcb.fAbortOnError = FALSE; + + /* Setup port */ + if (!SetCommState(ctx_ascii->w_ser.fd, &dcb)) + { + if (ctx->debug) + { + fprintf(stderr, "ERROR Error setting new configuration (LastError %d)\n", + (int)GetLastError()); + } + CloseHandle(ctx_ascii->w_ser.fd); + ctx_ascii->w_ser.fd = INVALID_HANDLE_VALUE; + return -1; + } +#elif defined(ARDUINO) + ctx_ascii->rs485->begin(ctx_ascii->baud, ctx_ascii->config); + ctx_ascii->rs485->receive(); +#else + /* The O_NOCTTY flag tells UNIX that this program doesn't want + to be the "controlling terminal" for that port. If you + don't specify this then any input (such as keyboard abort + signals and so forth) will affect your process + + Timeouts are ignored in canonical input mode or when the + NDELAY option is set on the file via open or fcntl */ + flags = O_RDWR | O_NOCTTY | O_NDELAY | O_EXCL; +#ifdef O_CLOEXEC + flags |= O_CLOEXEC; +#endif + + ctx->s = open(ctx_ascii->device, flags); + if (ctx->s == -1) + { + if (ctx->debug) + { + fprintf(stderr, "ERROR Can't open the device %s (%s)\n", + ctx_ascii->device, strerror(errno)); + } + return -1; + } + + /* Save */ + tcgetattr(ctx->s, &ctx_ascii->old_tios); + + memset(&tios, 0, sizeof(struct termios)); + + /* C_ISPEED Input baud (new interface) + C_OSPEED Output baud (new interface) + */ + switch (ctx_ascii->baud) + { + case 110: + speed = B110; + break; + case 300: + speed = B300; + break; + case 600: + speed = B600; + break; + case 1200: + speed = B1200; + break; + case 2400: + speed = B2400; + break; + case 4800: + speed = B4800; + break; + case 9600: + speed = B9600; + break; + case 19200: + speed = B19200; + break; + case 38400: + speed = B38400; + break; +#ifdef B57600 + case 57600: + speed = B57600; + break; +#endif +#ifdef B115200 + case 115200: + speed = B115200; + break; +#endif +#ifdef B230400 + case 230400: + speed = B230400; + break; +#endif +#ifdef B460800 + case 460800: + speed = B460800; + break; +#endif +#ifdef B500000 + case 500000: + speed = B500000; + break; +#endif +#ifdef B576000 + case 576000: + speed = B576000; + break; +#endif +#ifdef B921600 + case 921600: + speed = B921600; + break; +#endif +#ifdef B1000000 + case 1000000: + speed = B1000000; + break; +#endif +#ifdef B1152000 + case 1152000: + speed = B1152000; + break; +#endif +#ifdef B1500000 + case 1500000: + speed = B1500000; + break; +#endif +#ifdef B2500000 + case 2500000: + speed = B2500000; + break; +#endif +#ifdef B3000000 + case 3000000: + speed = B3000000; + break; +#endif +#ifdef B3500000 + case 3500000: + speed = B3500000; + break; +#endif +#ifdef B4000000 + case 4000000: + speed = B4000000; + break; +#endif + default: + speed = B9600; + if (ctx->debug) + { + fprintf(stderr, + "WARNING Unknown baud rate %d for %s (B9600 used)\n", + ctx_ascii->baud, ctx_ascii->device); + } + } + + /* Set the baud rate */ + if ((cfsetispeed(&tios, speed) < 0) || + (cfsetospeed(&tios, speed) < 0)) + { + close(ctx->s); + ctx->s = -1; + return -1; + } + + /* C_CFLAG Control options + CLOCAL Local line - do not change "owner" of port + CREAD Enable receiver + */ + tios.c_cflag |= (CREAD | CLOCAL); + /* CSIZE, HUPCL, CRTSCTS (hardware flow control) */ + + /* Set data bits (5, 6, 7, 8 bits) + CSIZE Bit mask for data bits + */ + tios.c_cflag &= ~CSIZE; + switch (ctx_ascii->data_bit) + { + case 5: + tios.c_cflag |= CS5; + break; + case 6: + tios.c_cflag |= CS6; + break; + case 7: + tios.c_cflag |= CS7; + break; + case 8: + default: + tios.c_cflag |= CS8; + break; + } + + /* Stop bit (1 or 2) */ + if (ctx_ascii->stop_bit == 1) + tios.c_cflag &= ~CSTOPB; + else /* 2 */ + tios.c_cflag |= CSTOPB; + + /* PARENB Enable parity bit + PARODD Use odd parity instead of even */ + if (ctx_ascii->parity == 'N') + { + /* None */ + tios.c_cflag &= ~PARENB; + } + else if (ctx_ascii->parity == 'E') + { + /* Even */ + tios.c_cflag |= PARENB; + tios.c_cflag &= ~PARODD; + } + else + { + /* Odd */ + tios.c_cflag |= PARENB; + tios.c_cflag |= PARODD; + } + + /* Read the man page of termios if you need more information. */ + + /* This field isn't used on POSIX systems + tios.c_line = 0; + */ + + /* C_LFLAG Line options + + ISIG Enable SIGINTR, SIGSUSP, SIGDSUSP, and SIGQUIT signals + ICANON Enable canonical input (else raw) + XCASE Map uppercase \lowercase (obsolete) + ECHO Enable echoing of input characters + ECHOE Echo erase character as BS-SP-BS + ECHOK Echo NL after kill character + ECHONL Echo NL + NOFLSH Disable flushing of input buffers after + interrupt or quit characters + IEXTEN Enable extended functions + ECHOCTL Echo control characters as ^char and delete as ~? + ECHOPRT Echo erased character as character erased + ECHOKE BS-SP-BS entire line on line kill + FLUSHO Output being flushed + PENDIN Retype pending input at next read or input char + TOSTOP Send SIGTTOU for background output + + Canonical input is line-oriented. Input characters are put + into a buffer which can be edited interactively by the user + until a CR (carriage return) or LF (line feed) character is + received. + + Raw input is unprocessed. Input characters are passed + through exactly as they are received, when they are + received. Generally you'll deselect the ICANON, ECHO, + ECHOE, and ISIG options when using raw input + */ + + /* Raw input */ + tios.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); + + /* C_IFLAG Input options + + Constant Description + INPCK Enable parity check + IGNPAR Ignore parity errors + PARMRK Mark parity errors + ISTRIP Strip parity bits + IXON Enable software flow control (outgoing) + IXOFF Enable software flow control (incoming) + IXANY Allow any character to start flow again + IGNBRK Ignore break condition + BRKINT Send a SIGINT when a break condition is detected + INLCR Map NL to CR + IGNCR Ignore CR + ICRNL Map CR to NL + IUCLC Map uppercase to lowercase + IMAXBEL Echo BEL on input line too long + */ + if (ctx_ascii->parity == 'N') + { + /* None */ + tios.c_iflag &= ~INPCK; + } + else + { + tios.c_iflag |= INPCK; + } + + /* Software flow control is disabled */ + tios.c_iflag &= ~(IXON | IXOFF | IXANY); + + /* C_OFLAG Output options + OPOST Postprocess output (not set = raw output) + ONLCR Map NL to CR-NL + + ONCLR ant others needs OPOST to be enabled + */ + + /* Raw ouput */ + tios.c_oflag &= ~OPOST; + + /* C_CC Control characters + VMIN Minimum number of characters to read + VTIME Time to wait for data (tenths of seconds) + + UNIX serial interface drivers provide the ability to + specify character and packet timeouts. Two elements of the + c_cc array are used for timeouts: VMIN and VTIME. Timeouts + are ignored in canonical input mode or when the NDELAY + option is set on the file via open or fcntl. + + VMIN specifies the minimum number of characters to read. If + it is set to 0, then the VTIME value specifies the time to + wait for every character read. Note that this does not mean + that a read call for N bytes will wait for N characters to + come in. Rather, the timeout will apply to the first + character and the read call will return the number of + characters immediately available (up to the number you + request). + + If VMIN is non-zero, VTIME specifies the time to wait for + the first character read. If a character is read within the + time given, any read will block (wait) until all VMIN + characters are read. That is, once the first character is + read, the serial interface driver expects to receive an + entire packet of characters (VMIN bytes total). If no + character is read within the time allowed, then the call to + read returns 0. This method allows you to tell the serial + driver you need exactly N bytes and any read call will + return 0 or N bytes. However, the timeout only applies to + the first character read, so if for some reason the driver + misses one character inside the N byte packet then the read + call could block forever waiting for additional input + characters. + + VTIME specifies the amount of time to wait for incoming + characters in tenths of seconds. If VTIME is set to 0 (the + default), reads will block (wait) indefinitely unless the + NDELAY option is set on the port with open or fcntl. + */ + /* Unused because we use open with the NDELAY option */ + tios.c_cc[VMIN] = 0; + tios.c_cc[VTIME] = 0; + + if (tcsetattr(ctx->s, TCSANOW, &tios) < 0) + { + close(ctx->s); + ctx->s = -1; + return -1; + } +#endif + + return 0; +} + +#ifndef ARDUINO +int modbus_ascii_set_serial_mode(modbus_t *ctx, int mode) +{ + if (ctx == NULL) + { + errno = EINVAL; + return -1; + } + + if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_ASCII) + { +#if HAVE_DECL_TIOCSRS485 + modbus_ascii_t *ctx_ascii = ctx->backend_data; + struct serial_rs485 rs485conf; + memset(&rs485conf, 0x0, sizeof(struct serial_rs485)); + + if (mode == MODBUS_ASCII_RS485) + { + rs485conf.flags = SER_RS485_ENABLED; + if (ioctl(ctx->s, TIOCSRS485, &rs485conf) < 0) + { + return -1; + } + + ctx_ascii->serial_mode = MODBUS_ASCII_RS485; + return 0; + } + else if (mode == MODBUS_ASCII_RS232) + { + /* Turn off RS485 mode only if required */ + if (ctx_ascii->serial_mode == MODBUS_ASCII_RS485) + { + /* The ioctl call is avoided because it can fail on some RS232 ports */ + if (ioctl(ctx->s, TIOCSRS485, &rs485conf) < 0) + { + return -1; + } + } + ctx_ascii->serial_mode = MODBUS_ASCII_RS232; + return 0; + } +#else + if (ctx->debug) + { + fprintf(stderr, "This function isn't supported on your platform\n"); + } + errno = ENOTSUP; + return -1; +#endif + } + + /* Wrong backend and invalid mode specified */ + errno = EINVAL; + return -1; +} + +int modbus_ascii_get_serial_mode(modbus_t *ctx) +{ + if (ctx == NULL) + { + errno = EINVAL; + return -1; + } + + if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_ASCII) + { +#if HAVE_DECL_TIOCSRS485 + modbus_ascii_t *ctx_ascii = ctx->backend_data; + return ctx_ascii->serial_mode; +#else + if (ctx->debug) + { + fprintf(stderr, "This function isn't supported on your platform\n"); + } + errno = ENOTSUP; + return -1; +#endif + } + else + { + errno = EINVAL; + return -1; + } +} + +int modbus_ascii_get_rts(modbus_t *ctx) +{ + if (ctx == NULL) + { + errno = EINVAL; + return -1; + } + + if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_ASCII) + { +#if HAVE_DECL_TIOCM_RTS + modbus_ascii_t *ctx_ascii = ctx->backend_data; + return ctx_ascii->rts; +#else + if (ctx->debug) + { + fprintf(stderr, "This function isn't supported on your platform\n"); + } + errno = ENOTSUP; + return -1; +#endif + } + else + { + errno = EINVAL; + return -1; + } +} + +int modbus_ascii_set_rts(modbus_t *ctx, int mode) +{ + if (ctx == NULL) + { + errno = EINVAL; + return -1; + } + + if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_ASCII) + { +#if HAVE_DECL_TIOCM_RTS + modbus_ascii_t *ctx_ascii = ctx->backend_data; + + if (mode == MODBUS_ASCII_RTS_NONE || mode == MODBUS_ASCII_RTS_UP || + mode == MODBUS_ASCII_RTS_DOWN) + { + ctx_ascii->rts = mode; + + /* Set the RTS bit in order to not reserve the RS485 bus */ + ctx_ascii->set_rts(ctx, ctx_ascii->rts != MODBUS_ASCII_RTS_UP); + + return 0; + } + else + { + errno = EINVAL; + return -1; + } +#else + if (ctx->debug) + { + fprintf(stderr, "This function isn't supported on your platform\n"); + } + errno = ENOTSUP; + return -1; +#endif + } + /* Wrong backend or invalid mode specified */ + errno = EINVAL; + return -1; +} + +int modbus_ascii_set_custom_rts(modbus_t *ctx, void (*set_rts)(modbus_t *ctx, int on)) +{ + if (ctx == NULL) + { + errno = EINVAL; + return -1; + } + + if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_ASCII) + { +#if HAVE_DECL_TIOCM_RTS + modbus_ascii_t *ctx_ascii = ctx->backend_data; + ctx_ascii->set_rts = set_rts; + return 0; +#else + if (ctx->debug) + { + fprintf(stderr, "This function isn't supported on your platform\n"); + } + errno = ENOTSUP; + return -1; +#endif + } + else + { + errno = EINVAL; + return -1; + } +} + +int modbus_ascii_get_rts_delay(modbus_t *ctx) +{ + if (ctx == NULL) + { + errno = EINVAL; + return -1; + } + + if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_ASCII) + { +#if HAVE_DECL_TIOCM_RTS + modbus_ascii_t *ctx_ascii; + ctx_ascii = (modbus_ascii_t *)ctx->backend_data; + return ctx_ascii->rts_delay; +#else + if (ctx->debug) + { + fprintf(stderr, "This function isn't supported on your platform\n"); + } + errno = ENOTSUP; + return -1; +#endif + } + else + { + errno = EINVAL; + return -1; + } +} + +int modbus_ascii_set_rts_delay(modbus_t *ctx, int us) +{ + if (ctx == NULL || us < 0) + { + errno = EINVAL; + return -1; + } + + if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_ASCII) + { +#if HAVE_DECL_TIOCM_RTS + modbus_ascii_t *ctx_ascii; + ctx_ascii = (modbus_ascii_t *)ctx->backend_data; + ctx_ascii->rts_delay = us; + return 0; +#else + if (ctx->debug) + { + fprintf(stderr, "This function isn't supported on your platform\n"); + } + errno = ENOTSUP; + return -1; +#endif + } + else + { + errno = EINVAL; + return -1; + } +} +#endif + +static void _modbus_ascii_close(modbus_t *ctx) +{ + /* Restore line settings and close file descriptor in ASCII mode */ +#ifdef ARDUINO + modbus_ascii_t *ctx_ascii = (modbus_ascii_t *)ctx->backend_data; +#else + modbus_ascii_t *ctx_ascii = ctx->backend_data; +#endif + +#if defined(_WIN32) + /* Revert settings */ + if (!SetCommState(ctx_ascii->w_ser.fd, &ctx_ascii->old_dcb) && ctx->debug) + { + fprintf(stderr, "ERROR Couldn't revert to configuration (LastError %d)\n", + (int)GetLastError()); + } + + if (!CloseHandle(ctx_ascii->w_ser.fd) && ctx->debug) + { + fprintf(stderr, "ERROR Error while closing handle (LastError %d)\n", + (int)GetLastError()); + } +#elif defined(ARDUINO) + (void)ctx_ascii; + + ctx_ascii->rs485->noReceive(); + ctx_ascii->rs485->end(); +#else + if (ctx->s != -1) + { + tcsetattr(ctx->s, TCSANOW, &ctx_ascii->old_tios); + close(ctx->s); + ctx->s = -1; + } +#endif +} + +static int _modbus_ascii_flush(modbus_t *ctx) +{ +#if defined(_WIN32) + modbus_ascii_t *ctx_ascii = ctx->backend_data; + ctx_ascii->w_ser.n_bytes = 0; + return (PurgeComm(ctx_ascii->w_ser.fd, PURGE_RXCLEAR) == FALSE); +#elif defined(ARDUINO) + modbus_ascii_t *ctx_ascii = (modbus_ascii_t *)ctx->backend_data; + + while (ctx_ascii->rs485->available()) + { + ctx_ascii->rs485->read(); + } + + return 0; +#else + return tcflush(ctx->s, TCIOFLUSH); +#endif +} + +static int _modbus_ascii_select(modbus_t *ctx, fd_set *rset, + struct timeval *tv, int length_to_read) +{ + int s_rc; +#if defined(_WIN32) + s_rc = win32_ser_select(&((modbus_ascii_t *)ctx->backend_data)->w_ser, + length_to_read, tv); + if (s_rc == 0) + { + errno = ETIMEDOUT; + return -1; + } + + if (s_rc < 0) + { + return -1; + } +#elif defined(ARDUINO) + modbus_ascii_t *ctx_ascii = (modbus_ascii_t *)ctx->backend_data; + (void)rset; + + unsigned long wait_time_millis = (tv == NULL) ? 0 : (tv->tv_sec * 1000) + (tv->tv_usec / 1000); + unsigned long start = millis(); + + do + { + s_rc = ctx_ascii->rs485->available(); + + if (s_rc >= length_to_read) + { + break; + } + } while ((millis() - start) < wait_time_millis); + + if (s_rc == 0) + { + /* Timeout */ + errno = ETIMEDOUT; + return -1; + } +#else + while ((s_rc = select(ctx->s + 1, rset, NULL, NULL, tv)) == -1) + { + if (errno == EINTR) + { + if (ctx->debug) + { + fprintf(stderr, "A non blocked signal was caught\n"); + } + /* Necessary after an error */ + FD_ZERO(rset); + FD_SET(ctx->s, rset); + } + else + { + return -1; + } + } + + if (s_rc == 0) + { + /* Timeout */ + errno = ETIMEDOUT; + return -1; + } +#endif + + return s_rc; +} + +static void _modbus_ascii_free(modbus_t *ctx) +{ +#ifndef ARDUINO + free(((modbus_ascii_t *)ctx->backend_data)->device); +#endif + free(ctx->backend_data); + free(ctx); +} + +const modbus_backend_t _modbus_ascii_backend = { + _MODBUS_BACKEND_TYPE_ASCII, + _MODBUS_ASCII_HEADER_LENGTH, + _MODBUS_ASCII_CHECKSUM_LENGTH, + MODBUS_ASCII_MAX_ADU_LENGTH, + _modbus_set_slave, + _modbus_ascii_build_request_basis, + _modbus_ascii_build_response_basis, + _modbus_ascii_prepare_response_tid, + _modbus_ascii_send_msg_pre, + _modbus_ascii_send, + _modbus_ascii_receive, + _modbus_ascii_recv, + _modbus_ascii_check_integrity, + _modbus_ascii_pre_check_confirmation, + _modbus_ascii_connect, + _modbus_ascii_close, + _modbus_ascii_flush, + _modbus_ascii_select, + _modbus_ascii_free}; + +#ifdef ARDUINO +modbus_t *modbus_new_ascii(RS485Class *rs485, unsigned long baud, RS485_SER_CONF_TYPE config) +#else +modbus_t *modbus_new_ascii(const char *device, + int baud, char parity, int data_bit, + int stop_bit) +#endif +{ + modbus_t *ctx; + modbus_ascii_t *ctx_ascii; + +#ifndef ARDUINO + /* Check device argument */ + if (device == NULL || *device == 0) + { + fprintf(stderr, "The device string is empty\n"); + errno = EINVAL; + return NULL; + } + + /* Check baud argument */ + if (baud == 0) + { + fprintf(stderr, "The baud rate value must not be zero\n"); + errno = EINVAL; + return NULL; + } +#endif + + ctx = (modbus_t *)malloc(sizeof(modbus_t)); + _modbus_init_common(ctx); + ctx->backend = &_modbus_ascii_backend; + ctx->backend_data = (modbus_ascii_t *)malloc(sizeof(modbus_ascii_t)); + ctx_ascii = (modbus_ascii_t *)ctx->backend_data; +#ifdef ARDUINO + ctx_ascii->rs485 = rs485; + ctx_ascii->baud = baud; + ctx_ascii->config = config; +#else + ctx_ascii->device = NULL; + + /* Device name and \0 */ + ctx_ascii->device = (char *)malloc((strlen(device) + 1) * sizeof(char)); + strcpy(ctx_ascii->device, device); + + ctx_ascii->baud = baud; + if (parity == 'N' || parity == 'E' || parity == 'O') + { + ctx_ascii->parity = parity; + } + else + { + modbus_free(ctx); + errno = EINVAL; + return NULL; + } + ctx_ascii->data_bit = data_bit; + ctx_ascii->stop_bit = stop_bit; + +#if HAVE_DECL_TIOCSRS485 + /* The RS232 mode has been set by default */ + ctx_ascii->serial_mode = MODBUS_ASCII_RS232; +#endif + +#if HAVE_DECL_TIOCM_RTS + /* The RTS use has been set by default */ + ctx_ascii->rts = MODBUS_ASCII_RTS_NONE; + + /* Calculate estimated time in micro second to send one byte */ + ctx_ascii->onebyte_time = 1000000 * (1 + data_bit + (parity == 'N' ? 0 : 1) + stop_bit) / baud; + + /* The internal function is used by default to set RTS */ + ctx_ascii->set_rts = _modbus_ascii_ioctl_rts; + + /* The delay before and after transmission when toggling the RTS pin */ + ctx_ascii->rts_delay = ctx_ascii->onebyte_time; +#endif +#endif + + ctx_ascii->confirmation_to_ignore = FALSE; + + return ctx; +} diff --git a/src/libmodbus/modbus-ascii.h b/src/libmodbus/modbus-ascii.h new file mode 100644 index 0000000..1189929 --- /dev/null +++ b/src/libmodbus/modbus-ascii.h @@ -0,0 +1,48 @@ +/* + * Copyright © 2001-2011 Stéphane Raimbault + * Copyright © 2018 Arduino SA. All rights reserved. + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#ifndef MODBUS_ASCII_H +#define MODBUS_ASCII_H + +#include "modbus.h" + +MODBUS_BEGIN_DECLS + +/* Modbus_Application_Protocol_V1_1b.pdf Chapter 4 Section 1 Page 5 + * RS232 / RS485 ADU = 253 bytes + slave (1 byte) + CRC (2 bytes) = 256 bytes + */ +#define MODBUS_ASCII_MAX_ADU_LENGTH 256 + +#ifdef ARDUINO +class RS485Class; +MODBUS_API modbus_t* modbus_new_ascii(RS485Class *rs485, unsigned long baud, RS485_SER_CONF_TYPE config); +#else +MODBUS_API modbus_t* modbus_new_ascii(const char *device, int baud, char parity, + int data_bit, int stop_bit); + +#define MODBUS_ASCII_RS232 0 +#define MODBUS_ASCII_RS485 1 + +MODBUS_API int modbus_ascii_set_serial_mode(modbus_t *ctx, int mode); +MODBUS_API int modbus_ascii_get_serial_mode(modbus_t *ctx); + +#define MODBUS_ASCII_RTS_NONE 0 +#define MODBUS_ASCII_RTS_UP 1 +#define MODBUS_ASCII_RTS_DOWN 2 + +MODBUS_API int modbus_ascii_set_rts(modbus_t *ctx, int mode); +MODBUS_API int modbus_ascii_get_rts(modbus_t *ctx); + +MODBUS_API int modbus_ascii_set_custom_rts(modbus_t *ctx, void (*set_rts) (modbus_t *ctx, int on)); + +MODBUS_API int modbus_ascii_set_rts_delay(modbus_t *ctx, int us); +MODBUS_API int modbus_ascii_get_rts_delay(modbus_t *ctx); +#endif + +MODBUS_END_DECLS + +#endif /* MODBUS_ASCII_H */ diff --git a/src/libmodbus/modbus-private.h b/src/libmodbus/modbus-private.h index 79e7b93..a2dd983 100644 --- a/src/libmodbus/modbus-private.h +++ b/src/libmodbus/modbus-private.h @@ -53,9 +53,11 @@ MODBUS_BEGIN_DECLS #define _RESPONSE_TIMEOUT 500000 #define _BYTE_TIMEOUT 500000 -typedef enum { - _MODBUS_BACKEND_TYPE_RTU=0, - _MODBUS_BACKEND_TYPE_TCP +typedef enum +{ + _MODBUS_BACKEND_TYPE_RTU = 0, + _MODBUS_BACKEND_TYPE_TCP, + _MODBUS_BACKEND_TYPE_ASCII } modbus_backend_type_t; /* From 5245972eae371a77204e969d05ce5ac236f383f2 Mon Sep 17 00:00:00 2001 From: mostafahk <30733372+mostafahk@users.noreply.github.com> Date: Mon, 28 Feb 2022 12:19:34 +0330 Subject: [PATCH 09/10] include buxfix --- src/libmodbus/modbus-ascii.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libmodbus/modbus-ascii.cpp b/src/libmodbus/modbus-ascii.cpp index 5cbe25e..ec61b5e 100644 --- a/src/libmodbus/modbus-ascii.cpp +++ b/src/libmodbus/modbus-ascii.cpp @@ -7,7 +7,7 @@ #ifdef ARDUINO #include -#include + #ifndef DEBUG #define printf(...) \ { \ From a8e852e66a0fa8f926df2cbba82c9101dbcdab60 Mon Sep 17 00:00:00 2001 From: mostafahk <30733372+mostafahk@users.noreply.github.com> Date: Mon, 28 Feb 2022 12:20:31 +0330 Subject: [PATCH 10/10] include bugfix --- library.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library.properties b/library.properties index c3c9953..2a1e714 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=ArduinoModbus -version=1.0.71 +version=1.0.72 author=Arduino maintainer=Arduino sentence=Use Modbus equipment with your Arduino.