diff --git a/applications/EmsShow/.gitignore b/applications/EmsShow/.gitignore
new file mode 100644
index 0000000..4a0b530
--- /dev/null
+++ b/applications/EmsShow/.gitignore
@@ -0,0 +1,74 @@
+# This file is used to ignore files which are generated
+# ----------------------------------------------------------------------------
+
+*~
+*.autosave
+*.a
+*.core
+*.moc
+*.o
+*.obj
+*.orig
+*.rej
+*.so
+*.so.*
+*_pch.h.cpp
+*_resource.rc
+*.qm
+.#*
+*.*#
+core
+!core/
+tags
+.DS_Store
+.directory
+*.debug
+Makefile*
+*.prl
+*.app
+moc_*.cpp
+ui_*.h
+qrc_*.cpp
+Thumbs.db
+*.res
+*.rc
+/.qmake.cache
+/.qmake.stash
+
+# qtcreator generated files
+*.pro.user*
+CMakeLists.txt.user*
+
+# xemacs temporary files
+*.flc
+
+# Vim temporary files
+.*.swp
+
+# Visual Studio generated files
+*.ib_pdb_index
+*.idb
+*.ilk
+*.pdb
+*.sln
+*.suo
+*.vcproj
+*vcproj.*.*.user
+*.ncb
+*.sdf
+*.opensdf
+*.vcxproj
+*vcxproj.*
+
+# MinGW generated files
+*.Debug
+*.Release
+
+# Python byte code
+*.pyc
+
+# Binaries
+# --------
+*.dll
+*.exe
+
diff --git a/applications/EmsShow/EmsShow.pro b/applications/EmsShow/EmsShow.pro
new file mode 100644
index 0000000..959743d
--- /dev/null
+++ b/applications/EmsShow/EmsShow.pro
@@ -0,0 +1,34 @@
+QT += core gui serialport serialbus
+
+greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
+
+CONFIG += c++17
+
+# You can make your code fail to compile if it uses deprecated APIs.
+# In order to do so, uncomment the following line.
+#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
+
+SOURCES += \
+ main.cpp \
+ mainwindow.cpp \
+ modbuscontroller.cpp
+
+HEADERS += \
+ mainwindow.h \
+ modbuscontroller.h
+
+FORMS += \
+ mainwindow.ui
+
+#TRANSLATIONS += \
+# EmsShow_en_US.ts
+#CONFIG += lrelease
+#CONFIG += embed_translations
+
+# Default rules for deployment.
+qnx: target.path = /tmp/$${TARGET}/bin
+else: unix:!android: target.path = /opt/$${TARGET}/bin
+!isEmpty(target.path): INSTALLS += target
+
+DISTFILES += \
+ emsshow.ini
diff --git a/applications/EmsShow/EmsShow_en_US.ts b/applications/EmsShow/EmsShow_en_US.ts
new file mode 100644
index 0000000..edd0d34
--- /dev/null
+++ b/applications/EmsShow/EmsShow_en_US.ts
@@ -0,0 +1,3 @@
+
+
+
diff --git a/applications/EmsShow/emsshow.ini b/applications/EmsShow/emsshow.ini
new file mode 100644
index 0000000..a50261d
--- /dev/null
+++ b/applications/EmsShow/emsshow.ini
@@ -0,0 +1,49 @@
+;通用设置
+[general]
+ver="1.0.0"
+
+[modbus]
+;1=RTU 0=TCP
+type=1
+
+;if type=1, ignore ip and port setting
+ip="127.0.0.1"
+port=502
+
+com="COM20"
+baund=9600
+data=8
+
+;0=None 1=Odd 2=Even
+parity=0
+
+stop=1
+
+[slaves]
+;如果从机数量多于1,则从机地址slave_id以:分隔(英文冒号)
+slave_id="1"
+
+;每一个从机的配置
+;由两个节组成:slave_%d 和 slave_%d_function_%d
+
+[slave_0]
+;支持的功能码数量
+function_code_counts=1
+
+[slave_0_function_0]
+;功能码
+function_code=3
+
+;起始地址,10进制
+start_addr=40001
+
+;读取数量
+quantity=10
+
+;扫描间隔,单位ms
+interval=1000
+
+;serial_number与read_quantity的元素数量要一样
+equipment_code=81
+serial_number="0:1:2:3:4:5:6:7:8:9:10:11:12:13:14"
+read_quantity="1:1:1:1:1:1:1:1:1:1:1:1:1:1:1"
diff --git a/applications/EmsShow/main.cpp b/applications/EmsShow/main.cpp
new file mode 100644
index 0000000..ee1ac0b
--- /dev/null
+++ b/applications/EmsShow/main.cpp
@@ -0,0 +1,26 @@
+#include "mainwindow.h"
+
+#include
+#include
+#include
+
+int main(int argc, char *argv[])
+{
+ QApplication a(argc, argv);
+
+ QTranslator translator;
+ const QStringList uiLanguages = QLocale::system().uiLanguages();
+ for (const QString &locale : uiLanguages)
+ {
+ const QString baseName = "EmsShow_" + QLocale(locale).name();
+ if (translator.load(":/i18n/" + baseName))
+ {
+ a.installTranslator(&translator);
+ break;
+ }
+ }
+
+ MainWindow w;
+ w.show();
+ return a.exec();
+}
diff --git a/applications/EmsShow/mainwindow.cpp b/applications/EmsShow/mainwindow.cpp
new file mode 100644
index 0000000..32f649a
--- /dev/null
+++ b/applications/EmsShow/mainwindow.cpp
@@ -0,0 +1,35 @@
+#include "mainwindow.h"
+#include "ui_mainwindow.h"
+#include
+
+MainWindow::MainWindow(QWidget *parent)
+ : QMainWindow(parent)
+ , ui(new Ui::MainWindow)
+{
+ ui->setupUi(this);
+}
+
+MainWindow::~MainWindow()
+{
+ delete ui;
+}
+
+void MainWindow::on_btn_Initialize_clicked()
+{
+ QString appDir = QCoreApplication::applicationDirPath();
+ QString iniFilePath = appDir + QString::fromStdString("/emsshow.ini");
+
+ m_controller.getConfiguration(iniFilePath);
+}
+
+
+void MainWindow::on_btn_Read_clicked()
+{
+ if (!m_controller.Open())
+ {
+ QMessageBox::critical(this, tr("Critical Message"), tr("串口打开失败"));
+ return;
+ }
+ m_controller.Read();
+}
+
diff --git a/applications/EmsShow/mainwindow.h b/applications/EmsShow/mainwindow.h
new file mode 100644
index 0000000..d6e9255
--- /dev/null
+++ b/applications/EmsShow/mainwindow.h
@@ -0,0 +1,34 @@
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+#pragma execution_character_set("utf-8")
+
+#include
+#include "modbuscontroller.h"
+
+QT_BEGIN_NAMESPACE
+namespace Ui
+{
+class MainWindow;
+}
+QT_END_NAMESPACE
+
+class MainWindow : public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ MainWindow(QWidget *parent = nullptr);
+ ~MainWindow();
+
+private slots:
+ void on_btn_Initialize_clicked();
+
+ void on_btn_Read_clicked();
+
+private:
+ Ui::MainWindow *ui;
+
+protected:
+ ModbusController m_controller;
+};
+#endif // MAINWINDOW_H
diff --git a/applications/EmsShow/mainwindow.ui b/applications/EmsShow/mainwindow.ui
new file mode 100644
index 0000000..7399d6c
--- /dev/null
+++ b/applications/EmsShow/mainwindow.ui
@@ -0,0 +1,58 @@
+
+
+ MainWindow
+
+
+
+ 0
+ 0
+ 800
+ 600
+
+
+
+ MainWindow
+
+
+
+
+
+ 30
+ 30
+ 93
+ 29
+
+
+
+ Initialize
+
+
+
+
+
+ 130
+ 30
+ 121
+ 29
+
+
+
+ Read Modbus
+
+
+
+
+
+
+
+
+
diff --git a/applications/EmsShow/modbuscontroller.cpp b/applications/EmsShow/modbuscontroller.cpp
new file mode 100644
index 0000000..280b260
--- /dev/null
+++ b/applications/EmsShow/modbuscontroller.cpp
@@ -0,0 +1,191 @@
+#include "modbuscontroller.h"
+
+#define _DEBUG_VSPD_
+
+ModbusController::ModbusController():m_pModbusMaster(nullptr), m_pSettings(nullptr)
+{
+ m_version= "1.0.0";
+ m_modbus_type = 1;
+ m_modbus_ip = "127.0.0.1";
+ m_modbus_port = 502;
+ m_modbus_com = "COM1";
+ m_modbus_baund = 9600;
+ m_modbus_data = 1;
+ m_modbus_parity = 0;
+ m_modbus_stop = 1;
+}
+
+ModbusController::~ModbusController()
+{
+ if (m_pSettings!=nullptr)
+ {
+ delete m_pSettings;
+ m_pSettings = nullptr;
+ }
+
+ for (SlaveData* ptr : m_slaves)
+ {
+ delete ptr;
+ ptr = nullptr;
+ }
+
+ m_slaves.clear();
+ m_slaves.squeeze();
+
+ if (m_pModbusMaster != nullptr)
+ {
+ m_pModbusMaster->disconnectDevice();
+ delete m_pModbusMaster;
+ m_pModbusMaster = nullptr;
+ }
+}
+
+void ModbusController::getConfiguration(QString iniFilePath)
+{
+ m_pSettings = new QSettings(iniFilePath, QSettings::IniFormat);
+
+ m_version = m_pSettings->value("general/ver").toString();
+ m_modbus_type = m_pSettings->value("modbus/type").toInt();
+
+ m_modbus_ip = m_pSettings->value("modbus/ip").toString();
+ m_modbus_port = m_pSettings->value("modbus/port").toInt();
+
+ m_modbus_com = m_pSettings->value("modbus/com").toString();
+ m_modbus_baund = m_pSettings->value("modbus/baund").toInt();
+ m_modbus_data = m_pSettings->value("modbus/data").toInt();
+ m_modbus_parity = m_pSettings->value("modbus/parity").toInt();
+ m_modbus_stop = m_pSettings->value("modbus/stop").toInt();
+
+ QStringList slaves_list = m_pSettings->value("slaves/slave_id").toString().split(":", Qt::SkipEmptyParts);
+
+ int slave_id = 0;
+ foreach (const QString &str, slaves_list)
+ {
+ QString app = QString("slave_%1/function_code_counts").arg(slave_id);
+
+ int functions_count = m_pSettings->value(app).toInt();
+ for(int i=0; ivalue(app2 + "/equipment_code").toInt();
+ equipmentCode = equipmentCode << 9;
+ equipmentCode &= 0xFE00; // 二进制掩码: 1111 1110 0000 0000(保留高7位)
+
+ QStringList sn_list = m_pSettings->value(app2 + "/serial_number").toString().split(":", Qt::SkipEmptyParts);
+ QStringList quantity_list = m_pSettings->value(app2 + "/read_quantity").toString().split(":", Qt::SkipEmptyParts);
+
+ assert(sn_list.size() == quantity_list.size());
+
+ int j = 0;
+ foreach(const QString sn, sn_list)
+ {
+ SlaveData* pSlaveData = new SlaveData();
+ pSlaveData->id = str.toInt();
+ pSlaveData->function_code = m_pSettings->value(app2 + "/function_code").toInt();
+ pSlaveData->interval = m_pSettings->value(app2 + "/interval").toInt();
+
+
+ unsigned short serialNumber = sn.toInt();
+
+ serialNumber &= 0x01FF; // 二进制掩码: 0000 0001 1111 1111(保留低9位)
+
+ // 组合操作:高7位左移9位,低9位直接填充
+ unsigned short address = pSlaveData->start_address = (equipmentCode ) | serialNumber; // 关键位操作
+
+ pSlaveData->start_address = address;
+ pSlaveData->quantity = quantity_list.at(j).toInt();
+ m_slaves.push_back(pSlaveData);
+ j++;
+ }
+#else
+ SlaveData* pSlaveData = new SlaveData();
+ pSlaveData->id = str.toInt();
+ pSlaveData->function_code = m_pSettings->value(app2 + "/function_code").toInt();
+ pSlaveData->interval = m_pSettings->value(app2 + "/interval").toInt();
+ pSlaveData->quantity = m_pSettings->value(app2 + "/quantity").toInt();
+ pSlaveData->start_address = m_pSettings->value(app2 + "/start_addr").toInt();
+ m_slaves.push_back(pSlaveData);
+#endif
+ }
+
+ slave_id++;
+ }
+}
+
+bool ModbusController::Open()
+{
+ if (m_modbus_type == 1)
+ {
+ return modbus_rtu();
+ }
+ else
+ {
+ return modbus_tcp();
+ }
+}
+
+bool ModbusController::modbus_rtu()
+{
+ m_pModbusMaster = new QModbusRtuSerialClient();
+ m_pModbusMaster->setConnectionParameter(QModbusDevice::SerialPortNameParameter, m_modbus_com); //串口号
+ m_pModbusMaster->setConnectionParameter(QModbusDevice::SerialBaudRateParameter, m_modbus_baund); //波特率
+ m_pModbusMaster->setConnectionParameter(QModbusDevice::SerialParityParameter, m_modbus_parity); //无校验
+ m_pModbusMaster->setConnectionParameter(QModbusDevice::SerialDataBitsParameter, m_modbus_data); //数据位
+ m_pModbusMaster->setConnectionParameter(QModbusDevice::SerialStopBitsParameter, m_modbus_stop); //停止位
+ m_pModbusMaster->setTimeout(1000); // 设置超时2秒
+
+ return m_pModbusMaster->connectDevice();
+}
+
+bool ModbusController::modbus_tcp()
+{
+ return false;
+}
+
+bool ModbusController::Read()
+{
+ //对每一个从机进行操作
+ for( SlaveData* sd : m_slaves)
+ {
+ //定义读写单元
+ QModbusDataUnit readUnit(QModbusDataUnit::HoldingRegisters, sd->start_address, sd->quantity);
+ //发送请求
+ auto* reply = m_pModbusMaster->sendReadRequest(readUnit, sd->id);
+ if (reply)
+ {
+ connect(reply, &QModbusReply::finished, this, &ModbusController::readRegisters);
+ }
+ }
+
+ return true;
+}
+
+void ModbusController::readRegisters()
+{
+ //接收信号
+ auto reply = qobject_cast(sender());
+ if (!reply)
+ {
+ qDebug() << "无效回复";
+ return;
+ }
+ //处理响应
+ const QModbusDataUnit unit = reply->result();
+ // 将数据值转换为字符串
+ QString dataStr;
+ for (int i = 0; i < unit.valueCount(); ++i)
+ {
+ dataStr += QString::number(unit.value(i)) + " ";
+ }
+ if (dataStr.size() != 0)
+ {
+ qDebug() << "读取成功";
+ qDebug() << dataStr;
+ }
+ else
+ {
+ qDebug() << "无数据";
+ }
+}
\ No newline at end of file
diff --git a/applications/EmsShow/modbuscontroller.h b/applications/EmsShow/modbuscontroller.h
new file mode 100644
index 0000000..83bb901
--- /dev/null
+++ b/applications/EmsShow/modbuscontroller.h
@@ -0,0 +1,66 @@
+#ifndef MODBUSCONTROLLER_H
+#define MODBUSCONTROLLER_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+//#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+typedef struct _tagSlave
+{
+ int id;
+ int function_code;
+ int start_address;
+ int quantity;
+ int interval;
+} SlaveData;
+
+class ModbusController :public QObject
+{
+ Q_OBJECT
+public:
+ ModbusController();
+ ~ModbusController();
+
+public:
+ void getConfiguration(QString iniFilePath);
+ bool Open();
+ bool Read();
+
+
+protected:
+ bool modbus_rtu();
+ bool modbus_tcp();
+protected:
+ QString m_version;
+ int m_modbus_type;
+ QString m_modbus_ip;
+ int m_modbus_port;
+ QString m_modbus_com;
+ int m_modbus_baund;
+ int m_modbus_data;
+ int m_modbus_parity;
+ int m_modbus_stop;
+
+ QVector m_slaves;
+
+protected:
+ QModbusRtuSerialClient* m_pModbusMaster; //创建modbus对象
+ QSettings* m_pSettings;
+
+public slots:
+ void readRegisters(); //读寄存器函数
+};
+
+#endif // MODBUSCONTROLLER_H
diff --git a/applications/EmsShower/.gitignore b/applications/EmsShower/.gitignore
new file mode 100644
index 0000000..4a0b530
--- /dev/null
+++ b/applications/EmsShower/.gitignore
@@ -0,0 +1,74 @@
+# This file is used to ignore files which are generated
+# ----------------------------------------------------------------------------
+
+*~
+*.autosave
+*.a
+*.core
+*.moc
+*.o
+*.obj
+*.orig
+*.rej
+*.so
+*.so.*
+*_pch.h.cpp
+*_resource.rc
+*.qm
+.#*
+*.*#
+core
+!core/
+tags
+.DS_Store
+.directory
+*.debug
+Makefile*
+*.prl
+*.app
+moc_*.cpp
+ui_*.h
+qrc_*.cpp
+Thumbs.db
+*.res
+*.rc
+/.qmake.cache
+/.qmake.stash
+
+# qtcreator generated files
+*.pro.user*
+CMakeLists.txt.user*
+
+# xemacs temporary files
+*.flc
+
+# Vim temporary files
+.*.swp
+
+# Visual Studio generated files
+*.ib_pdb_index
+*.idb
+*.ilk
+*.pdb
+*.sln
+*.suo
+*.vcproj
+*vcproj.*.*.user
+*.ncb
+*.sdf
+*.opensdf
+*.vcxproj
+*vcxproj.*
+
+# MinGW generated files
+*.Debug
+*.Release
+
+# Python byte code
+*.pyc
+
+# Binaries
+# --------
+*.dll
+*.exe
+
diff --git a/applications/EmsShower/EmsShower.pro b/applications/EmsShower/EmsShower.pro
new file mode 100644
index 0000000..c79aacc
--- /dev/null
+++ b/applications/EmsShower/EmsShower.pro
@@ -0,0 +1,47 @@
+QT += core gui
+
+greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
+
+CONFIG += c++17
+
+# You can make your code fail to compile if it uses deprecated APIs.
+# In order to do so, uncomment the following line.
+#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
+
+SOURCES += \
+ libmodbus/modbus-data.c \
+ libmodbus/modbus-rtu.c \
+ libmodbus/modbus-tcp.c \
+ libmodbus/modbus.c \
+ main.cpp \
+ mainwindow.cpp
+
+HEADERS += \
+ libmodbus/config.h \
+ libmodbus/modbus-private.h \
+ libmodbus/modbus-rtu-private.h \
+ libmodbus/modbus-rtu.h \
+ libmodbus/modbus-tcp-private.h \
+ libmodbus/modbus-tcp.h \
+ libmodbus/modbus-version.h \
+ libmodbus/modbus.h \
+ mainwindow.h \
+ slave_define.h
+
+FORMS += \
+ mainwindow.ui
+
+LIBS += -lws2_32
+
+TRANSLATIONS += \
+ EmsShower_en_US.ts
+CONFIG += lrelease
+CONFIG += embed_translations
+
+# Default rules for deployment.
+qnx: target.path = /tmp/$${TARGET}/bin
+else: unix:!android: target.path = /opt/$${TARGET}/bin
+!isEmpty(target.path): INSTALLS += target
+
+DISTFILES += \
+ emsshower.ini
diff --git a/applications/EmsShower/EmsShower_en_US.ts b/applications/EmsShower/EmsShower_en_US.ts
new file mode 100644
index 0000000..edd0d34
--- /dev/null
+++ b/applications/EmsShower/EmsShower_en_US.ts
@@ -0,0 +1,3 @@
+
+
+
diff --git a/applications/EmsShower/emsshower.ini b/applications/EmsShower/emsshower.ini
new file mode 100644
index 0000000..ce76875
--- /dev/null
+++ b/applications/EmsShower/emsshower.ini
@@ -0,0 +1,67 @@
+;通用设置
+[version]
+ver="1_0_0"
+
+[modbus]
+;1=RTU 0=TCP
+type=1
+
+;if type=1, ignore ip and port setting
+ip="127.0.0.1"
+port=502
+
+com="COM20"
+baund=9600
+data=8
+
+;0=None 1=Odd 2=Even
+parity=0
+
+stop=1
+
+[slaves]
+;如果从机数量多于1,则从机地址slave_id以:分隔(英文冒号)
+slave_id="1"
+
+;每一个从机的配置
+;由两个节组成:slave_%d 和 slave_%d_function_%d
+
+[slave_1]
+;支持的功能码数量
+function_code_counts=2
+
+[slave_1_function_0]
+;功能码
+function_code=3
+
+;起始地址,10进制
+start_addr=40000
+
+;读取数量
+quantity=9
+
+;扫描间隔,单位ms
+interval=1000
+
+;serial_number与read_quantity的元素数量要一样
+equipment_code=81
+serial_number="0:1:2:3:4:5:6:7:8:9:10:11:12:13:14"
+read_quantity="1:1:1:1:1:1:1:1:1:1:1:1:1:1:1"
+
+[slave_1_function_1]
+;功能码
+function_code=3
+
+;起始地址,10进制
+start_addr=40009
+
+;读取数量
+quantity=6
+
+;扫描间隔,单位ms
+interval=1000
+
+;serial_number与read_quantity的元素数量要一样
+equipment_code=81
+serial_number="0:1:2:3:4:5:6:7:8:9:10:11:12:13:14"
+read_quantity="1:1:1:1:1:1:1:1:1:1:1:1:1:1:1"
\ No newline at end of file
diff --git a/applications/EmsShower/libmodbus/config.h b/applications/EmsShower/libmodbus/config.h
new file mode 100644
index 0000000..a5e4998
--- /dev/null
+++ b/applications/EmsShower/libmodbus/config.h
@@ -0,0 +1,164 @@
+/* config.h. Generated from config.h.in by configure. */
+/* config.h.in. Generated from configure.ac by autoheader. */
+
+/* Define to 1 if you have the header file. */
+/* #undef HAVE_ARPA_INET_H */
+
+/* Define to 1 if you have the declaration of `TIOCSRS485', and to 0 if you
+ don't. */
+/* #undef HAVE_DECL_TIOCSRS485 */
+
+/* Define to 1 if you have the declaration of `__CYGWIN__', and to 0 if you
+ don't. */
+/* #undef HAVE_DECL___CYGWIN__ */
+
+/* Define to 1 if you have the header file. */
+/* #undef HAVE_DLFCN_H */
+
+/* Define to 1 if you have the header file. */
+#define HAVE_ERRNO_H 1
+
+/* Define to 1 if you have the header file. */
+#define HAVE_FCNTL_H 1
+
+/* Define to 1 if you have the `fork' function. */
+/* #undef HAVE_FORK */
+
+/* Define to 1 if you have the `getaddrinfo' function. */
+/* #undef HAVE_GETADDRINFO */
+
+/* Define to 1 if you have the `gettimeofday' function. */
+/* #undef HAVE_GETTIMEOFDAY */
+
+/* Define to 1 if you have the header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the header file. */
+#define HAVE_LIMITS_H 1
+
+/* Define to 1 if you have the header file. */
+/* #undef HAVE_LINUX_SERIAL_H */
+
+/* Define to 1 if you have the header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the `memset' function. */
+#define HAVE_MEMSET 1
+
+/* Define to 1 if you have the header file. */
+/* #undef HAVE_NETDB_H */
+
+/* Define to 1 if you have the header file. */
+/* #undef HAVE_NETINET_IN_H */
+
+/* Define to 1 if you have the header file. */
+/* #undef HAVE_NETINET_TCP_H */
+
+/* Define to 1 if you have the `select' function. */
+/* #undef HAVE_SELECT */
+
+/* Define to 1 if you have the `socket' function. */
+/* #undef HAVE_SOCKET */
+
+/* Define to 1 if you have the header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the `strerror' function. */
+#define HAVE_STRERROR 1
+
+/* Define to 1 if you have the header file. */
+/* #undef HAVE_STRINGS_H */
+
+/* Define to 1 if you have the header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the `strlcpy' function. */
+/* #undef HAVE_STRLCPY */
+
+/* Define to 1 if you have the header file. */
+/* #undef HAVE_SYS_IOCTL_H */
+
+/* Define to 1 if you have the header file. */
+/* #undef HAVE_SYS_SOCKET_H */
+
+/* Define to 1 if you have the header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the header file. */
+/* #undef HAVE_SYS_TIME_H */
+
+/* Define to 1 if you have the header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the header file. */
+/* #undef HAVE_TERMIOS_H */
+
+/* Define to 1 if you have the header file. */
+#define HAVE_TIME_H 1
+
+/* Define to 1 if you have the header file. */
+/* #undef HAVE_UNISTD_H */
+
+/* Define to 1 if you have the `vfork' function. */
+/* #undef HAVE_VFORK */
+
+/* Define to 1 if you have the header file. */
+/* #undef HAVE_VFORK_H */
+
+/* Define to 1 if you have the header file. */
+#define HAVE_WINSOCK2_H 1
+
+/* Define to 1 if `fork' works. */
+/* #undef HAVE_WORKING_FORK */
+
+/* Define to 1 if `vfork' works. */
+/* #undef HAVE_WORKING_VFORK */
+
+/* Define to the sub-directory in which libtool stores uninstalled libraries.
+ */
+/* #undef LT_OBJDIR */
+
+/* Name of package */
+#define PACKAGE "libmodbus"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT "https://github.com/stephane/libmodbus/issues"
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "libmodbus"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "libmodbus 3.1.11"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "libmodbus"
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL ""
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "3.1.11"
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Define to 1 if you can safely include both and . */
+/* #undef TIME_WITH_SYS_TIME */
+
+/* Version number of package */
+#define VERSION "3.1.11"
+
+/* Define to empty if `const' does not conform to ANSI C. */
+/* #undef const */
+
+/* Define to `int' if does not define. */
+/* #undef pid_t */
+
+/* Define to `unsigned int' if does not define. */
+/* #undef size_t */
+
+/* Define as `fork' if `vfork' does not work. */
+#define vfork fork
diff --git a/applications/EmsShower/libmodbus/modbus-data.c b/applications/EmsShower/libmodbus/modbus-data.c
new file mode 100644
index 0000000..3a1872d
--- /dev/null
+++ b/applications/EmsShower/libmodbus/modbus-data.c
@@ -0,0 +1,241 @@
+/*
+ * Copyright © Stéphane Raimbault
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include
+
+// clang-format off
+#ifndef _MSC_VER
+# include
+#else
+# include "stdint.h"
+#endif
+
+#include
+#include
+
+#if defined(_WIN32)
+# include
+#else
+# include
+#endif
+
+#include "libmodbus/config.h"
+
+#include "modbus.h"
+
+// clang-format on
+
+/* Sets many bits from a single byte value (all 8 bits of the byte value are
+ set) */
+void modbus_set_bits_from_byte(uint8_t *dest, int idx, const uint8_t value)
+{
+ int i;
+
+ for (i = 0; i < 8; i++) {
+ dest[idx + i] = (value & (1 << i)) ? 1 : 0;
+ }
+}
+
+/* Sets many bits from a table of bytes (only the bits between idx and
+ idx + nb_bits are set) */
+void modbus_set_bits_from_bytes(uint8_t *dest,
+ int idx,
+ unsigned int nb_bits,
+ const uint8_t *tab_byte)
+{
+ unsigned int i;
+ int shift = 0;
+
+ for (i = idx; i < idx + nb_bits; i++) {
+ dest[i] = tab_byte[(i - idx) / 8] & (1 << shift) ? 1 : 0;
+ /* gcc doesn't like: shift = (++shift) % 8; */
+ shift++;
+ shift %= 8;
+ }
+}
+
+/* Gets the byte value from many bits.
+ To obtain a full byte, set nb_bits to 8. */
+uint8_t modbus_get_byte_from_bits(const uint8_t *src, int idx, unsigned int nb_bits)
+{
+ unsigned int i;
+ uint8_t value = 0;
+
+ if (nb_bits > 8) {
+ /* Assert is ignored if NDEBUG is set */
+ assert(nb_bits < 8);
+ nb_bits = 8;
+ }
+
+ for (i = 0; i < nb_bits; i++) {
+ value |= (src[idx + i] << i);
+ }
+
+ return value;
+}
+
+/* Get a float from 4 bytes (Modbus) without any conversion (ABCD) */
+float modbus_get_float_abcd(const uint16_t *src)
+{
+ float f;
+ uint32_t i;
+ uint8_t a, b, c, d;
+
+ // Mind: src contains 16-bit numbers in processor-endianness, hence
+ // we use shift operations and do not access memory directly
+ a = (src[0] >> 8) & 0xFF; // high byte of first word
+ b = (src[0] >> 0) & 0xFF; // low byte of first word
+ c = (src[1] >> 8) & 0xFF; // high byte of second word
+ d = (src[1] >> 0) & 0xFF; // low byte of second word
+
+ // we assemble 32bit integer always in abcd order via shift operations
+ i = (a << 24) | (b << 16) | (c << 8) | (d << 0);
+ memcpy(&f, &i, 4);
+
+ return f;
+}
+
+/* Get a float from 4 bytes (Modbus) in inversed format (DCBA) */
+float modbus_get_float_dcba(const uint16_t *src)
+{
+ float f;
+ uint32_t i;
+ uint8_t a, b, c, d;
+
+ // byte order is defined when reading from src: dcba
+ d = (src[0] >> 8) & 0xFF;
+ c = (src[0] >> 0) & 0xFF;
+ b = (src[1] >> 8) & 0xFF;
+ a = (src[1] >> 0) & 0xFF;
+
+ // we assemble 32bit integer always in abcd order via shift operations
+ i = (a << 24) | (b << 16) | (c << 8) | (d << 0);
+ memcpy(&f, &i, 4);
+
+ return f;
+}
+
+/* Get a float from 4 bytes (Modbus) with swapped bytes (BADC) */
+float modbus_get_float_badc(const uint16_t *src)
+{
+ float f;
+ uint32_t i;
+ uint8_t a, b, c, d;
+
+ // byte order is defined when reading from src: badc
+ b = (src[0] >> 8) & 0xFF;
+ a = (src[0] >> 0) & 0xFF;
+ d = (src[1] >> 8) & 0xFF;
+ c = (src[1] >> 0) & 0xFF;
+
+ // we assemble 32bit integer always in abcd order via shift operations
+ i = (a << 24) | (b << 16) | (c << 8) | (d << 0);
+ memcpy(&f, &i, 4);
+
+ return f;
+}
+
+/* Get a float from 4 bytes (Modbus) with swapped words (CDAB) */
+float modbus_get_float_cdab(const uint16_t *src)
+{
+ float f;
+ uint32_t i;
+ uint8_t a, b, c, d;
+
+ // byte order is defined when reading from src: cdab
+ c = (src[0] >> 8) & 0xFF;
+ d = (src[0] >> 0) & 0xFF;
+ a = (src[1] >> 8) & 0xFF;
+ b = (src[1] >> 0) & 0xFF;
+
+ // we assemble 32bit integer always in abcd order via shift operations
+ i = (a << 24) | (b << 16) | (c << 8) | (d << 0);
+ memcpy(&f, &i, 4);
+
+ return f;
+}
+
+/* DEPRECATED - Get a float from 4 bytes in sort of Modbus format */
+float modbus_get_float(const uint16_t *src)
+{
+ return modbus_get_float_cdab(src);
+}
+
+/* Set a float to 4 bytes for Modbus w/o any conversion (ABCD) */
+void modbus_set_float_abcd(float f, uint16_t *dest)
+{
+ // The straight-forward type conversion won't work because of type-punned pointer aliasing warning
+ // uint32_t i = *(uint32_t*)(&f);
+ float * fptr = &f;
+ uint32_t * iptr = (uint32_t *)fptr;
+ uint32_t i = *iptr;
+ uint8_t a, b, c, d;
+
+ a = (i >> 24) & 0xFF;
+ b = (i >> 16) & 0xFF;
+ c = (i >> 8) & 0xFF;
+ d = (i >> 0) & 0xFF;
+
+ dest[0] = (a << 8) | b;
+ dest[1] = (c << 8) | d;
+}
+
+/* Set a float to 4 bytes for Modbus with byte and word swap conversion (DCBA) */
+void modbus_set_float_dcba(float f, uint16_t *dest)
+{
+ float * fptr = &f;
+ uint32_t * iptr = (uint32_t *)fptr;
+ uint32_t i = *iptr;
+ uint8_t a, b, c, d;
+
+ a = (i >> 24) & 0xFF;
+ b = (i >> 16) & 0xFF;
+ c = (i >> 8) & 0xFF;
+ d = (i >> 0) & 0xFF;
+
+ dest[0] = (d << 8) | c;
+ dest[1] = (b << 8) | a;
+}
+
+/* Set a float to 4 bytes for Modbus with byte swap conversion (BADC) */
+void modbus_set_float_badc(float f, uint16_t *dest)
+{
+ float * fptr = &f;
+ uint32_t * iptr = (uint32_t *)fptr;
+ uint32_t i = *iptr;
+ uint8_t a, b, c, d;
+
+ a = (i >> 24) & 0xFF;
+ b = (i >> 16) & 0xFF;
+ c = (i >> 8) & 0xFF;
+ d = (i >> 0) & 0xFF;
+
+ dest[0] = (b << 8) | a;
+ dest[1] = (d << 8) | c;
+}
+
+/* Set a float to 4 bytes for Modbus with word swap conversion (CDAB) */
+void modbus_set_float_cdab(float f, uint16_t *dest)
+{
+ float * fptr = &f;
+ uint32_t * iptr = (uint32_t *)fptr;
+ uint32_t i = *iptr;
+ uint8_t a, b, c, d;
+
+ a = (i >> 24) & 0xFF;
+ b = (i >> 16) & 0xFF;
+ c = (i >> 8) & 0xFF;
+ d = (i >> 0) & 0xFF;
+
+ dest[0] = (c << 8) | d;
+ dest[1] = (a << 8) | b;
+}
+
+/* DEPRECATED - Set a float to 4 bytes in a sort of Modbus format! */
+void modbus_set_float(float f, uint16_t *dest)
+{
+ modbus_set_float_cdab(f, dest);
+}
diff --git a/applications/EmsShower/libmodbus/modbus-private.h b/applications/EmsShower/libmodbus/modbus-private.h
new file mode 100644
index 0000000..1b48269
--- /dev/null
+++ b/applications/EmsShower/libmodbus/modbus-private.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright © Stéphane Raimbault
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef MODBUS_PRIVATE_H
+#define MODBUS_PRIVATE_H
+
+// clang-format off
+#ifndef _MSC_VER
+# include
+# include
+#else
+# include "stdint.h"
+# include
+typedef int ssize_t;
+#endif
+// clang-format on
+#include "libmodbus/config.h"
+#include
+
+#include "modbus.h"
+
+MODBUS_BEGIN_DECLS
+
+/* It's not really the minimal length (the real one is report slave ID
+ * in RTU (4 bytes)) but it's a convenient size to use in RTU or TCP
+ * communications to read many values or write a single one.
+ * Maximum between :
+ * - HEADER_LENGTH_TCP (7) + function (1) + address (2) + number (2)
+ * - HEADER_LENGTH_RTU (1) + function (1) + address (2) + number (2) + CRC (2)
+ */
+#define _MIN_REQ_LENGTH 12
+
+#define _REPORT_SLAVE_ID 180
+
+#define _MODBUS_EXCEPTION_RSP_LENGTH 5
+
+/* Timeouts in microsecond (0.5 s) */
+#define _RESPONSE_TIMEOUT 500000
+#define _BYTE_TIMEOUT 500000
+
+typedef enum
+{
+ _MODBUS_BACKEND_TYPE_RTU = 0,
+ _MODBUS_BACKEND_TYPE_TCP
+} modbus_backend_type_t;
+
+/*
+ * ---------- Request Indication ----------
+ * | Client | ---------------------->| Server |
+ * ---------- Confirmation Response ----------
+ */
+typedef enum
+{
+ /* Request message on the server side */
+ MSG_INDICATION,
+ /* Request message on the client side */
+ MSG_CONFIRMATION
+} msg_type_t;
+
+/* This structure reduces the number of params in functions and so
+ * optimizes the speed of execution (~ 37%). */
+typedef struct _sft
+{
+ int slave;
+ int function;
+ int t_id;
+} sft_t;
+
+typedef struct _modbus_backend
+{
+ unsigned int backend_type;
+ unsigned int header_length;
+ unsigned int checksum_length;
+ unsigned int max_adu_length;
+ int (*set_slave)(modbus_t *ctx, int slave);
+ int (*build_request_basis)(
+ modbus_t *ctx, int function, int addr, int nb, uint8_t *req);
+ int (*build_response_basis)(sft_t *sft, uint8_t *rsp);
+ int (*get_response_tid)(const uint8_t *req);
+ int (*send_msg_pre)(uint8_t *req, int req_length);
+ ssize_t (*send)(modbus_t *ctx, const uint8_t *req, int req_length);
+ int (*receive)(modbus_t *ctx, uint8_t *req);
+ ssize_t (*recv)(modbus_t *ctx, uint8_t *rsp, int rsp_length);
+ int (*check_integrity)(modbus_t *ctx, uint8_t *msg, const int msg_length);
+ int (*pre_check_confirmation)(modbus_t *ctx,
+ const uint8_t *req,
+ const uint8_t *rsp,
+ int rsp_length);
+ int (*connect)(modbus_t *ctx);
+ unsigned int (*is_connected)(modbus_t *ctx);
+ void (*close)(modbus_t *ctx);
+ int (*flush)(modbus_t *ctx);
+ int (*select)(modbus_t *ctx, fd_set *rset, struct timeval *tv, int msg_length);
+ void (*free)(modbus_t *ctx);
+} modbus_backend_t;
+
+struct _modbus
+{
+ /* Slave address */
+ int slave;
+ /* Socket or file descriptor */
+ int s;
+ int debug;
+ int error_recovery;
+ int quirks;
+ struct timeval response_timeout;
+ struct timeval byte_timeout;
+ struct timeval indication_timeout;
+ const modbus_backend_t *backend;
+ void *backend_data;
+};
+
+void _modbus_init_common(modbus_t *ctx);
+void _error_print(modbus_t *ctx, const char *context);
+int _modbus_receive_msg(modbus_t *ctx, uint8_t *msg, msg_type_t msg_type);
+
+#ifndef HAVE_STRLCPY
+size_t strlcpy(char *dest, const char *src, size_t dest_size);
+#endif
+
+MODBUS_END_DECLS
+
+#endif /* MODBUS_PRIVATE_H */
diff --git a/applications/EmsShower/libmodbus/modbus-rtu-private.h b/applications/EmsShower/libmodbus/modbus-rtu-private.h
new file mode 100644
index 0000000..01e6a91
--- /dev/null
+++ b/applications/EmsShower/libmodbus/modbus-rtu-private.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright © Stéphane Raimbault
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef MODBUS_RTU_PRIVATE_H
+#define MODBUS_RTU_PRIVATE_H
+
+#ifndef _MSC_VER
+#include
+#else
+#include "stdint.h"
+#endif
+
+#if defined(_WIN32)
+#include
+#else
+#include
+#endif
+
+#define _MODBUS_RTU_HEADER_LENGTH 1
+#define _MODBUS_RTU_PRESET_REQ_LENGTH 6
+#define _MODBUS_RTU_PRESET_RSP_LENGTH 2
+
+#define _MODBUS_RTU_CHECKSUM_LENGTH 2
+
+#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_rtu {
+ /* 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
+ /* To handle many slaves on the same link */
+ int confirmation_to_ignore;
+} modbus_rtu_t;
+
+#endif /* MODBUS_RTU_PRIVATE_H */
diff --git a/applications/EmsShower/libmodbus/modbus-rtu.c b/applications/EmsShower/libmodbus/modbus-rtu.c
new file mode 100644
index 0000000..ebef934
--- /dev/null
+++ b/applications/EmsShower/libmodbus/modbus-rtu.c
@@ -0,0 +1,1288 @@
+/*
+ * Copyright © Stéphane Raimbault
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include
+#include
+#include
+#include
+#include
+#ifndef _MSC_VER
+#include
+#endif
+#include "modbus-private.h"
+#include
+
+#include "modbus-rtu-private.h"
+#include "modbus-rtu.h"
+
+#if HAVE_DECL_TIOCSRS485 || HAVE_DECL_TIOCM_RTS
+#include
+#endif
+
+#if HAVE_DECL_TIOCSRS485
+#include
+#endif
+
+/* Table of CRC values for high-order byte */
+static const uint8_t table_crc_hi[] = {
+ 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
+ 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
+ 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1,
+ 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
+ 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
+ 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
+ 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1,
+ 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
+ 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
+ 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
+ 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
+ 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
+ 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
+ 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
+ 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
+ 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
+ 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
+ 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
+ 0x00, 0xC1, 0x81, 0x40};
+
+/* Table of CRC values for low-order byte */
+static const uint8_t table_crc_lo[] = {
+ 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5,
+ 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B,
+ 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE,
+ 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6,
+ 0xD2, 0x12, 0x13, 0xD3, 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,
+ 0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D,
+ 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8,
+ 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C,
+ 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21,
+ 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,
+ 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A,
+ 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA,
+ 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, 0x77, 0xB7,
+ 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91, 0x51,
+ 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
+ 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98,
+ 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D,
+ 0x4C, 0x8C, 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83,
+ 0x41, 0x81, 0x80, 0x40};
+
+/* 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)
+{
+ int max_slave = (ctx->quirks & MODBUS_QUIRK_MAX_SLAVE) ? 255 : 247;
+
+ /* Broadcast address is 0 (MODBUS_BROADCAST_ADDRESS) */
+ if (slave >= 0 && slave <= max_slave) {
+ ctx->slave = slave;
+ } else {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Builds a RTU request header */
+static int _modbus_rtu_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_RTU_PRESET_REQ_LENGTH;
+}
+
+/* Builds a RTU response header */
+static int _modbus_rtu_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_rtu_listen */
+ rsp[0] = sft->slave;
+ rsp[1] = sft->function;
+
+ return _MODBUS_RTU_PRESET_RSP_LENGTH;
+}
+
+static uint16_t crc16(uint8_t *buffer, uint16_t buffer_length)
+{
+ uint8_t crc_hi = 0xFF; /* high CRC byte initialized */
+ uint8_t crc_lo = 0xFF; /* low CRC byte initialized */
+ unsigned int i; /* will index into CRC lookup */
+
+ /* pass through message buffer */
+ while (buffer_length--) {
+ i = crc_lo ^ *buffer++; /* calculate the CRC */
+ crc_lo = crc_hi ^ table_crc_hi[i];
+ crc_hi = table_crc_lo[i];
+ }
+
+ return (crc_hi << 8 | crc_lo);
+}
+
+static int _modbus_rtu_get_response_tid(const uint8_t *req)
+{
+ /* No TID */
+ return 0;
+}
+
+static int _modbus_rtu_send_msg_pre(uint8_t *req, int req_length)
+{
+ uint16_t crc = crc16(req, req_length);
+
+ /* According to the MODBUS specs (p. 14), the low order byte of the CRC comes
+ * first in the RTU message */
+ req[req_length++] = crc & 0x00FF;
+ req[req_length++] = crc >> 8;
+
+ 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_rtu_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_rtu_send(modbus_t *ctx, const uint8_t *req, int req_length)
+{
+#if defined(_WIN32)
+ modbus_rtu_t *ctx_rtu = ctx->backend_data;
+ DWORD n_bytes = 0;
+ return (WriteFile(ctx_rtu->w_ser.fd, req, req_length, &n_bytes, NULL))
+ ? (ssize_t) n_bytes
+ : -1;
+#else
+#if HAVE_DECL_TIOCM_RTS
+ modbus_rtu_t *ctx_rtu = ctx->backend_data;
+ if (ctx_rtu->rts != MODBUS_RTU_RTS_NONE) {
+ ssize_t size;
+
+ if (ctx->debug) {
+ fprintf(stderr, "Sending request using RTS signal\n");
+ }
+
+ ctx_rtu->set_rts(ctx, ctx_rtu->rts == MODBUS_RTU_RTS_UP);
+ usleep(ctx_rtu->rts_delay);
+
+ size = write(ctx->s, req, req_length);
+
+ usleep(ctx_rtu->onebyte_time * req_length + ctx_rtu->rts_delay);
+ ctx_rtu->set_rts(ctx, ctx_rtu->rts != MODBUS_RTU_RTS_UP);
+
+ return size;
+ } else {
+#endif
+ return write(ctx->s, req, req_length);
+#if HAVE_DECL_TIOCM_RTS
+ }
+#endif
+#endif
+}
+
+static int _modbus_rtu_receive(modbus_t *ctx, uint8_t *req)
+{
+ int rc;
+ modbus_rtu_t *ctx_rtu = ctx->backend_data;
+
+ if (ctx_rtu->confirmation_to_ignore) {
+ _modbus_receive_msg(ctx, req, MSG_CONFIRMATION);
+ /* Ignore errors and reset the flag */
+ ctx_rtu->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_rtu->confirmation_to_ignore = TRUE;
+ }
+ }
+ return rc;
+}
+
+static ssize_t _modbus_rtu_recv(modbus_t *ctx, uint8_t *rsp, int rsp_length)
+{
+#if defined(_WIN32)
+ return win32_ser_read(&((modbus_rtu_t *) ctx->backend_data)->w_ser, rsp, rsp_length);
+#else
+ return read(ctx->s, rsp, rsp_length);
+#endif
+}
+
+static int _modbus_rtu_flush(modbus_t *);
+
+static int _modbus_rtu_pre_check_confirmation(modbus_t *ctx,
+ const uint8_t *req,
+ const uint8_t *rsp,
+ int rsp_length)
+{
+ /* 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 if the message is ignored and the
+ message length if the CRC is valid. Otherwise it shall return -1 and set
+ errno to EMBBADCRC. */
+static int _modbus_rtu_check_integrity(modbus_t *ctx, uint8_t *msg, const int msg_length)
+{
+ uint16_t crc_calculated;
+ uint16_t crc_received;
+ int slave = msg[0];
+
+ crc_calculated = crc16(msg, msg_length - 2);
+ crc_received = (msg[msg_length - 1] << 8) | msg[msg_length - 2];
+
+ /* Check CRC of msg */
+ if (crc_calculated != crc_received) {
+ 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_rtu_flush(ctx);
+ }
+ errno = EMBBADCRC;
+ return -1;
+ }
+
+ /* Filter on the Modbus unit identifier (slave) in RTU mode */
+ 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;
+ }
+
+ return msg_length;
+}
+
+/* Sets up a serial port for RTU communications */
+#if defined(_WIN32)
+static int _modbus_rtu_connect(modbus_t *ctx)
+{
+ DCB dcb;
+ modbus_rtu_t *ctx_rtu = ctx->backend_data;
+
+ if (ctx->debug) {
+ printf("Opening %s at %d bauds (%c, %d, %d)\n",
+ ctx_rtu->device,
+ ctx_rtu->baud,
+ ctx_rtu->parity,
+ ctx_rtu->data_bit,
+ ctx_rtu->stop_bit);
+ }
+
+ /* Some references here:
+ * http://msdn.microsoft.com/en-us/library/aa450602.aspx
+ */
+ win32_ser_init(&ctx_rtu->w_ser);
+
+ /* ctx_rtu->device should contain a string like "COMxx:" xx being a decimal
+ * number */
+ ctx_rtu->w_ser.fd = CreateFileA(
+ ctx_rtu->device, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
+
+ /* Error checking */
+ if (ctx_rtu->w_ser.fd == INVALID_HANDLE_VALUE) {
+ if (ctx->debug) {
+ fprintf(stderr,
+ "ERROR Can't open the device %s (LastError %d)\n",
+ ctx_rtu->device,
+ (int) GetLastError());
+ }
+ return -1;
+ }
+
+ /* Save params */
+ ctx_rtu->old_dcb.DCBlength = sizeof(DCB);
+ if (!GetCommState(ctx_rtu->w_ser.fd, &ctx_rtu->old_dcb)) {
+ if (ctx->debug) {
+ fprintf(stderr,
+ "ERROR Error getting configuration (LastError %d)\n",
+ (int) GetLastError());
+ }
+ CloseHandle(ctx_rtu->w_ser.fd);
+ ctx_rtu->w_ser.fd = INVALID_HANDLE_VALUE;
+ return -1;
+ }
+
+ /* Build new configuration (starting from current settings) */
+ dcb = ctx_rtu->old_dcb;
+
+ /* Speed setting */
+ dcb.BaudRate = ctx_rtu->baud;
+
+ /* Data bits */
+ switch (ctx_rtu->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_rtu->stop_bit == 1)
+ dcb.StopBits = ONESTOPBIT;
+ else /* 2 */
+ dcb.StopBits = TWOSTOPBITS;
+
+ /* Parity */
+ if (ctx_rtu->parity == 'N') {
+ dcb.Parity = NOPARITY;
+ dcb.fParity = FALSE;
+ } else if (ctx_rtu->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_rtu->w_ser.fd, &dcb)) {
+ if (ctx->debug) {
+ fprintf(stderr,
+ "ERROR Error setting new configuration (LastError %d)\n",
+ (int) GetLastError());
+ }
+ CloseHandle(ctx_rtu->w_ser.fd);
+ ctx_rtu->w_ser.fd = INVALID_HANDLE_VALUE;
+ return -1;
+ }
+
+ return 0;
+}
+#else
+
+static speed_t _get_termios_speed(int baud, int debug)
+{
+ speed_t speed;
+
+ switch (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 (debug) {
+ fprintf(stderr, "WARNING Unknown baud rate %d (B9600 used)\n", baud);
+ }
+ }
+
+ return speed;
+}
+
+/* POSIX */
+static int _modbus_rtu_connect(modbus_t *ctx)
+{
+ struct termios tios;
+ int flags;
+ speed_t speed;
+ modbus_rtu_t *ctx_rtu = ctx->backend_data;
+
+ if (ctx->debug) {
+ printf("Opening %s at %d bauds (%c, %d, %d)\n",
+ ctx_rtu->device,
+ ctx_rtu->baud,
+ ctx_rtu->parity,
+ ctx_rtu->data_bit,
+ ctx_rtu->stop_bit);
+ }
+
+ /* 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
+ NONBLOCK option is set on the file via open or fcntl */
+ flags = O_RDWR | O_NOCTTY | O_NONBLOCK | O_EXCL;
+#ifdef O_CLOEXEC
+ flags |= O_CLOEXEC;
+#endif
+
+ ctx->s = open(ctx_rtu->device, flags);
+ if (ctx->s < 0) {
+ if (ctx->debug) {
+ fprintf(stderr,
+ "ERROR Can't open the device %s (%s)\n",
+ ctx_rtu->device,
+ strerror(errno));
+ }
+ return -1;
+ }
+
+ /* Save */
+ tcgetattr(ctx->s, &ctx_rtu->old_tios);
+
+ memset(&tios, 0, sizeof(struct termios));
+
+ /* C_ISPEED Input baud (new interface)
+ C_OSPEED Output baud (new interface)
+ */
+
+ /* Set the baud rate */
+
+ /*
+ On MacOS, constants of baud rates are equal to the integer in argument but
+ that's not the case under Linux so we have to find the corresponding
+ constant. Until the code is upgraded to termios2, the list of possible
+ values is limited (no 14400 for example).
+ */
+ if (9600 == B9600) {
+ speed = ctx_rtu->baud;
+ } else {
+ speed = _get_termios_speed(ctx_rtu->baud, ctx->debug);
+ }
+
+ 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_rtu->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_rtu->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_rtu->parity == 'N') {
+ /* None */
+ tios.c_cflag &= ~PARENB;
+ } else if (ctx_rtu->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_rtu->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 output */
+ 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 NONBLOCK
+ 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
+ NONBLOCK option is set on the port with open or fcntl.
+ */
+ /* Unused because we use open with the NONBLOCK 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;
+ }
+
+ return 0;
+}
+#endif
+
+// FIXME Temporary solution before rewriting Windows RTU backend
+static unsigned int _modbus_rtu_is_connected(modbus_t *ctx)
+{
+#if defined(_WIN32)
+ modbus_rtu_t *ctx_rtu = ctx->backend_data;
+
+ /* Check if file handle is valid */
+ return ctx_rtu->w_ser.fd != INVALID_HANDLE_VALUE;
+#else
+ return ctx->s >= 0;
+#endif
+}
+
+int modbus_rtu_set_serial_mode(modbus_t *ctx, int mode)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) {
+#if HAVE_DECL_TIOCSRS485
+ modbus_rtu_t *ctx_rtu = ctx->backend_data;
+ struct serial_rs485 rs485conf;
+
+ if (mode == MODBUS_RTU_RS485) {
+ // Get
+ if (ioctl(ctx->s, TIOCGRS485, &rs485conf) < 0) {
+ return -1;
+ }
+ // Set
+ rs485conf.flags |= SER_RS485_ENABLED;
+ if (ioctl(ctx->s, TIOCSRS485, &rs485conf) < 0) {
+ return -1;
+ }
+
+ ctx_rtu->serial_mode = MODBUS_RTU_RS485;
+ return 0;
+ } else if (mode == MODBUS_RTU_RS232) {
+ /* Turn off RS485 mode only if required */
+ if (ctx_rtu->serial_mode == MODBUS_RTU_RS485) {
+ /* The ioctl call is avoided because it can fail on some RS232 ports */
+ if (ioctl(ctx->s, TIOCGRS485, &rs485conf) < 0) {
+ return -1;
+ }
+ rs485conf.flags &= ~SER_RS485_ENABLED;
+ if (ioctl(ctx->s, TIOCSRS485, &rs485conf) < 0) {
+ return -1;
+ }
+ }
+ ctx_rtu->serial_mode = MODBUS_RTU_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_rtu_get_serial_mode(modbus_t *ctx)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) {
+#if HAVE_DECL_TIOCSRS485
+ modbus_rtu_t *ctx_rtu = ctx->backend_data;
+ return ctx_rtu->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_rtu_get_rts(modbus_t *ctx)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) {
+#if HAVE_DECL_TIOCM_RTS
+ modbus_rtu_t *ctx_rtu = ctx->backend_data;
+ return ctx_rtu->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_rtu_set_rts(modbus_t *ctx, int mode)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) {
+#if HAVE_DECL_TIOCM_RTS
+ modbus_rtu_t *ctx_rtu = ctx->backend_data;
+
+ if (mode == MODBUS_RTU_RTS_NONE || mode == MODBUS_RTU_RTS_UP ||
+ mode == MODBUS_RTU_RTS_DOWN) {
+ ctx_rtu->rts = mode;
+
+ /* Set the RTS bit in order to not reserve the RS485 bus */
+ ctx_rtu->set_rts(ctx, ctx_rtu->rts != MODBUS_RTU_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_rtu_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_RTU) {
+#if HAVE_DECL_TIOCM_RTS
+ modbus_rtu_t *ctx_rtu = ctx->backend_data;
+ ctx_rtu->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_rtu_get_rts_delay(modbus_t *ctx)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) {
+#if HAVE_DECL_TIOCM_RTS
+ modbus_rtu_t *ctx_rtu;
+ ctx_rtu = (modbus_rtu_t *) ctx->backend_data;
+ return ctx_rtu->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_rtu_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_RTU) {
+#if HAVE_DECL_TIOCM_RTS
+ modbus_rtu_t *ctx_rtu;
+ ctx_rtu = (modbus_rtu_t *) ctx->backend_data;
+ ctx_rtu->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;
+ }
+}
+
+static void _modbus_rtu_close(modbus_t *ctx)
+{
+ /* Restore line settings and close file descriptor in RTU mode */
+ modbus_rtu_t *ctx_rtu = ctx->backend_data;
+
+#if defined(_WIN32)
+ /* Revert settings */
+ if (!SetCommState(ctx_rtu->w_ser.fd, &ctx_rtu->old_dcb) && ctx->debug) {
+ fprintf(stderr,
+ "ERROR Couldn't revert to configuration (LastError %d)\n",
+ (int) GetLastError());
+ }
+
+ if (!CloseHandle(ctx_rtu->w_ser.fd) && ctx->debug) {
+ fprintf(stderr,
+ "ERROR Error while closing handle (LastError %d)\n",
+ (int) GetLastError());
+ }
+#else
+ if (ctx->s >= 0) {
+ tcsetattr(ctx->s, TCSANOW, &ctx_rtu->old_tios);
+ close(ctx->s);
+ ctx->s = -1;
+ }
+#endif
+}
+
+static int _modbus_rtu_flush(modbus_t *ctx)
+{
+#if defined(_WIN32)
+ modbus_rtu_t *ctx_rtu = ctx->backend_data;
+ ctx_rtu->w_ser.n_bytes = 0;
+ return (PurgeComm(ctx_rtu->w_ser.fd, PURGE_RXCLEAR) == FALSE);
+#else
+ return tcflush(ctx->s, TCIOFLUSH);
+#endif
+}
+
+static int
+_modbus_rtu_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_rtu_t *) ctx->backend_data)->w_ser, length_to_read, tv);
+ if (s_rc == 0) {
+ errno = ETIMEDOUT;
+ return -1;
+ }
+
+ if (s_rc < 0) {
+ 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_rtu_free(modbus_t *ctx)
+{
+ if (ctx->backend_data) {
+ free(((modbus_rtu_t *) ctx->backend_data)->device);
+ free(ctx->backend_data);
+ }
+
+ free(ctx);
+}
+
+// clang-format off
+const modbus_backend_t _modbus_rtu_backend = {
+ _MODBUS_BACKEND_TYPE_RTU,
+ _MODBUS_RTU_HEADER_LENGTH,
+ _MODBUS_RTU_CHECKSUM_LENGTH,
+ MODBUS_RTU_MAX_ADU_LENGTH,
+ _modbus_set_slave,
+ _modbus_rtu_build_request_basis,
+ _modbus_rtu_build_response_basis,
+ _modbus_rtu_get_response_tid,
+ _modbus_rtu_send_msg_pre,
+ _modbus_rtu_send,
+ _modbus_rtu_receive,
+ _modbus_rtu_recv,
+ _modbus_rtu_check_integrity,
+ _modbus_rtu_pre_check_confirmation,
+ _modbus_rtu_connect,
+ _modbus_rtu_is_connected,
+ _modbus_rtu_close,
+ _modbus_rtu_flush,
+ _modbus_rtu_select,
+ _modbus_rtu_free
+};
+
+// clang-format on
+
+modbus_t *
+modbus_new_rtu(const char *device, int baud, char parity, int data_bit, int stop_bit)
+{
+ modbus_t *ctx;
+ modbus_rtu_t *ctx_rtu;
+
+ /* 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;
+ }
+
+ ctx = (modbus_t *) malloc(sizeof(modbus_t));
+ if (ctx == NULL) {
+ return NULL;
+ }
+
+ _modbus_init_common(ctx);
+ ctx->backend = &_modbus_rtu_backend;
+ ctx->backend_data = (modbus_rtu_t *) malloc(sizeof(modbus_rtu_t));
+ if (ctx->backend_data == NULL) {
+ modbus_free(ctx);
+ errno = ENOMEM;
+ return NULL;
+ }
+ ctx_rtu = (modbus_rtu_t *) ctx->backend_data;
+
+ /* Device name and \0 */
+ ctx_rtu->device = (char *) malloc((strlen(device) + 1) * sizeof(char));
+ if (ctx_rtu->device == NULL) {
+ modbus_free(ctx);
+ errno = ENOMEM;
+ return NULL;
+ }
+
+#if defined(_WIN32)
+ strcpy_s(ctx_rtu->device, strlen(device) + 1, device);
+#else
+ strcpy(ctx_rtu->device, device);
+#endif
+
+ ctx_rtu->baud = baud;
+ if (parity == 'N' || parity == 'E' || parity == 'O') {
+ ctx_rtu->parity = parity;
+ } else {
+ modbus_free(ctx);
+ errno = EINVAL;
+ return NULL;
+ }
+ ctx_rtu->data_bit = data_bit;
+ ctx_rtu->stop_bit = stop_bit;
+
+#if HAVE_DECL_TIOCSRS485
+ /* The RS232 mode has been set by default */
+ ctx_rtu->serial_mode = MODBUS_RTU_RS232;
+#endif
+
+#if HAVE_DECL_TIOCM_RTS
+ /* The RTS use has been set by default */
+ ctx_rtu->rts = MODBUS_RTU_RTS_NONE;
+
+ /* Calculate estimated time in micro second to send one byte */
+ ctx_rtu->onebyte_time =
+ 1000000 * (1 + data_bit + (parity == 'N' ? 0 : 1) + stop_bit) / baud;
+
+ /* The internal function is used by default to set RTS */
+ ctx_rtu->set_rts = _modbus_rtu_ioctl_rts;
+
+ /* The delay before and after transmission when toggling the RTS pin */
+ ctx_rtu->rts_delay = ctx_rtu->onebyte_time;
+#endif
+
+ ctx_rtu->confirmation_to_ignore = FALSE;
+
+ return ctx;
+}
diff --git a/applications/EmsShower/libmodbus/modbus-rtu.h b/applications/EmsShower/libmodbus/modbus-rtu.h
new file mode 100644
index 0000000..8e89e73
--- /dev/null
+++ b/applications/EmsShower/libmodbus/modbus-rtu.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright © Stéphane Raimbault
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef MODBUS_RTU_H
+#define MODBUS_RTU_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_RTU_MAX_ADU_LENGTH 256
+
+MODBUS_API modbus_t *
+modbus_new_rtu(const char *device, int baud, char parity, int data_bit, int stop_bit);
+
+#define MODBUS_RTU_RS232 0
+#define MODBUS_RTU_RS485 1
+
+MODBUS_API int modbus_rtu_set_serial_mode(modbus_t *ctx, int mode);
+MODBUS_API int modbus_rtu_get_serial_mode(modbus_t *ctx);
+
+#define MODBUS_RTU_RTS_NONE 0
+#define MODBUS_RTU_RTS_UP 1
+#define MODBUS_RTU_RTS_DOWN 2
+
+MODBUS_API int modbus_rtu_set_rts(modbus_t *ctx, int mode);
+MODBUS_API int modbus_rtu_get_rts(modbus_t *ctx);
+
+MODBUS_API int modbus_rtu_set_custom_rts(modbus_t *ctx,
+ void (*set_rts)(modbus_t *ctx, int on));
+
+MODBUS_API int modbus_rtu_set_rts_delay(modbus_t *ctx, int us);
+MODBUS_API int modbus_rtu_get_rts_delay(modbus_t *ctx);
+
+MODBUS_END_DECLS
+
+#endif /* MODBUS_RTU_H */
diff --git a/applications/EmsShower/libmodbus/modbus-tcp-private.h b/applications/EmsShower/libmodbus/modbus-tcp-private.h
new file mode 100644
index 0000000..faffc21
--- /dev/null
+++ b/applications/EmsShower/libmodbus/modbus-tcp-private.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright © Stéphane Raimbault
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef MODBUS_TCP_PRIVATE_H
+#define MODBUS_TCP_PRIVATE_H
+
+#define _MODBUS_TCP_HEADER_LENGTH 7
+#define _MODBUS_TCP_PRESET_REQ_LENGTH 12
+#define _MODBUS_TCP_PRESET_RSP_LENGTH 8
+
+#define _MODBUS_TCP_CHECKSUM_LENGTH 0
+
+/* In both structures, the transaction ID must be placed on first position
+ to have a quick access not dependent of the TCP backend */
+typedef struct _modbus_tcp {
+ /* Extract from MODBUS Messaging on TCP/IP Implementation Guide V1.0b
+ (page 23/46):
+ The transaction identifier is used to associate the future response
+ with the request. This identifier is unique on each TCP connection. */
+ uint16_t t_id;
+ /* TCP port */
+ int port;
+ /* IP address */
+ char ip[16];
+} modbus_tcp_t;
+
+typedef struct _modbus_tcp_pi {
+ /* Transaction ID */
+ uint16_t t_id;
+ /* TCP port */
+ int port;
+ /* Node */
+ char *node;
+ /* Service */
+ char *service;
+} modbus_tcp_pi_t;
+
+#endif /* MODBUS_TCP_PRIVATE_H */
diff --git a/applications/EmsShower/libmodbus/modbus-tcp.c b/applications/EmsShower/libmodbus/modbus-tcp.c
new file mode 100644
index 0000000..8e5e37c
--- /dev/null
+++ b/applications/EmsShower/libmodbus/modbus-tcp.c
@@ -0,0 +1,1004 @@
+/*
+ * Copyright © Stéphane Raimbault
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+// clang-format off
+#if defined(_WIN32)
+# define OS_WIN32
+/* ws2_32.dll has getaddrinfo and freeaddrinfo on Windows XP and later.
+ * minwg32 headers check WINVER before allowing the use of these */
+# ifndef WINVER
+# define WINVER 0x0501
+# endif
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+#ifndef _MSC_VER
+#include
+#endif
+#include
+#include
+
+#if defined(_WIN32)
+/* Already set in modbus-tcp.h but it seems order matters in VS2005 */
+# include
+# include
+# define SHUT_RDWR 2
+# define close closesocket
+# define strdup _strdup
+#else
+# include
+# include
+
+#if defined(__OpenBSD__) || (defined(__FreeBSD__) && __FreeBSD__ < 5)
+# define OS_BSD
+# include
+#endif
+
+#ifdef HAVE_NETINET_IN_H
+#include
+#endif /* HAVE_NETINET_IN_H */
+#ifdef HAVE_NETINET_IP_H
+#include
+#endif /* HAVE_NETINET_IP_H */
+# include
+# include
+# include
+#endif
+
+#if !defined(MSG_NOSIGNAL)
+#define MSG_NOSIGNAL 0
+#endif
+
+#if defined(_AIX) && !defined(MSG_DONTWAIT)
+#define MSG_DONTWAIT MSG_NONBLOCK
+#endif
+// clang-format on
+
+#include "modbus-private.h"
+
+#include "modbus-tcp-private.h"
+#include "modbus-tcp.h"
+
+#ifdef OS_WIN32
+static int _modbus_tcp_init_win32(void)
+{
+ /* Initialise Windows Socket API */
+ WSADATA wsaData;
+
+ if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
+ fprintf(stderr,
+ "WSAStartup() returned error code %d\n",
+ (unsigned int) GetLastError());
+ errno = EIO;
+ return -1;
+ }
+ return 0;
+}
+#endif
+
+static int _modbus_set_slave(modbus_t *ctx, int slave)
+{
+ int max_slave = (ctx->quirks & MODBUS_QUIRK_MAX_SLAVE) ? 255 : 247;
+
+ /* Broadcast address is 0 (MODBUS_BROADCAST_ADDRESS) */
+ if (slave >= 0 && slave <= max_slave) {
+ ctx->slave = slave;
+ } else if (slave == MODBUS_TCP_SLAVE) {
+ /* The special value MODBUS_TCP_SLAVE (0xFF) can be used in TCP mode to
+ * restore the default value. */
+ ctx->slave = slave;
+ } else {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Builds a TCP request header */
+static int _modbus_tcp_build_request_basis(
+ modbus_t *ctx, int function, int addr, int nb, uint8_t *req)
+{
+ modbus_tcp_t *ctx_tcp = ctx->backend_data;
+
+ /* Increase transaction ID */
+ if (ctx_tcp->t_id < UINT16_MAX)
+ ctx_tcp->t_id++;
+ else
+ ctx_tcp->t_id = 0;
+ req[0] = ctx_tcp->t_id >> 8;
+ req[1] = ctx_tcp->t_id & 0x00ff;
+
+ /* Protocol Modbus */
+ req[2] = 0;
+ req[3] = 0;
+
+ /* Length will be defined later by set_req_length_tcp at offsets 4
+ and 5 */
+
+ req[6] = ctx->slave;
+ req[7] = function;
+ req[8] = addr >> 8;
+ req[9] = addr & 0x00ff;
+ req[10] = nb >> 8;
+ req[11] = nb & 0x00ff;
+
+ return _MODBUS_TCP_PRESET_REQ_LENGTH;
+}
+
+/* Builds a TCP response header */
+static int _modbus_tcp_build_response_basis(sft_t *sft, uint8_t *rsp)
+{
+ /* Extract from MODBUS Messaging on TCP/IP Implementation
+ Guide V1.0b (page 23/46):
+ The transaction identifier is used to associate the future
+ response with the request. */
+ rsp[0] = sft->t_id >> 8;
+ rsp[1] = sft->t_id & 0x00ff;
+
+ /* Protocol Modbus */
+ rsp[2] = 0;
+ rsp[3] = 0;
+
+ /* Length will be set later by send_msg (4 and 5) */
+
+ /* The slave ID is copied from the indication */
+ rsp[6] = sft->slave;
+ rsp[7] = sft->function;
+
+ return _MODBUS_TCP_PRESET_RSP_LENGTH;
+}
+
+static int _modbus_tcp_get_response_tid(const uint8_t *req)
+{
+ return (req[0] << 8) + req[1];
+}
+
+static int _modbus_tcp_send_msg_pre(uint8_t *req, int req_length)
+{
+ /* Subtract the header length to the message length */
+ int mbap_length = req_length - 6;
+
+ req[4] = mbap_length >> 8;
+ req[5] = mbap_length & 0x00FF;
+
+ return req_length;
+}
+
+static ssize_t _modbus_tcp_send(modbus_t *ctx, const uint8_t *req, int req_length)
+{
+ /* MSG_NOSIGNAL
+ Requests not to send SIGPIPE on errors on stream oriented
+ sockets when the other end breaks the connection. The EPIPE
+ error is still returned. */
+ return send(ctx->s, (const char *) req, req_length, MSG_NOSIGNAL);
+}
+
+static int _modbus_tcp_receive(modbus_t *ctx, uint8_t *req)
+{
+ return _modbus_receive_msg(ctx, req, MSG_INDICATION);
+}
+
+static ssize_t _modbus_tcp_recv(modbus_t *ctx, uint8_t *rsp, int rsp_length)
+{
+ return recv(ctx->s, (char *) rsp, rsp_length, 0);
+}
+
+static int _modbus_tcp_check_integrity(modbus_t *ctx, uint8_t *msg, const int msg_length)
+{
+ return msg_length;
+}
+
+static int _modbus_tcp_pre_check_confirmation(modbus_t *ctx,
+ const uint8_t *req,
+ const uint8_t *rsp,
+ int rsp_length)
+{
+ unsigned int protocol_id;
+ /* Check transaction ID */
+ if (req[0] != rsp[0] || req[1] != rsp[1]) {
+ if (ctx->debug) {
+ fprintf(stderr,
+ "Invalid transaction ID received 0x%X (not 0x%X)\n",
+ (rsp[0] << 8) + rsp[1],
+ (req[0] << 8) + req[1]);
+ }
+ errno = EMBBADDATA;
+ return -1;
+ }
+
+ /* Check protocol ID */
+ protocol_id = (rsp[2] << 8) + rsp[3];
+ if (protocol_id != 0x0) {
+ if (ctx->debug) {
+ fprintf(stderr, "Invalid protocol ID received 0x%X (not 0x0)\n", protocol_id);
+ }
+ errno = EMBBADDATA;
+ return -1;
+ }
+
+ return 0;
+}
+
+static int _modbus_tcp_set_ipv4_options(int s)
+{
+ int rc;
+ int option;
+
+ /* Set the TCP no delay flag */
+ /* SOL_TCP = IPPROTO_TCP */
+ option = 1;
+ rc = setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &option, sizeof(int));
+ if (rc == -1) {
+ return -1;
+ }
+
+ /* If the OS does not offer SOCK_NONBLOCK, fall back to setting FIONBIO to
+ * make sockets non-blocking */
+ /* Do not care about the return value, this is optional */
+#if !defined(SOCK_NONBLOCK) && defined(FIONBIO)
+#ifdef OS_WIN32
+ {
+ /* Setting FIONBIO expects an unsigned long according to MSDN */
+ u_long loption = 1;
+ ioctlsocket(s, FIONBIO, &loption);
+ }
+#else
+ option = 1;
+ ioctl(s, FIONBIO, &option);
+#endif
+#endif
+
+#ifndef OS_WIN32
+ /**
+ * Cygwin defines IPTOS_LOWDELAY but can't handle that flag so it's
+ * necessary to workaround that problem.
+ **/
+ /* Set the IP low delay option */
+ option = IPTOS_LOWDELAY;
+ rc = setsockopt(s, IPPROTO_IP, IP_TOS, &option, sizeof(int));
+ if (rc == -1) {
+ return -1;
+ }
+#endif
+
+ return 0;
+}
+
+static int _connect(int sockfd,
+ const struct sockaddr *addr,
+ socklen_t addrlen,
+ const struct timeval *ro_tv)
+{
+ int rc = connect(sockfd, addr, addrlen);
+
+#ifdef OS_WIN32
+ int wsaError = 0;
+ if (rc == -1) {
+ wsaError = WSAGetLastError();
+ }
+
+ if (wsaError == WSAEWOULDBLOCK || wsaError == WSAEINPROGRESS) {
+#else
+ if (rc == -1 && errno == EINPROGRESS) {
+#endif
+ fd_set wset;
+ int optval;
+ socklen_t optlen = sizeof(optval);
+ struct timeval tv = *ro_tv;
+
+ /* Wait to be available in writing */
+ FD_ZERO(&wset);
+ FD_SET(sockfd, &wset);
+ rc = select(sockfd + 1, NULL, &wset, NULL, &tv);
+ if (rc < 0) {
+ /* Fail */
+ return -1;
+ }
+
+ if (rc == 0) {
+ /* Timeout */
+ errno = ETIMEDOUT;
+ return -1;
+ }
+
+ /* The connection is established if SO_ERROR and optval are set to 0 */
+ rc = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void *) &optval, &optlen);
+ if (rc == 0 && optval == 0) {
+ return 0;
+ } else {
+ errno = ECONNREFUSED;
+ return -1;
+ }
+ }
+ return rc;
+}
+
+/* Establishes a modbus TCP connection with a Modbus server. */
+static int _modbus_tcp_connect(modbus_t *ctx)
+{
+ int rc;
+ /* Specialized version of sockaddr for Internet socket address (same size) */
+ struct sockaddr_in addr;
+ modbus_tcp_t *ctx_tcp = ctx->backend_data;
+ int flags = SOCK_STREAM;
+
+#ifdef OS_WIN32
+ if (_modbus_tcp_init_win32() == -1) {
+ return -1;
+ }
+#endif
+
+#ifdef SOCK_CLOEXEC
+ flags |= SOCK_CLOEXEC;
+#endif
+
+#ifdef SOCK_NONBLOCK
+ flags |= SOCK_NONBLOCK;
+#endif
+
+ ctx->s = socket(PF_INET, flags, 0);
+ if (ctx->s < 0) {
+ return -1;
+ }
+
+ rc = _modbus_tcp_set_ipv4_options(ctx->s);
+ if (rc == -1) {
+ close(ctx->s);
+ ctx->s = -1;
+ return -1;
+ }
+
+ if (ctx->debug) {
+ printf("Connecting to %s:%d\n", ctx_tcp->ip, ctx_tcp->port);
+ }
+
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(ctx_tcp->port);
+ rc = inet_pton(addr.sin_family, ctx_tcp->ip, &(addr.sin_addr));
+ if (rc <= 0) {
+ if (ctx->debug) {
+ fprintf(stderr, "Invalid IP address: %s\n", ctx_tcp->ip);
+ }
+ close(ctx->s);
+ ctx->s = -1;
+ return -1;
+ }
+
+ rc =
+ _connect(ctx->s, (struct sockaddr *) &addr, sizeof(addr), &ctx->response_timeout);
+ if (rc == -1) {
+ close(ctx->s);
+ ctx->s = -1;
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Establishes a modbus TCP PI connection with a Modbus server. */
+static int _modbus_tcp_pi_connect(modbus_t *ctx)
+{
+ int rc;
+ struct addrinfo *ai_list;
+ struct addrinfo *ai_ptr;
+ struct addrinfo ai_hints;
+ modbus_tcp_pi_t *ctx_tcp_pi = ctx->backend_data;
+
+#ifdef OS_WIN32
+ if (_modbus_tcp_init_win32() == -1) {
+ return -1;
+ }
+#endif
+
+ memset(&ai_hints, 0, sizeof(ai_hints));
+#ifdef AI_ADDRCONFIG
+ ai_hints.ai_flags |= AI_ADDRCONFIG;
+#endif
+ ai_hints.ai_family = AF_UNSPEC;
+ ai_hints.ai_socktype = SOCK_STREAM;
+ ai_hints.ai_addr = NULL;
+ ai_hints.ai_canonname = NULL;
+ ai_hints.ai_next = NULL;
+
+ ai_list = NULL;
+ rc = getaddrinfo(ctx_tcp_pi->node, ctx_tcp_pi->service, &ai_hints, &ai_list);
+ if (rc != 0) {
+ if (ctx->debug) {
+#ifdef HAVE_GAI_STRERROR
+ fprintf(stderr, "Error returned by getaddrinfo: %s\n", gai_strerror(rc));
+#else
+ fprintf(stderr, "Error returned by getaddrinfo: %d\n", rc);
+#endif
+ }
+ freeaddrinfo(ai_list);
+ errno = ECONNREFUSED;
+ return -1;
+ }
+
+ for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next) {
+ int flags = ai_ptr->ai_socktype;
+ int s;
+
+#ifdef SOCK_CLOEXEC
+ flags |= SOCK_CLOEXEC;
+#endif
+
+#ifdef SOCK_NONBLOCK
+ flags |= SOCK_NONBLOCK;
+#endif
+
+ s = socket(ai_ptr->ai_family, flags, ai_ptr->ai_protocol);
+ if (s < 0)
+ continue;
+
+ if (ai_ptr->ai_family == AF_INET)
+ _modbus_tcp_set_ipv4_options(s);
+
+ if (ctx->debug) {
+ printf("Connecting to [%s]:%s\n", ctx_tcp_pi->node, ctx_tcp_pi->service);
+ }
+
+ rc = _connect(s, ai_ptr->ai_addr, ai_ptr->ai_addrlen, &ctx->response_timeout);
+ if (rc == -1) {
+ close(s);
+ continue;
+ }
+
+ ctx->s = s;
+ break;
+ }
+
+ freeaddrinfo(ai_list);
+
+ if (ctx->s < 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static unsigned int _modbus_tcp_is_connected(modbus_t *ctx)
+{
+ return ctx->s >= 0;
+}
+
+/* Closes the network connection and socket in TCP mode */
+static void _modbus_tcp_close(modbus_t *ctx)
+{
+ if (ctx->s >= 0) {
+ shutdown(ctx->s, SHUT_RDWR);
+ close(ctx->s);
+ ctx->s = -1;
+ }
+}
+
+static int _modbus_tcp_flush(modbus_t *ctx)
+{
+ int rc;
+ // Use an unsigned 16-bit integer to reduce overflow risk. The flush function
+ // is not expected to handle huge amounts of data (> 2GB).
+ uint16_t rc_sum = 0;
+
+ do {
+ /* Extract the garbage from the socket */
+ char devnull[MODBUS_TCP_MAX_ADU_LENGTH];
+#ifndef OS_WIN32
+ rc = recv(ctx->s, devnull, MODBUS_TCP_MAX_ADU_LENGTH, MSG_DONTWAIT);
+#else
+ /* On Win32, it's a bit more complicated to not wait */
+ fd_set rset;
+ struct timeval tv;
+
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ FD_ZERO(&rset);
+ FD_SET(ctx->s, &rset);
+ rc = select(ctx->s + 1, &rset, NULL, NULL, &tv);
+ if (rc == -1) {
+ return -1;
+ }
+
+ if (rc == 1) {
+ /* There is data to flush */
+ rc = recv(ctx->s, devnull, MODBUS_TCP_MAX_ADU_LENGTH, 0);
+ }
+#endif
+ if (rc > 0) {
+ // Check for overflow before adding
+ if (rc_sum <= UINT16_MAX - rc) {
+ rc_sum += rc;
+ } else {
+ // Handle overflow
+ ctx->error_recovery = MODBUS_ERROR_RECOVERY_PROTOCOL;
+ errno = EOVERFLOW;
+ return -1;
+ }
+ }
+ } while (rc == MODBUS_TCP_MAX_ADU_LENGTH);
+
+ return rc_sum;
+}
+
+/* Listens for any request from one or many modbus masters in TCP */
+int modbus_tcp_listen(modbus_t *ctx, int nb_connection)
+{
+ int new_s;
+ int enable;
+ int flags;
+ struct sockaddr_in addr;
+ modbus_tcp_t *ctx_tcp;
+ int rc;
+
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ ctx_tcp = ctx->backend_data;
+
+#ifdef OS_WIN32
+ if (_modbus_tcp_init_win32() == -1) {
+ return -1;
+ }
+#endif
+
+ flags = SOCK_STREAM;
+
+#ifdef SOCK_CLOEXEC
+ flags |= SOCK_CLOEXEC;
+#endif
+
+ new_s = socket(PF_INET, flags, IPPROTO_TCP);
+ if (new_s == -1) {
+ return -1;
+ }
+
+ enable = 1;
+ if (setsockopt(new_s, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) == -1) {
+ close(new_s);
+ return -1;
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ /* If the modbus port is < to 1024, we need the setuid root. */
+ addr.sin_port = htons(ctx_tcp->port);
+ if (ctx_tcp->ip[0] == '0') {
+ /* Listen any addresses */
+ addr.sin_addr.s_addr = htonl(INADDR_ANY);
+ } else {
+ /* Listen only specified IP address */
+ rc = inet_pton(addr.sin_family, ctx_tcp->ip, &(addr.sin_addr));
+ if (rc <= 0) {
+ if (ctx->debug) {
+ fprintf(stderr, "Invalid IP address: %s\n", ctx_tcp->ip);
+ }
+ close(new_s);
+ return -1;
+ }
+ }
+
+ if (bind(new_s, (struct sockaddr *) &addr, sizeof(addr)) == -1) {
+ close(new_s);
+ return -1;
+ }
+
+ if (listen(new_s, nb_connection) == -1) {
+ close(new_s);
+ return -1;
+ }
+
+ return new_s;
+}
+
+int modbus_tcp_pi_listen(modbus_t *ctx, int nb_connection)
+{
+ int rc;
+ struct addrinfo *ai_list;
+ struct addrinfo *ai_ptr;
+ struct addrinfo ai_hints;
+ const char *node;
+ const char *service;
+ int new_s;
+ modbus_tcp_pi_t *ctx_tcp_pi;
+
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ ctx_tcp_pi = ctx->backend_data;
+
+#ifdef OS_WIN32
+ if (_modbus_tcp_init_win32() == -1) {
+ return -1;
+ }
+#endif
+
+ if (ctx_tcp_pi->node[0] == 0) {
+ node = NULL; /* == any */
+ } else {
+ node = ctx_tcp_pi->node;
+ }
+
+ if (ctx_tcp_pi->service[0] == 0) {
+ service = "502";
+ } else {
+ service = ctx_tcp_pi->service;
+ }
+
+ memset(&ai_hints, 0, sizeof(ai_hints));
+ /* If node is not NULL, than the AI_PASSIVE flag is ignored. */
+ ai_hints.ai_flags |= AI_PASSIVE;
+#ifdef AI_ADDRCONFIG
+ ai_hints.ai_flags |= AI_ADDRCONFIG;
+#endif
+ ai_hints.ai_family = AF_UNSPEC;
+ ai_hints.ai_socktype = SOCK_STREAM;
+ ai_hints.ai_addr = NULL;
+ ai_hints.ai_canonname = NULL;
+ ai_hints.ai_next = NULL;
+
+ ai_list = NULL;
+ rc = getaddrinfo(node, service, &ai_hints, &ai_list);
+ if (rc != 0) {
+ if (ctx->debug) {
+#ifdef HAVE_GAI_STRERROR
+ fprintf(stderr, "Error returned by getaddrinfo: %s\n", gai_strerror(rc));
+#else
+ fprintf(stderr, "Error returned by getaddrinfo: %d\n", rc);
+#endif
+ }
+ freeaddrinfo(ai_list);
+ errno = ECONNREFUSED;
+ return -1;
+ }
+
+ new_s = -1;
+ for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next) {
+ int flags = ai_ptr->ai_socktype;
+ int s;
+
+#ifdef SOCK_CLOEXEC
+ flags |= SOCK_CLOEXEC;
+#endif
+
+ s = socket(ai_ptr->ai_family, flags, ai_ptr->ai_protocol);
+ if (s < 0) {
+ if (ctx->debug) {
+ perror("socket");
+ }
+ continue;
+ } else {
+ int enable = 1;
+ rc = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable));
+ if (rc != 0) {
+ close(s);
+ if (ctx->debug) {
+ perror("setsockopt");
+ }
+ continue;
+ }
+ }
+
+ rc = bind(s, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
+ if (rc != 0) {
+ close(s);
+ if (ctx->debug) {
+ perror("bind");
+ }
+ continue;
+ }
+
+ rc = listen(s, nb_connection);
+ if (rc != 0) {
+ close(s);
+ if (ctx->debug) {
+ perror("listen");
+ }
+ continue;
+ }
+
+ new_s = s;
+ break;
+ }
+ freeaddrinfo(ai_list);
+
+ if (new_s < 0) {
+ return -1;
+ }
+
+ return new_s;
+}
+
+int modbus_tcp_accept(modbus_t *ctx, int *s)
+{
+ struct sockaddr_in addr;
+ socklen_t addrlen;
+
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ addrlen = sizeof(addr);
+#ifdef HAVE_ACCEPT4
+ /* Inherit socket flags and use accept4 call */
+ ctx->s = accept4(*s, (struct sockaddr *) &addr, &addrlen, SOCK_CLOEXEC);
+#else
+ ctx->s = accept(*s, (struct sockaddr *) &addr, &addrlen);
+#endif
+
+ if (ctx->s < 0) {
+ return -1;
+ }
+
+ if (ctx->debug) {
+ char buf[INET_ADDRSTRLEN];
+ if (inet_ntop(AF_INET, &(addr.sin_addr), buf, INET_ADDRSTRLEN) == NULL) {
+ fprintf(stderr, "Client connection accepted from unparsable IP.\n");
+ } else {
+ printf("Client connection accepted from %s.\n", buf);
+ }
+ }
+
+ return ctx->s;
+}
+
+int modbus_tcp_pi_accept(modbus_t *ctx, int *s)
+{
+ struct sockaddr_in6 addr;
+ socklen_t addrlen;
+
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ addrlen = sizeof(addr);
+#ifdef HAVE_ACCEPT4
+ /* Inherit socket flags and use accept4 call */
+ ctx->s = accept4(*s, (struct sockaddr *) &addr, &addrlen, SOCK_CLOEXEC);
+#else
+ ctx->s = accept(*s, (struct sockaddr *) &addr, &addrlen);
+#endif
+
+ if (ctx->s < 0) {
+ return -1;
+ }
+
+ if (ctx->debug) {
+ char buf[INET6_ADDRSTRLEN];
+ if (inet_ntop(AF_INET6, &(addr.sin6_addr), buf, INET6_ADDRSTRLEN) == NULL) {
+ fprintf(stderr, "Client connection accepted from unparsable IP.\n");
+ } else {
+ printf("Client connection accepted from %s.\n", buf);
+ }
+ }
+
+ return ctx->s;
+}
+
+static int
+_modbus_tcp_select(modbus_t *ctx, fd_set *rset, struct timeval *tv, int length_to_read)
+{
+ int s_rc;
+ 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) {
+ errno = ETIMEDOUT;
+ return -1;
+ }
+
+ return s_rc;
+}
+
+static void _modbus_tcp_free(modbus_t *ctx)
+{
+ if (ctx->backend_data) {
+ free(ctx->backend_data);
+ }
+ free(ctx);
+}
+
+static void _modbus_tcp_pi_free(modbus_t *ctx)
+{
+ if (ctx->backend_data) {
+ modbus_tcp_pi_t *ctx_tcp_pi = ctx->backend_data;
+ free(ctx_tcp_pi->node);
+ free(ctx_tcp_pi->service);
+ free(ctx->backend_data);
+ }
+
+ free(ctx);
+}
+
+// clang-format off
+const modbus_backend_t _modbus_tcp_backend = {
+ _MODBUS_BACKEND_TYPE_TCP,
+ _MODBUS_TCP_HEADER_LENGTH,
+ _MODBUS_TCP_CHECKSUM_LENGTH,
+ MODBUS_TCP_MAX_ADU_LENGTH,
+ _modbus_set_slave,
+ _modbus_tcp_build_request_basis,
+ _modbus_tcp_build_response_basis,
+ _modbus_tcp_get_response_tid,
+ _modbus_tcp_send_msg_pre,
+ _modbus_tcp_send,
+ _modbus_tcp_receive,
+ _modbus_tcp_recv,
+ _modbus_tcp_check_integrity,
+ _modbus_tcp_pre_check_confirmation,
+ _modbus_tcp_connect,
+ _modbus_tcp_is_connected,
+ _modbus_tcp_close,
+ _modbus_tcp_flush,
+ _modbus_tcp_select,
+ _modbus_tcp_free
+};
+
+const modbus_backend_t _modbus_tcp_pi_backend = {
+ _MODBUS_BACKEND_TYPE_TCP,
+ _MODBUS_TCP_HEADER_LENGTH,
+ _MODBUS_TCP_CHECKSUM_LENGTH,
+ MODBUS_TCP_MAX_ADU_LENGTH,
+ _modbus_set_slave,
+ _modbus_tcp_build_request_basis,
+ _modbus_tcp_build_response_basis,
+ _modbus_tcp_get_response_tid,
+ _modbus_tcp_send_msg_pre,
+ _modbus_tcp_send,
+ _modbus_tcp_receive,
+ _modbus_tcp_recv,
+ _modbus_tcp_check_integrity,
+ _modbus_tcp_pre_check_confirmation,
+ _modbus_tcp_pi_connect,
+ _modbus_tcp_is_connected,
+ _modbus_tcp_close,
+ _modbus_tcp_flush,
+ _modbus_tcp_select,
+ _modbus_tcp_pi_free
+};
+
+// clang-format on
+
+modbus_t *modbus_new_tcp(const char *ip, int port)
+{
+ modbus_t *ctx;
+ modbus_tcp_t *ctx_tcp;
+ size_t dest_size;
+ size_t ret_size;
+
+#if defined(OS_BSD)
+ /* MSG_NOSIGNAL is unsupported on *BSD so we install an ignore
+ handler for SIGPIPE. */
+ struct sigaction sa;
+
+ sa.sa_handler = SIG_IGN;
+ if (sigaction(SIGPIPE, &sa, NULL) < 0) {
+ /* The debug flag can't be set here... */
+ fprintf(stderr, "Could not install SIGPIPE handler.\n");
+ return NULL;
+ }
+#endif
+
+ ctx = (modbus_t *) malloc(sizeof(modbus_t));
+ if (ctx == NULL) {
+ return NULL;
+ }
+ _modbus_init_common(ctx);
+
+ /* Could be changed after to reach a remote serial Modbus device */
+ ctx->slave = MODBUS_TCP_SLAVE;
+
+ ctx->backend = &_modbus_tcp_backend;
+
+ ctx->backend_data = (modbus_tcp_t *) malloc(sizeof(modbus_tcp_t));
+ if (ctx->backend_data == NULL) {
+ modbus_free(ctx);
+ errno = ENOMEM;
+ return NULL;
+ }
+ ctx_tcp = (modbus_tcp_t *) ctx->backend_data;
+
+ if (ip != NULL) {
+ dest_size = sizeof(char) * 16;
+ ret_size = strlcpy(ctx_tcp->ip, ip, dest_size);
+ if (ret_size == 0) {
+ fprintf(stderr, "The IP string is empty\n");
+ modbus_free(ctx);
+ errno = EINVAL;
+ return NULL;
+ }
+
+ if (ret_size >= dest_size) {
+ fprintf(stderr, "The IP string has been truncated\n");
+ modbus_free(ctx);
+ errno = EINVAL;
+ return NULL;
+ }
+ } else {
+ ctx_tcp->ip[0] = '0';
+ }
+ ctx_tcp->port = port;
+ ctx_tcp->t_id = 0;
+
+ return ctx;
+}
+
+modbus_t *modbus_new_tcp_pi(const char *node, const char *service)
+{
+ modbus_t *ctx;
+ modbus_tcp_pi_t *ctx_tcp_pi;
+
+ ctx = (modbus_t *) malloc(sizeof(modbus_t));
+ if (ctx == NULL) {
+ return NULL;
+ }
+ _modbus_init_common(ctx);
+
+ /* Could be changed after to reach a remote serial Modbus device */
+ ctx->slave = MODBUS_TCP_SLAVE;
+
+ ctx->backend = &_modbus_tcp_pi_backend;
+
+ ctx->backend_data = (modbus_tcp_pi_t *) malloc(sizeof(modbus_tcp_pi_t));
+ if (ctx->backend_data == NULL) {
+ modbus_free(ctx);
+ errno = ENOMEM;
+ return NULL;
+ }
+ ctx_tcp_pi = (modbus_tcp_pi_t *) ctx->backend_data;
+ ctx_tcp_pi->node = NULL;
+ ctx_tcp_pi->service = NULL;
+
+ if (node != NULL) {
+ ctx_tcp_pi->node = strdup(node);
+ } else {
+ /* The node argument can be empty to indicate any hosts */
+ ctx_tcp_pi->node = strdup("");
+ }
+
+ if (ctx_tcp_pi->node == NULL) {
+ modbus_free(ctx);
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ if (service != NULL && service[0] != '\0') {
+ ctx_tcp_pi->service = strdup(service);
+ } else {
+ /* Default Modbus port number */
+ ctx_tcp_pi->service = strdup("502");
+ }
+
+ if (ctx_tcp_pi->service == NULL) {
+ modbus_free(ctx);
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ ctx_tcp_pi->t_id = 0;
+
+ return ctx;
+}
diff --git a/applications/EmsShower/libmodbus/modbus-tcp.h b/applications/EmsShower/libmodbus/modbus-tcp.h
new file mode 100644
index 0000000..768d38c
--- /dev/null
+++ b/applications/EmsShower/libmodbus/modbus-tcp.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright © Stéphane Raimbault
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef MODBUS_TCP_H
+#define MODBUS_TCP_H
+
+#include "modbus.h"
+
+MODBUS_BEGIN_DECLS
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
+/* Win32 with MinGW, supplement to */
+#include
+#if !defined(ECONNRESET)
+#define ECONNRESET WSAECONNRESET
+#endif
+#if !defined(ECONNREFUSED)
+#define ECONNREFUSED WSAECONNREFUSED
+#endif
+#if !defined(ETIMEDOUT)
+#define ETIMEDOUT WSAETIMEDOUT
+#endif
+#if !defined(ENOPROTOOPT)
+#define ENOPROTOOPT WSAENOPROTOOPT
+#endif
+#if !defined(EINPROGRESS)
+#define EINPROGRESS WSAEINPROGRESS
+#endif
+#endif
+
+#define MODBUS_TCP_DEFAULT_PORT 502
+#define MODBUS_TCP_SLAVE 0xFF
+
+/* Modbus_Application_Protocol_V1_1b.pdf Chapter 4 Section 1 Page 5
+ * TCP MODBUS ADU = 253 bytes + MBAP (7 bytes) = 260 bytes
+ */
+#define MODBUS_TCP_MAX_ADU_LENGTH 260
+
+MODBUS_API modbus_t *modbus_new_tcp(const char *ip_address, int port);
+MODBUS_API int modbus_tcp_listen(modbus_t *ctx, int nb_connection);
+MODBUS_API int modbus_tcp_accept(modbus_t *ctx, int *s);
+
+MODBUS_API modbus_t *modbus_new_tcp_pi(const char *node, const char *service);
+MODBUS_API int modbus_tcp_pi_listen(modbus_t *ctx, int nb_connection);
+MODBUS_API int modbus_tcp_pi_accept(modbus_t *ctx, int *s);
+
+MODBUS_END_DECLS
+
+#endif /* MODBUS_TCP_H */
diff --git a/applications/EmsShower/libmodbus/modbus-version.h b/applications/EmsShower/libmodbus/modbus-version.h
new file mode 100644
index 0000000..72939ec
--- /dev/null
+++ b/applications/EmsShower/libmodbus/modbus-version.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright © Stéphane Raimbault
+ *
+ * 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_VERSION_H
+#define MODBUS_VERSION_H
+
+/* The major version, (1, if %LIBMODBUS_VERSION is 1.2.3) */
+#define LIBMODBUS_VERSION_MAJOR (3)
+
+/* The minor version (2, if %LIBMODBUS_VERSION is 1.2.3) */
+#define LIBMODBUS_VERSION_MINOR (1)
+
+/* The micro version (3, if %LIBMODBUS_VERSION is 1.2.3) */
+#define LIBMODBUS_VERSION_MICRO (11)
+
+/* The full version, like 1.2.3 */
+#define LIBMODBUS_VERSION 3.1.11
+
+/* The full version, in string form (suited for string concatenation)
+ */
+#define LIBMODBUS_VERSION_STRING "3.1.11"
+
+/* Numerically encoded version, eg. v1.2.3 is 0x010203 */
+#define LIBMODBUS_VERSION_HEX \
+ ((LIBMODBUS_VERSION_MAJOR << 16) | (LIBMODBUS_VERSION_MINOR << 8) | \
+ (LIBMODBUS_VERSION_MICRO << 0))
+
+/* Evaluates to True if the version is greater than @major, @minor and @micro
+ */
+#define LIBMODBUS_VERSION_CHECK(major, minor, micro) \
+ (LIBMODBUS_VERSION_MAJOR > (major) || \
+ (LIBMODBUS_VERSION_MAJOR == (major) && LIBMODBUS_VERSION_MINOR > (minor)) || \
+ (LIBMODBUS_VERSION_MAJOR == (major) && LIBMODBUS_VERSION_MINOR == (minor) && \
+ LIBMODBUS_VERSION_MICRO >= (micro)))
+
+#endif /* MODBUS_VERSION_H */
diff --git a/applications/EmsShower/libmodbus/modbus.c b/applications/EmsShower/libmodbus/modbus.c
new file mode 100644
index 0000000..9f9f4ae
--- /dev/null
+++ b/applications/EmsShower/libmodbus/modbus.c
@@ -0,0 +1,2128 @@
+/*
+ * Copyright © Stéphane Raimbault
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library implements the Modbus protocol.
+ * http://libmodbus.org/
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#ifndef _MSC_VER
+#include
+#endif
+
+#include "libmodbus/config.h"
+
+#include "modbus-private.h"
+#include "modbus.h"
+
+/* Internal use */
+#define MSG_LENGTH_UNDEFINED -1
+
+/* Exported version */
+const unsigned int libmodbus_version_major = LIBMODBUS_VERSION_MAJOR;
+const unsigned int libmodbus_version_minor = LIBMODBUS_VERSION_MINOR;
+const unsigned int libmodbus_version_micro = LIBMODBUS_VERSION_MICRO;
+
+/* Max between RTU and TCP max adu length (so TCP) */
+#define MAX_MESSAGE_LENGTH 260
+
+/* 3 steps are used to parse the query */
+typedef enum {
+ _STEP_FUNCTION,
+ _STEP_META,
+ _STEP_DATA
+} _step_t;
+
+const char *modbus_strerror(int errnum)
+{
+ switch (errnum) {
+ case EMBXILFUN:
+ return "Illegal function";
+ case EMBXILADD:
+ return "Illegal data address";
+ case EMBXILVAL:
+ return "Illegal data value";
+ case EMBXSFAIL:
+ return "Slave device or server failure";
+ case EMBXACK:
+ return "Acknowledge";
+ case EMBXSBUSY:
+ return "Slave device or server is busy";
+ case EMBXNACK:
+ return "Negative acknowledge";
+ case EMBXMEMPAR:
+ return "Memory parity error";
+ case EMBXGPATH:
+ return "Gateway path unavailable";
+ case EMBXGTAR:
+ return "Target device failed to respond";
+ case EMBBADCRC:
+ return "Invalid CRC";
+ case EMBBADDATA:
+ return "Invalid data";
+ case EMBBADEXC:
+ return "Invalid exception code";
+ case EMBMDATA:
+ return "Too many data";
+ case EMBBADSLAVE:
+ return "Response not from requested slave";
+ default:
+ return strerror(errnum);
+ }
+}
+
+void _error_print(modbus_t *ctx, const char *context)
+{
+ if (ctx->debug) {
+ fprintf(stderr, "ERROR %s", modbus_strerror(errno));
+ if (context != NULL) {
+ fprintf(stderr, ": %s\n", context);
+ } else {
+ fprintf(stderr, "\n");
+ }
+ }
+}
+
+static void _sleep_response_timeout(modbus_t *ctx)
+{
+ /* Response timeout is always positive */
+#ifdef _WIN32
+ /* usleep doesn't exist on Windows */
+ Sleep((ctx->response_timeout.tv_sec * 1000) + (ctx->response_timeout.tv_usec / 1000));
+#else
+ /* usleep source code */
+ struct timespec request, remaining;
+ request.tv_sec = ctx->response_timeout.tv_sec;
+ request.tv_nsec = ((long int) ctx->response_timeout.tv_usec) * 1000;
+ while (nanosleep(&request, &remaining) == -1 && errno == EINTR) {
+ request = remaining;
+ }
+#endif
+}
+
+int modbus_flush(modbus_t *ctx)
+{
+ int rc;
+
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ rc = ctx->backend->flush(ctx);
+ if (rc != -1 && ctx->debug) {
+ /* Not all backends are able to return the number of bytes flushed */
+ printf("Bytes flushed (%d)\n", rc);
+ }
+ return rc;
+}
+
+/* Computes the length of the expected response including checksum */
+static unsigned int compute_response_length_from_request(modbus_t *ctx, uint8_t *req)
+{
+ int length;
+ const int offset = ctx->backend->header_length;
+
+ switch (req[offset]) {
+ case MODBUS_FC_READ_COILS:
+ case MODBUS_FC_READ_DISCRETE_INPUTS: {
+ /* Header + nb values (code from write_bits) */
+ int nb = (req[offset + 3] << 8) | req[offset + 4];
+ length = 2 + (nb / 8) + ((nb % 8) ? 1 : 0);
+ } break;
+ case MODBUS_FC_WRITE_AND_READ_REGISTERS:
+ case MODBUS_FC_READ_HOLDING_REGISTERS:
+ case MODBUS_FC_READ_INPUT_REGISTERS:
+ /* Header + 2 * nb values */
+ length = 2 + 2 * (req[offset + 3] << 8 | req[offset + 4]);
+ break;
+ case MODBUS_FC_READ_EXCEPTION_STATUS:
+ length = 3;
+ break;
+ case MODBUS_FC_REPORT_SLAVE_ID:
+ /* The response is device specific (the header provides the
+ length) */
+ return MSG_LENGTH_UNDEFINED;
+ case MODBUS_FC_MASK_WRITE_REGISTER:
+ length = 7;
+ break;
+ default:
+ length = 5;
+ }
+
+ return offset + length + ctx->backend->checksum_length;
+}
+
+/* Sends a request/response */
+static int send_msg(modbus_t *ctx, uint8_t *msg, int msg_length)
+{
+ int rc;
+ int i;
+
+ msg_length = ctx->backend->send_msg_pre(msg, msg_length);
+
+ if (ctx->debug) {
+ for (i = 0; i < msg_length; i++)
+ printf("[%.2X]", msg[i]);
+ printf("\n");
+ }
+
+ /* In recovery mode, the write command will be issued until to be
+ successful! Disabled by default. */
+ do {
+ rc = ctx->backend->send(ctx, msg, msg_length);
+ if (rc == -1) {
+ _error_print(ctx, NULL);
+ if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK) {
+#ifdef _WIN32
+ const int wsa_err = WSAGetLastError();
+ if (wsa_err == WSAENETRESET || wsa_err == WSAENOTCONN ||
+ wsa_err == WSAENOTSOCK || wsa_err == WSAESHUTDOWN ||
+ wsa_err == WSAEHOSTUNREACH || wsa_err == WSAECONNABORTED ||
+ wsa_err == WSAECONNRESET || wsa_err == WSAETIMEDOUT) {
+ modbus_close(ctx);
+ _sleep_response_timeout(ctx);
+ modbus_connect(ctx);
+ } else {
+ _sleep_response_timeout(ctx);
+ modbus_flush(ctx);
+ }
+#else
+ int saved_errno = errno;
+
+ if ((errno == EBADF || errno == ECONNRESET || errno == EPIPE)) {
+ modbus_close(ctx);
+ _sleep_response_timeout(ctx);
+ modbus_connect(ctx);
+ } else {
+ _sleep_response_timeout(ctx);
+ modbus_flush(ctx);
+ }
+ errno = saved_errno;
+#endif
+ }
+ }
+ } while ((ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK) && rc == -1);
+
+ if (rc > 0 && rc != msg_length) {
+ errno = EMBBADDATA;
+ return -1;
+ }
+
+ return rc;
+}
+
+int modbus_send_raw_request_tid(modbus_t *ctx,
+ const uint8_t *raw_req,
+ int raw_req_length,
+ int tid)
+{
+ sft_t sft;
+ uint8_t req[MAX_MESSAGE_LENGTH];
+ int req_length;
+
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (raw_req_length < 2 || raw_req_length > (MODBUS_MAX_PDU_LENGTH + 1)) {
+ /* The raw request must contain function and slave at least and
+ must not be longer than the maximum pdu length plus the slave
+ address. */
+ errno = EINVAL;
+ return -1;
+ }
+
+ sft.slave = raw_req[0];
+ sft.function = raw_req[1];
+ /* The t_id is left to zero */
+ sft.t_id = tid;
+ /* This response function only set the header so it's convenient here */
+ req_length = ctx->backend->build_response_basis(&sft, req);
+
+ if (raw_req_length > 2) {
+ /* Copy data after function code */
+ memcpy(req + req_length, raw_req + 2, raw_req_length - 2);
+ req_length += raw_req_length - 2;
+ }
+
+ return send_msg(ctx, req, req_length);
+}
+
+int modbus_send_raw_request(modbus_t *ctx, const uint8_t *raw_req, int raw_req_length)
+{
+ return modbus_send_raw_request_tid(ctx, raw_req, raw_req_length, 0);
+}
+
+/*
+ * ---------- Request Indication ----------
+ * | Client | ---------------------->| Server |
+ * ---------- Confirmation Response ----------
+ */
+
+/* Computes the length to read after the function received */
+static uint8_t compute_meta_length_after_function(int function, msg_type_t msg_type)
+{
+ int length;
+
+ if (msg_type == MSG_INDICATION) {
+ if (function <= MODBUS_FC_WRITE_SINGLE_REGISTER) {
+ length = 4;
+ } else if (function == MODBUS_FC_WRITE_MULTIPLE_COILS ||
+ function == MODBUS_FC_WRITE_MULTIPLE_REGISTERS) {
+ length = 5;
+ } else if (function == MODBUS_FC_MASK_WRITE_REGISTER) {
+ length = 6;
+ } else if (function == MODBUS_FC_WRITE_AND_READ_REGISTERS) {
+ length = 9;
+ } else {
+ /* MODBUS_FC_READ_EXCEPTION_STATUS, MODBUS_FC_REPORT_SLAVE_ID */
+ length = 0;
+ }
+ } else {
+ /* MSG_CONFIRMATION */
+ switch (function) {
+ case MODBUS_FC_WRITE_SINGLE_COIL:
+ case MODBUS_FC_WRITE_SINGLE_REGISTER:
+ case MODBUS_FC_WRITE_MULTIPLE_COILS:
+ case MODBUS_FC_WRITE_MULTIPLE_REGISTERS:
+ length = 4;
+ break;
+ case MODBUS_FC_MASK_WRITE_REGISTER:
+ length = 6;
+ break;
+ default:
+ length = 1;
+ }
+ }
+
+ return length;
+}
+
+/* Computes the length to read after the meta information (address, count, etc) */
+static int
+compute_data_length_after_meta(modbus_t *ctx, uint8_t *msg, msg_type_t msg_type)
+{
+ int function = msg[ctx->backend->header_length];
+ int length;
+
+ if (msg_type == MSG_INDICATION) {
+ switch (function) {
+ case MODBUS_FC_WRITE_MULTIPLE_COILS:
+ case MODBUS_FC_WRITE_MULTIPLE_REGISTERS:
+ length = msg[ctx->backend->header_length + 5];
+ break;
+ case MODBUS_FC_WRITE_AND_READ_REGISTERS:
+ length = msg[ctx->backend->header_length + 9];
+ break;
+ default:
+ length = 0;
+ }
+ } else {
+ /* MSG_CONFIRMATION */
+ if (function <= MODBUS_FC_READ_INPUT_REGISTERS ||
+ function == MODBUS_FC_REPORT_SLAVE_ID ||
+ function == MODBUS_FC_WRITE_AND_READ_REGISTERS) {
+ length = msg[ctx->backend->header_length + 1];
+ } else {
+ length = 0;
+ }
+ }
+
+ length += ctx->backend->checksum_length;
+
+ return length;
+}
+
+/* Waits a response from a modbus server or a request from a modbus client.
+ This function blocks if there is no replies (3 timeouts).
+
+ The function shall return the number of received characters and the received
+ message in an array of uint8_t if successful. Otherwise it shall return -1
+ and errno is set to one of the values defined below:
+ - ECONNRESET
+ - EMBBADDATA
+ - ETIMEDOUT
+ - read() or recv() error codes
+*/
+
+int _modbus_receive_msg(modbus_t *ctx, uint8_t *msg, msg_type_t msg_type)
+{
+ int rc;
+ fd_set rset;
+ struct timeval tv;
+ struct timeval *p_tv;
+ unsigned int length_to_read;
+ int msg_length = 0;
+ _step_t step;
+#ifdef _WIN32
+ int wsa_err;
+#endif
+
+ if (ctx->debug) {
+ if (msg_type == MSG_INDICATION) {
+ printf("Waiting for an indication...\n");
+ } else {
+ printf("Waiting for a confirmation...\n");
+ }
+ }
+
+ if (!ctx->backend->is_connected(ctx)) {
+ if (ctx->debug) {
+ fprintf(stderr, "ERROR The connection is not established.\n");
+ }
+ return -1;
+ }
+
+ /* Add a file descriptor to the set */
+ FD_ZERO(&rset);
+ FD_SET(ctx->s, &rset);
+
+ /* We need to analyse the message step by step. At the first step, we want
+ * to reach the function code because all packets contain this
+ * information. */
+ step = _STEP_FUNCTION;
+ length_to_read = ctx->backend->header_length + 1;
+
+ if (msg_type == MSG_INDICATION) {
+ /* Wait for a message, we don't know when the message will be received */
+ if (ctx->indication_timeout.tv_sec == 0 && ctx->indication_timeout.tv_usec == 0) {
+ /* By default, the indication timeout isn't set */
+ p_tv = NULL;
+ } else {
+ /* Wait for an indication (name of a received request by a server, see schema)
+ */
+ tv.tv_sec = ctx->indication_timeout.tv_sec;
+ tv.tv_usec = ctx->indication_timeout.tv_usec;
+ p_tv = &tv;
+ }
+ } else {
+ tv.tv_sec = ctx->response_timeout.tv_sec;
+ tv.tv_usec = ctx->response_timeout.tv_usec;
+ p_tv = &tv;
+ }
+
+ while (length_to_read != 0) {
+ rc = ctx->backend->select(ctx, &rset, p_tv, length_to_read);
+ if (rc == -1) {
+ _error_print(ctx, "select");
+ if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK) {
+#ifdef _WIN32
+ wsa_err = WSAGetLastError();
+
+ // no equivalent to ETIMEDOUT when select fails on Windows
+ if (wsa_err == WSAENETDOWN || wsa_err == WSAENOTSOCK) {
+ modbus_close(ctx);
+ modbus_connect(ctx);
+ }
+#else
+ int saved_errno = errno;
+
+ if (errno == ETIMEDOUT) {
+ _sleep_response_timeout(ctx);
+ modbus_flush(ctx);
+ } else if (errno == EBADF) {
+ modbus_close(ctx);
+ modbus_connect(ctx);
+ }
+ errno = saved_errno;
+#endif
+ }
+ return -1;
+ }
+
+ rc = ctx->backend->recv(ctx, msg + msg_length, length_to_read);
+ if (rc == 0) {
+ errno = ECONNRESET;
+ rc = -1;
+ }
+
+ if (rc == -1) {
+ _error_print(ctx, "read");
+#ifdef _WIN32
+ wsa_err = WSAGetLastError();
+ if ((ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK) &&
+ (wsa_err == WSAENOTCONN || wsa_err == WSAENETRESET ||
+ wsa_err == WSAENOTSOCK || wsa_err == WSAESHUTDOWN ||
+ wsa_err == WSAECONNABORTED || wsa_err == WSAETIMEDOUT ||
+ wsa_err == WSAECONNRESET)) {
+ modbus_close(ctx);
+ modbus_connect(ctx);
+ }
+#else
+ if ((ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK) &&
+ (errno == ECONNRESET || errno == ECONNREFUSED || errno == EBADF)) {
+ int saved_errno = errno;
+ modbus_close(ctx);
+ modbus_connect(ctx);
+ /* Could be removed by previous calls */
+ errno = saved_errno;
+ }
+#endif
+ return -1;
+ }
+
+ /* Display the hex code of each character received */
+ if (ctx->debug) {
+ int i;
+ for (i = 0; i < rc; i++)
+ printf("<%.2X>", msg[msg_length + i]);
+ }
+
+ /* Sums bytes received */
+ msg_length += rc;
+ /* Computes remaining bytes */
+ length_to_read -= rc;
+
+ if (length_to_read == 0) {
+ switch (step) {
+ case _STEP_FUNCTION:
+ /* Function code position */
+ length_to_read = compute_meta_length_after_function(
+ msg[ctx->backend->header_length], msg_type);
+ if (length_to_read != 0) {
+ step = _STEP_META;
+ break;
+ } /* else switches straight to the next step */
+ case _STEP_META:
+ length_to_read = compute_data_length_after_meta(ctx, msg, msg_type);
+ if ((msg_length + length_to_read) > ctx->backend->max_adu_length) {
+ errno = EMBBADDATA;
+ _error_print(ctx, "too many data");
+ return -1;
+ }
+ step = _STEP_DATA;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (length_to_read > 0 &&
+ (ctx->byte_timeout.tv_sec > 0 || ctx->byte_timeout.tv_usec > 0)) {
+ /* If there is no character in the buffer, the allowed timeout
+ interval between two consecutive bytes is defined by
+ byte_timeout */
+ tv.tv_sec = ctx->byte_timeout.tv_sec;
+ tv.tv_usec = ctx->byte_timeout.tv_usec;
+ p_tv = &tv;
+ }
+ /* else timeout isn't set again, the full response must be read before
+ expiration of response timeout (for CONFIRMATION only) */
+ }
+
+ if (ctx->debug)
+ printf("\n");
+
+ return ctx->backend->check_integrity(ctx, msg, msg_length);
+}
+
+/* Receive the request from a modbus master */
+int modbus_receive(modbus_t *ctx, uint8_t *req)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return ctx->backend->receive(ctx, req);
+}
+
+/* Receives the confirmation.
+
+ The function shall store the read response in rsp and return the number of
+ values (bits or words). Otherwise, its shall return -1 and errno is set.
+
+ The function doesn't check the confirmation is the expected response to the
+ initial request.
+*/
+int modbus_receive_confirmation(modbus_t *ctx, uint8_t *rsp)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION);
+}
+
+static int check_confirmation(modbus_t *ctx, uint8_t *req, uint8_t *rsp, int rsp_length)
+{
+ int rc;
+ int rsp_length_computed;
+ const unsigned int offset = ctx->backend->header_length;
+ const int function = rsp[offset];
+
+ if (ctx->backend->pre_check_confirmation) {
+ rc = ctx->backend->pre_check_confirmation(ctx, req, rsp, rsp_length);
+ if (rc == -1) {
+ if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_PROTOCOL) {
+ _sleep_response_timeout(ctx);
+ modbus_flush(ctx);
+ }
+ return -1;
+ }
+ }
+
+ rsp_length_computed = compute_response_length_from_request(ctx, req);
+
+ /* Exception code */
+ if (function >= 0x80) {
+ if (rsp_length == (int) (offset + 2 + ctx->backend->checksum_length) &&
+ req[offset] == (rsp[offset] - 0x80)) {
+ /* Valid exception code received */
+
+ int exception_code = rsp[offset + 1];
+ if (exception_code < MODBUS_EXCEPTION_MAX) {
+ errno = MODBUS_ENOBASE + exception_code;
+ } else {
+ errno = EMBBADEXC;
+ }
+ _error_print(ctx, NULL);
+ return -1;
+ } else {
+ errno = EMBBADEXC;
+ _error_print(ctx, NULL);
+ return -1;
+ }
+ }
+
+ /* Check length */
+ if ((rsp_length == rsp_length_computed ||
+ rsp_length_computed == MSG_LENGTH_UNDEFINED) &&
+ function < 0x80) {
+ int req_nb_value;
+ int rsp_nb_value;
+ int resp_addr_ok = TRUE;
+ int resp_data_ok = TRUE;
+
+ /* Check function code */
+ if (function != req[offset]) {
+ if (ctx->debug) {
+ fprintf(
+ stderr,
+ "Received function not corresponding to the request (0x%X != 0x%X)\n",
+ function,
+ req[offset]);
+ }
+ if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_PROTOCOL) {
+ _sleep_response_timeout(ctx);
+ modbus_flush(ctx);
+ }
+ errno = EMBBADDATA;
+ return -1;
+ }
+
+ /* Check the number of values is corresponding to the request */
+ switch (function) {
+ case MODBUS_FC_READ_COILS:
+ case MODBUS_FC_READ_DISCRETE_INPUTS:
+ /* Read functions, 8 values in a byte (nb
+ * of values in the request and byte count in
+ * the response. */
+ req_nb_value = (req[offset + 3] << 8) + req[offset + 4];
+ req_nb_value = (req_nb_value / 8) + ((req_nb_value % 8) ? 1 : 0);
+ rsp_nb_value = rsp[offset + 1];
+ break;
+ case MODBUS_FC_WRITE_AND_READ_REGISTERS:
+ case MODBUS_FC_READ_HOLDING_REGISTERS:
+ case MODBUS_FC_READ_INPUT_REGISTERS:
+ /* Read functions 1 value = 2 bytes */
+ req_nb_value = (req[offset + 3] << 8) + req[offset + 4];
+ rsp_nb_value = (rsp[offset + 1] / 2);
+ break;
+ case MODBUS_FC_WRITE_MULTIPLE_COILS:
+ case MODBUS_FC_WRITE_MULTIPLE_REGISTERS:
+ /* address in request and response must be equal */
+ if ((req[offset + 1] != rsp[offset + 1]) ||
+ (req[offset + 2] != rsp[offset + 2])) {
+ resp_addr_ok = FALSE;
+ }
+ /* N Write functions */
+ req_nb_value = (req[offset + 3] << 8) + req[offset + 4];
+ rsp_nb_value = (rsp[offset + 3] << 8) | rsp[offset + 4];
+ break;
+ case MODBUS_FC_REPORT_SLAVE_ID:
+ /* Report slave ID (bytes received) */
+ req_nb_value = rsp_nb_value = rsp[offset + 1];
+ break;
+ case MODBUS_FC_WRITE_SINGLE_COIL:
+ case MODBUS_FC_WRITE_SINGLE_REGISTER:
+ /* address in request and response must be equal */
+ if ((req[offset + 1] != rsp[offset + 1]) ||
+ (req[offset + 2] != rsp[offset + 2])) {
+ resp_addr_ok = FALSE;
+ }
+ /* data in request and response must be equal */
+ if ((req[offset + 3] != rsp[offset + 3]) ||
+ (req[offset + 4] != rsp[offset + 4])) {
+ resp_data_ok = FALSE;
+ }
+ /* 1 Write functions & others */
+ req_nb_value = rsp_nb_value = 1;
+ break;
+ default:
+ /* 1 Write functions & others */
+ req_nb_value = rsp_nb_value = 1;
+ break;
+ }
+
+ if ((req_nb_value == rsp_nb_value) && (resp_addr_ok == TRUE) &&
+ (resp_data_ok == TRUE)) {
+ rc = rsp_nb_value;
+ } else {
+ if (ctx->debug) {
+ fprintf(stderr,
+ "Received data not corresponding to the request (%d != %d)\n",
+ rsp_nb_value,
+ req_nb_value);
+ }
+
+ if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_PROTOCOL) {
+ _sleep_response_timeout(ctx);
+ modbus_flush(ctx);
+ }
+
+ errno = EMBBADDATA;
+ rc = -1;
+ }
+ } else {
+ if (ctx->debug) {
+ fprintf(
+ stderr,
+ "Message length not corresponding to the computed length (%d != %d)\n",
+ rsp_length,
+ rsp_length_computed);
+ }
+ if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_PROTOCOL) {
+ _sleep_response_timeout(ctx);
+ modbus_flush(ctx);
+ }
+ errno = EMBBADDATA;
+ rc = -1;
+ }
+
+ return rc;
+}
+
+static int
+response_io_status(uint8_t *tab_io_status, int address, int nb, uint8_t *rsp, int offset)
+{
+ int shift = 0;
+ /* Instead of byte (not allowed in Win32) */
+ int one_byte = 0;
+ int i;
+
+ for (i = address; i < address + nb; i++) {
+ one_byte |= tab_io_status[i] << shift;
+ if (shift == 7) {
+ /* Byte is full */
+ rsp[offset++] = one_byte;
+ one_byte = shift = 0;
+ } else {
+ shift++;
+ }
+ }
+
+ if (shift != 0)
+ rsp[offset++] = one_byte;
+
+ return offset;
+}
+
+/* Build the exception response */
+static int response_exception(modbus_t *ctx,
+ sft_t *sft,
+ int exception_code,
+ uint8_t *rsp,
+ unsigned int to_flush,
+ const char *template,
+ ...)
+{
+ int rsp_length;
+
+ /* Print debug message */
+ if (ctx->debug) {
+ va_list ap;
+
+ va_start(ap, template);
+ vfprintf(stderr, template, ap);
+ va_end(ap);
+ }
+
+ /* Flush if required */
+ if (to_flush) {
+ _sleep_response_timeout(ctx);
+ modbus_flush(ctx);
+ }
+
+ /* Build exception response */
+ sft->function = sft->function + 0x80;
+ rsp_length = ctx->backend->build_response_basis(sft, rsp);
+ rsp[rsp_length++] = exception_code;
+
+ return rsp_length;
+}
+
+/* Send a response to the received request.
+ Analyses the request and constructs a response.
+
+ If an error occurs, this function construct the response
+ accordingly.
+*/
+int modbus_reply(modbus_t *ctx,
+ const uint8_t *req,
+ int req_length,
+ modbus_mapping_t *mb_mapping)
+{
+ unsigned int offset;
+ int slave;
+ int function;
+ uint16_t address;
+ uint8_t rsp[MAX_MESSAGE_LENGTH];
+ int rsp_length = 0;
+ sft_t sft;
+
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ offset = ctx->backend->header_length;
+ slave = req[offset - 1];
+ function = req[offset];
+ address = (req[offset + 1] << 8) + req[offset + 2];
+
+ sft.slave = slave;
+ sft.function = function;
+ sft.t_id = ctx->backend->get_response_tid(req);
+
+ /* Data are flushed on illegal number of values errors. */
+ switch (function) {
+ case MODBUS_FC_READ_COILS:
+ case MODBUS_FC_READ_DISCRETE_INPUTS: {
+ unsigned int is_input = (function == MODBUS_FC_READ_DISCRETE_INPUTS);
+ int start_bits = is_input ? mb_mapping->start_input_bits : mb_mapping->start_bits;
+ int nb_bits = is_input ? mb_mapping->nb_input_bits : mb_mapping->nb_bits;
+ uint8_t *tab_bits = is_input ? mb_mapping->tab_input_bits : mb_mapping->tab_bits;
+ const char *const name = is_input ? "read_input_bits" : "read_bits";
+ int nb = (req[offset + 3] << 8) + req[offset + 4];
+ /* The mapping can be shifted to reduce memory consumption and it
+ doesn't always start at address zero. */
+ int mapping_address = address - start_bits;
+
+ if (nb < 1 || MODBUS_MAX_READ_BITS < nb) {
+ rsp_length = response_exception(ctx,
+ &sft,
+ MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE,
+ rsp,
+ TRUE,
+ "Illegal nb of values %d in %s (max %d)\n",
+ nb,
+ name,
+ MODBUS_MAX_READ_BITS);
+ } else if (mapping_address < 0 || (mapping_address + nb) > nb_bits) {
+ rsp_length = response_exception(ctx,
+ &sft,
+ MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS,
+ rsp,
+ FALSE,
+ "Illegal data address 0x%0X in %s\n",
+ mapping_address < 0 ? address : address + nb,
+ name);
+ } else {
+ rsp_length = ctx->backend->build_response_basis(&sft, rsp);
+ rsp[rsp_length++] = (nb / 8) + ((nb % 8) ? 1 : 0);
+ rsp_length =
+ response_io_status(tab_bits, mapping_address, nb, rsp, rsp_length);
+ }
+ } break;
+ case MODBUS_FC_READ_HOLDING_REGISTERS:
+ case MODBUS_FC_READ_INPUT_REGISTERS: {
+ unsigned int is_input = (function == MODBUS_FC_READ_INPUT_REGISTERS);
+ int start_registers =
+ is_input ? mb_mapping->start_input_registers : mb_mapping->start_registers;
+ int nb_registers =
+ is_input ? mb_mapping->nb_input_registers : mb_mapping->nb_registers;
+ uint16_t *tab_registers =
+ is_input ? mb_mapping->tab_input_registers : mb_mapping->tab_registers;
+ const char *const name = is_input ? "read_input_registers" : "read_registers";
+ int nb = (req[offset + 3] << 8) + req[offset + 4];
+ /* The mapping can be shifted to reduce memory consumption and it
+ doesn't always start at address zero. */
+ int mapping_address = address - start_registers;
+
+ if (nb < 1 || MODBUS_MAX_READ_REGISTERS < nb) {
+ rsp_length = response_exception(ctx,
+ &sft,
+ MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE,
+ rsp,
+ TRUE,
+ "Illegal nb of values %d in %s (max %d)\n",
+ nb,
+ name,
+ MODBUS_MAX_READ_REGISTERS);
+ } else if (mapping_address < 0 || (mapping_address + nb) > nb_registers) {
+ rsp_length = response_exception(ctx,
+ &sft,
+ MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS,
+ rsp,
+ FALSE,
+ "Illegal data address 0x%0X in %s\n",
+ mapping_address < 0 ? address : address + nb,
+ name);
+ } else {
+ int i;
+
+ rsp_length = ctx->backend->build_response_basis(&sft, rsp);
+ rsp[rsp_length++] = nb << 1;
+ for (i = mapping_address; i < mapping_address + nb; i++) {
+ rsp[rsp_length++] = tab_registers[i] >> 8;
+ rsp[rsp_length++] = tab_registers[i] & 0xFF;
+ }
+ }
+ } break;
+ case MODBUS_FC_WRITE_SINGLE_COIL: {
+ int mapping_address = address - mb_mapping->start_bits;
+
+ if (mapping_address < 0 || mapping_address >= mb_mapping->nb_bits) {
+ rsp_length = response_exception(ctx,
+ &sft,
+ MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS,
+ rsp,
+ FALSE,
+ "Illegal data address 0x%0X in write bit\n",
+ address);
+ break;
+ }
+
+ /* This check is only done here to ensure using memcpy is safe. */
+ rsp_length = compute_response_length_from_request(ctx, (uint8_t *) req);
+ if (rsp_length != req_length) {
+ /* Bad use of modbus_reply */
+ rsp_length = response_exception(
+ ctx,
+ &sft,
+ MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE,
+ rsp,
+ FALSE,
+ "Invalid request length in modbus_reply to write bit (%d)\n",
+ req_length);
+ break;
+ }
+
+ /* Don't copy the CRC, if any, it will be computed later (even if identical to the
+ * request) */
+ rsp_length -= ctx->backend->checksum_length;
+
+ int data = (req[offset + 3] << 8) + req[offset + 4];
+ if (data == 0xFF00 || data == 0x0) {
+ /* Apply the change to mapping */
+ mb_mapping->tab_bits[mapping_address] = data ? ON : OFF;
+ /* Prepare response */
+ memcpy(rsp, req, rsp_length);
+ } else {
+ rsp_length = response_exception(
+ ctx,
+ &sft,
+ MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE,
+ rsp,
+ FALSE,
+ "Illegal data value 0x%0X in write_bit request at address %0X\n",
+ data,
+ address);
+ }
+ } break;
+ case MODBUS_FC_WRITE_SINGLE_REGISTER: {
+ int mapping_address = address - mb_mapping->start_registers;
+
+ if (mapping_address < 0 || mapping_address >= mb_mapping->nb_registers) {
+ rsp_length =
+ response_exception(ctx,
+ &sft,
+ MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS,
+ rsp,
+ FALSE,
+ "Illegal data address 0x%0X in write_register\n",
+ address);
+ break;
+ }
+
+ rsp_length = compute_response_length_from_request(ctx, (uint8_t *) req);
+ if (rsp_length != req_length) {
+ /* Bad use of modbus_reply */
+ rsp_length = response_exception(
+ ctx,
+ &sft,
+ MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE,
+ rsp,
+ FALSE,
+ "Invalid request length in modbus_reply to write register (%d)\n",
+ req_length);
+ break;
+ }
+ int data = (req[offset + 3] << 8) + req[offset + 4];
+
+ mb_mapping->tab_registers[mapping_address] = data;
+
+ rsp_length -= ctx->backend->checksum_length;
+ memcpy(rsp, req, rsp_length);
+ } break;
+ case MODBUS_FC_WRITE_MULTIPLE_COILS: {
+ int nb = (req[offset + 3] << 8) + req[offset + 4];
+ int nb_bits = req[offset + 5];
+ int mapping_address = address - mb_mapping->start_bits;
+
+ if (nb < 1 || MODBUS_MAX_WRITE_BITS < nb || nb_bits * 8 < nb) {
+ /* May be the indication has been truncated on reading because of
+ * invalid address (eg. nb is 0 but the request contains values to
+ * write) so it's necessary to flush. */
+ rsp_length =
+ response_exception(ctx,
+ &sft,
+ MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE,
+ rsp,
+ TRUE,
+ "Illegal number of values %d in write_bits (max %d)\n",
+ nb,
+ MODBUS_MAX_WRITE_BITS);
+ } else if (mapping_address < 0 || (mapping_address + nb) > mb_mapping->nb_bits) {
+ rsp_length = response_exception(ctx,
+ &sft,
+ MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS,
+ rsp,
+ FALSE,
+ "Illegal data address 0x%0X in write_bits\n",
+ mapping_address < 0 ? address : address + nb);
+ } else {
+ /* 6 = byte count */
+ modbus_set_bits_from_bytes(
+ mb_mapping->tab_bits, mapping_address, nb, &req[offset + 6]);
+
+ rsp_length = ctx->backend->build_response_basis(&sft, rsp);
+ /* 4 to copy the bit address (2) and the quantity of bits */
+ memcpy(rsp + rsp_length, req + rsp_length, 4);
+ rsp_length += 4;
+ }
+ } break;
+ case MODBUS_FC_WRITE_MULTIPLE_REGISTERS: {
+ int nb = (req[offset + 3] << 8) + req[offset + 4];
+ int nb_bytes = req[offset + 5];
+ int mapping_address = address - mb_mapping->start_registers;
+
+ if (nb < 1 || MODBUS_MAX_WRITE_REGISTERS < nb || nb_bytes != nb * 2) {
+ rsp_length = response_exception(
+ ctx,
+ &sft,
+ MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE,
+ rsp,
+ TRUE,
+ "Illegal number of values %d in write_registers (max %d)\n",
+ nb,
+ MODBUS_MAX_WRITE_REGISTERS);
+ } else if (mapping_address < 0 ||
+ (mapping_address + nb) > mb_mapping->nb_registers) {
+ rsp_length =
+ response_exception(ctx,
+ &sft,
+ MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS,
+ rsp,
+ FALSE,
+ "Illegal data address 0x%0X in write_registers\n",
+ mapping_address < 0 ? address : address + nb);
+ } else {
+ int i, j;
+ for (i = mapping_address, j = 6; i < mapping_address + nb; i++, j += 2) {
+ /* 6 and 7 = first value */
+ mb_mapping->tab_registers[i] =
+ (req[offset + j] << 8) + req[offset + j + 1];
+ }
+
+ rsp_length = ctx->backend->build_response_basis(&sft, rsp);
+ /* 4 to copy the address (2) and the no. of registers */
+ memcpy(rsp + rsp_length, req + rsp_length, 4);
+ rsp_length += 4;
+ }
+ } break;
+ case MODBUS_FC_REPORT_SLAVE_ID: {
+ int str_len;
+ int byte_count_pos;
+
+ rsp_length = ctx->backend->build_response_basis(&sft, rsp);
+ /* Skip byte count for now */
+ byte_count_pos = rsp_length++;
+ rsp[rsp_length++] = _REPORT_SLAVE_ID;
+ /* Run indicator status to ON */
+ rsp[rsp_length++] = 0xFF;
+ /* LMB + length of LIBMODBUS_VERSION_STRING */
+ str_len = 3 + strlen(LIBMODBUS_VERSION_STRING);
+ memcpy(rsp + rsp_length, "LMB" LIBMODBUS_VERSION_STRING, str_len);
+ rsp_length += str_len;
+ rsp[byte_count_pos] = rsp_length - byte_count_pos - 1;
+ } break;
+ case MODBUS_FC_READ_EXCEPTION_STATUS:
+ if (ctx->debug) {
+ fprintf(stderr, "FIXME Not implemented\n");
+ }
+ errno = ENOPROTOOPT;
+ return -1;
+ break;
+ case MODBUS_FC_MASK_WRITE_REGISTER: {
+ int mapping_address = address - mb_mapping->start_registers;
+
+ if (mapping_address < 0 || mapping_address >= mb_mapping->nb_registers) {
+ rsp_length =
+ response_exception(ctx,
+ &sft,
+ MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS,
+ rsp,
+ FALSE,
+ "Illegal data address 0x%0X in write_register\n",
+ address);
+ } else {
+ uint16_t data = mb_mapping->tab_registers[mapping_address];
+ uint16_t and = (req[offset + 3] << 8) + req[offset + 4];
+ uint16_t or = (req[offset + 5] << 8) + req[offset + 6];
+
+ data = (data & and) | (or &(~and) );
+ mb_mapping->tab_registers[mapping_address] = data;
+
+ rsp_length = compute_response_length_from_request(ctx, (uint8_t *) req);
+ if (rsp_length != req_length) {
+ /* Bad use of modbus_reply */
+ rsp_length = response_exception(ctx,
+ &sft,
+ MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE,
+ rsp,
+ FALSE,
+ "Invalid request length in modbus_reply "
+ "to mask write register (%d)\n",
+ req_length);
+ break;
+ }
+
+ rsp_length -= ctx->backend->checksum_length;
+ memcpy(rsp, req, rsp_length);
+ }
+ } break;
+ case MODBUS_FC_WRITE_AND_READ_REGISTERS: {
+ int nb = (req[offset + 3] << 8) + req[offset + 4];
+ uint16_t address_write = (req[offset + 5] << 8) + req[offset + 6];
+ int nb_write = (req[offset + 7] << 8) + req[offset + 8];
+ int nb_write_bytes = req[offset + 9];
+ int mapping_address = address - mb_mapping->start_registers;
+ int mapping_address_write = address_write - mb_mapping->start_registers;
+
+ if (nb_write < 1 || MODBUS_MAX_WR_WRITE_REGISTERS < nb_write || nb < 1 ||
+ MODBUS_MAX_WR_READ_REGISTERS < nb || nb_write_bytes != nb_write * 2) {
+ rsp_length = response_exception(
+ ctx,
+ &sft,
+ MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE,
+ rsp,
+ TRUE,
+ "Illegal nb of values (W%d, R%d) in write_and_read_registers (max W%d, "
+ "R%d)\n",
+ nb_write,
+ nb,
+ MODBUS_MAX_WR_WRITE_REGISTERS,
+ MODBUS_MAX_WR_READ_REGISTERS);
+ } else if (mapping_address < 0 ||
+ (mapping_address + nb) > mb_mapping->nb_registers ||
+ mapping_address_write < 0 ||
+ (mapping_address_write + nb_write) > mb_mapping->nb_registers) {
+ rsp_length = response_exception(
+ ctx,
+ &sft,
+ MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS,
+ rsp,
+ FALSE,
+ "Illegal data read address 0x%0X or write address 0x%0X "
+ "write_and_read_registers\n",
+ mapping_address < 0 ? address : address + nb,
+ mapping_address_write < 0 ? address_write : address_write + nb_write);
+ } else {
+ int i, j;
+ rsp_length = ctx->backend->build_response_basis(&sft, rsp);
+ rsp[rsp_length++] = nb << 1;
+
+ /* Write first.
+ 10 and 11 are the offset of the first values to write */
+ for (i = mapping_address_write, j = 10; i < mapping_address_write + nb_write;
+ i++, j += 2) {
+ mb_mapping->tab_registers[i] =
+ (req[offset + j] << 8) + req[offset + j + 1];
+ }
+
+ /* and read the data for the response */
+ for (i = mapping_address; i < mapping_address + nb; i++) {
+ rsp[rsp_length++] = mb_mapping->tab_registers[i] >> 8;
+ rsp[rsp_length++] = mb_mapping->tab_registers[i] & 0xFF;
+ }
+ }
+ } break;
+
+ default:
+ rsp_length = response_exception(ctx,
+ &sft,
+ MODBUS_EXCEPTION_ILLEGAL_FUNCTION,
+ rsp,
+ TRUE,
+ "Unknown Modbus function code: 0x%0X\n",
+ function);
+ break;
+ }
+
+ /* Suppress any responses in RTU when the request was a broadcast, excepted when
+ * quirk is enabled. */
+ if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU &&
+ slave == MODBUS_BROADCAST_ADDRESS &&
+ !(ctx->quirks & MODBUS_QUIRK_REPLY_TO_BROADCAST)) {
+ return 0;
+ }
+ return send_msg(ctx, rsp, rsp_length);
+}
+
+int modbus_reply_exception(modbus_t *ctx, const uint8_t *req, unsigned int exception_code)
+{
+ unsigned int offset;
+ int slave;
+ int function;
+ uint8_t rsp[MAX_MESSAGE_LENGTH];
+ int rsp_length;
+ sft_t sft;
+
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ offset = ctx->backend->header_length;
+ slave = req[offset - 1];
+ function = req[offset];
+
+ sft.slave = slave;
+ sft.function = function + 0x80;
+ sft.t_id = ctx->backend->get_response_tid(req);
+ rsp_length = ctx->backend->build_response_basis(&sft, rsp);
+
+ /* Positive exception code */
+ if (exception_code < MODBUS_EXCEPTION_MAX) {
+ rsp[rsp_length++] = exception_code;
+ return send_msg(ctx, rsp, rsp_length);
+ } else {
+ errno = EINVAL;
+ return -1;
+ }
+}
+
+/* Reads IO status */
+static int read_io_status(modbus_t *ctx, int function, int addr, int nb, uint8_t *dest)
+{
+ int rc;
+ int req_length;
+
+ uint8_t req[_MIN_REQ_LENGTH];
+ uint8_t rsp[MAX_MESSAGE_LENGTH];
+
+ req_length = ctx->backend->build_request_basis(ctx, function, addr, nb, req);
+
+ rc = send_msg(ctx, req, req_length);
+ if (rc > 0) {
+ int temp, bit;
+ int pos = 0;
+ unsigned int offset;
+ unsigned int offset_end;
+ unsigned int i;
+
+ rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION);
+ if (rc == -1)
+ return -1;
+
+ rc = check_confirmation(ctx, req, rsp, rc);
+ if (rc == -1)
+ return -1;
+
+ offset = ctx->backend->header_length + 2;
+ offset_end = offset + rc;
+ for (i = offset; i < offset_end; i++) {
+ /* Shift reg hi_byte to temp */
+ temp = rsp[i];
+
+ for (bit = 0x01; (bit & 0xff) && (pos < nb);) {
+ dest[pos++] = (temp & bit) ? TRUE : FALSE;
+ bit = bit << 1;
+ }
+ }
+ }
+
+ return rc;
+}
+
+/* Reads the boolean status of bits and sets the array elements
+ in the destination to TRUE or FALSE (single bits). */
+int modbus_read_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest)
+{
+ int rc;
+
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (nb > MODBUS_MAX_READ_BITS) {
+ if (ctx->debug) {
+ fprintf(stderr,
+ "ERROR Too many bits requested (%d > %d)\n",
+ nb,
+ MODBUS_MAX_READ_BITS);
+ }
+ errno = EMBMDATA;
+ return -1;
+ }
+
+ rc = read_io_status(ctx, MODBUS_FC_READ_COILS, addr, nb, dest);
+
+ if (rc == -1)
+ return -1;
+ else
+ return nb;
+}
+
+/* Same as modbus_read_bits but reads the remote device input table */
+int modbus_read_input_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest)
+{
+ int rc;
+
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (nb > MODBUS_MAX_READ_BITS) {
+ if (ctx->debug) {
+ fprintf(stderr,
+ "ERROR Too many discrete inputs requested (%d > %d)\n",
+ nb,
+ MODBUS_MAX_READ_BITS);
+ }
+ errno = EMBMDATA;
+ return -1;
+ }
+
+ rc = read_io_status(ctx, MODBUS_FC_READ_DISCRETE_INPUTS, addr, nb, dest);
+
+ if (rc == -1)
+ return -1;
+ else
+ return nb;
+}
+
+/* Reads the data from a remote device and put that data into an array */
+static int read_registers(modbus_t *ctx, int function, int addr, int nb, uint16_t *dest)
+{
+ int rc;
+ int req_length;
+ uint8_t req[_MIN_REQ_LENGTH];
+ uint8_t rsp[MAX_MESSAGE_LENGTH];
+
+ if (nb > MODBUS_MAX_READ_REGISTERS) {
+ if (ctx->debug) {
+ fprintf(stderr,
+ "ERROR Too many registers requested (%d > %d)\n",
+ nb,
+ MODBUS_MAX_READ_REGISTERS);
+ }
+ errno = EMBMDATA;
+ return -1;
+ }
+
+ req_length = ctx->backend->build_request_basis(ctx, function, addr, nb, req);
+
+ rc = send_msg(ctx, req, req_length);
+ if (rc > 0) {
+ unsigned int offset;
+ int i;
+
+ rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION);
+ if (rc == -1)
+ return -1;
+
+ rc = check_confirmation(ctx, req, rsp, rc);
+ if (rc == -1)
+ return -1;
+
+ offset = ctx->backend->header_length;
+
+ for (i = 0; i < rc; i++) {
+ /* shift reg hi_byte to temp OR with lo_byte */
+ dest[i] = (rsp[offset + 2 + (i << 1)] << 8) | rsp[offset + 3 + (i << 1)];
+ }
+ }
+
+ return rc;
+}
+
+/* Reads the holding registers of remote device and put the data into an
+ array */
+int modbus_read_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest)
+{
+ int status;
+
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (nb > MODBUS_MAX_READ_REGISTERS) {
+ if (ctx->debug) {
+ fprintf(stderr,
+ "ERROR Too many registers requested (%d > %d)\n",
+ nb,
+ MODBUS_MAX_READ_REGISTERS);
+ }
+ errno = EMBMDATA;
+ return -1;
+ }
+
+ status = read_registers(ctx, MODBUS_FC_READ_HOLDING_REGISTERS, addr, nb, dest);
+ return status;
+}
+
+/* Reads the input registers of remote device and put the data into an array */
+int modbus_read_input_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest)
+{
+ int status;
+
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (nb > MODBUS_MAX_READ_REGISTERS) {
+ if (ctx->debug) {
+ fprintf(stderr,
+ "ERROR Too many input registers requested (%d > %d)\n",
+ nb,
+ MODBUS_MAX_READ_REGISTERS);
+ }
+ errno = EMBMDATA;
+ return -1;
+ }
+
+ status = read_registers(ctx, MODBUS_FC_READ_INPUT_REGISTERS, addr, nb, dest);
+
+ return status;
+}
+
+/* Write a value to the specified register of the remote device.
+ Used by write_bit and write_register */
+static int write_single(modbus_t *ctx, int function, int addr, const uint16_t value)
+{
+ int rc;
+ int req_length;
+ uint8_t req[_MIN_REQ_LENGTH];
+
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ req_length = ctx->backend->build_request_basis(ctx, function, addr, (int) value, req);
+
+ rc = send_msg(ctx, req, req_length);
+ if (rc > 0) {
+ /* Used by write_bit and write_register */
+ uint8_t rsp[MAX_MESSAGE_LENGTH];
+
+ rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION);
+ if (rc == -1)
+ return -1;
+
+ rc = check_confirmation(ctx, req, rsp, rc);
+ }
+
+ return rc;
+}
+
+/* Turns ON or OFF a single bit of the remote device */
+int modbus_write_bit(modbus_t *ctx, int addr, int status)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return write_single(ctx, MODBUS_FC_WRITE_SINGLE_COIL, addr, status ? 0xFF00 : 0);
+}
+
+/* Writes a value in one register of the remote device */
+int modbus_write_register(modbus_t *ctx, int addr, const uint16_t value)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return write_single(ctx, MODBUS_FC_WRITE_SINGLE_REGISTER, addr, value);
+}
+
+/* Write the bits of the array in the remote device */
+int modbus_write_bits(modbus_t *ctx, int addr, int nb, const uint8_t *src)
+{
+ int rc;
+ int i;
+ int byte_count;
+ int req_length;
+ int bit_check = 0;
+ int pos = 0;
+ uint8_t req[MAX_MESSAGE_LENGTH];
+
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (nb > MODBUS_MAX_WRITE_BITS) {
+ if (ctx->debug) {
+ fprintf(stderr,
+ "ERROR Writing too many bits (%d > %d)\n",
+ nb,
+ MODBUS_MAX_WRITE_BITS);
+ }
+ errno = EMBMDATA;
+ return -1;
+ }
+
+ req_length = ctx->backend->build_request_basis(
+ ctx, MODBUS_FC_WRITE_MULTIPLE_COILS, addr, nb, req);
+ byte_count = (nb / 8) + ((nb % 8) ? 1 : 0);
+ req[req_length++] = byte_count;
+
+ for (i = 0; i < byte_count; i++) {
+ int bit;
+
+ bit = 0x01;
+ req[req_length] = 0;
+
+ while ((bit & 0xFF) && (bit_check++ < nb)) {
+ if (src[pos++])
+ req[req_length] |= bit;
+ else
+ req[req_length] &= ~bit;
+
+ bit = bit << 1;
+ }
+ req_length++;
+ }
+
+ rc = send_msg(ctx, req, req_length);
+ if (rc > 0) {
+ uint8_t rsp[MAX_MESSAGE_LENGTH];
+
+ rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION);
+ if (rc == -1)
+ return -1;
+
+ rc = check_confirmation(ctx, req, rsp, rc);
+ }
+
+ return rc;
+}
+
+/* Write the values from the array to the registers of the remote device */
+int modbus_write_registers(modbus_t *ctx, int addr, int nb, const uint16_t *src)
+{
+ int rc;
+ int i;
+ int req_length;
+ int byte_count;
+ uint8_t req[MAX_MESSAGE_LENGTH];
+
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (nb > MODBUS_MAX_WRITE_REGISTERS) {
+ if (ctx->debug) {
+ fprintf(stderr,
+ "ERROR Trying to write to too many registers (%d > %d)\n",
+ nb,
+ MODBUS_MAX_WRITE_REGISTERS);
+ }
+ errno = EMBMDATA;
+ return -1;
+ }
+
+ req_length = ctx->backend->build_request_basis(
+ ctx, MODBUS_FC_WRITE_MULTIPLE_REGISTERS, addr, nb, req);
+ byte_count = nb * 2;
+ req[req_length++] = byte_count;
+
+ for (i = 0; i < nb; i++) {
+ req[req_length++] = src[i] >> 8;
+ req[req_length++] = src[i] & 0x00FF;
+ }
+
+ rc = send_msg(ctx, req, req_length);
+ if (rc > 0) {
+ uint8_t rsp[MAX_MESSAGE_LENGTH];
+
+ rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION);
+ if (rc == -1)
+ return -1;
+
+ rc = check_confirmation(ctx, req, rsp, rc);
+ }
+
+ return rc;
+}
+
+int modbus_mask_write_register(modbus_t *ctx,
+ int addr,
+ uint16_t and_mask,
+ uint16_t or_mask)
+{
+ int rc;
+ int req_length;
+ /* The request length can not exceed _MIN_REQ_LENGTH - 2 and 4 bytes to
+ * store the masks. The ugly substraction is there to remove the 'nb' value
+ * (2 bytes) which is not used. */
+ uint8_t req[_MIN_REQ_LENGTH + 2];
+
+ req_length = ctx->backend->build_request_basis(
+ ctx, MODBUS_FC_MASK_WRITE_REGISTER, addr, 0, req);
+
+ /* HACKISH, count is not used */
+ req_length -= 2;
+
+ req[req_length++] = and_mask >> 8;
+ req[req_length++] = and_mask & 0x00ff;
+ req[req_length++] = or_mask >> 8;
+ req[req_length++] = or_mask & 0x00ff;
+
+ rc = send_msg(ctx, req, req_length);
+ if (rc > 0) {
+ /* Used by write_bit and write_register */
+ uint8_t rsp[MAX_MESSAGE_LENGTH];
+
+ rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION);
+ if (rc == -1)
+ return -1;
+
+ rc = check_confirmation(ctx, req, rsp, rc);
+ }
+
+ return rc;
+}
+
+/* Write multiple registers from src array to remote device and read multiple
+ registers from remote device to dest array. */
+int modbus_write_and_read_registers(modbus_t *ctx,
+ int write_addr,
+ int write_nb,
+ const uint16_t *src,
+ int read_addr,
+ int read_nb,
+ uint16_t *dest)
+
+{
+ int rc;
+ int req_length;
+ int i;
+ int byte_count;
+ uint8_t req[MAX_MESSAGE_LENGTH];
+ uint8_t rsp[MAX_MESSAGE_LENGTH];
+
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (write_nb > MODBUS_MAX_WR_WRITE_REGISTERS) {
+ if (ctx->debug) {
+ fprintf(stderr,
+ "ERROR Too many registers to write (%d > %d)\n",
+ write_nb,
+ MODBUS_MAX_WR_WRITE_REGISTERS);
+ }
+ errno = EMBMDATA;
+ return -1;
+ }
+
+ if (read_nb > MODBUS_MAX_WR_READ_REGISTERS) {
+ if (ctx->debug) {
+ fprintf(stderr,
+ "ERROR Too many registers requested (%d > %d)\n",
+ read_nb,
+ MODBUS_MAX_WR_READ_REGISTERS);
+ }
+ errno = EMBMDATA;
+ return -1;
+ }
+ req_length = ctx->backend->build_request_basis(
+ ctx, MODBUS_FC_WRITE_AND_READ_REGISTERS, read_addr, read_nb, req);
+
+ req[req_length++] = write_addr >> 8;
+ req[req_length++] = write_addr & 0x00ff;
+ req[req_length++] = write_nb >> 8;
+ req[req_length++] = write_nb & 0x00ff;
+ byte_count = write_nb * 2;
+ req[req_length++] = byte_count;
+
+ for (i = 0; i < write_nb; i++) {
+ req[req_length++] = src[i] >> 8;
+ req[req_length++] = src[i] & 0x00FF;
+ }
+
+ rc = send_msg(ctx, req, req_length);
+ if (rc > 0) {
+ unsigned int offset;
+
+ rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION);
+ if (rc == -1)
+ return -1;
+
+ rc = check_confirmation(ctx, req, rsp, rc);
+ if (rc == -1)
+ return -1;
+
+ offset = ctx->backend->header_length;
+ for (i = 0; i < rc; i++) {
+ /* shift reg hi_byte to temp OR with lo_byte */
+ dest[i] = (rsp[offset + 2 + (i << 1)] << 8) | rsp[offset + 3 + (i << 1)];
+ }
+ }
+
+ return rc;
+}
+
+/* Send a request to get the slave ID of the device (only available in serial
+ communication). */
+int modbus_report_slave_id(modbus_t *ctx, int max_dest, uint8_t *dest)
+{
+ int rc;
+ int req_length;
+ uint8_t req[_MIN_REQ_LENGTH];
+
+ if (ctx == NULL || max_dest <= 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ req_length =
+ ctx->backend->build_request_basis(ctx, MODBUS_FC_REPORT_SLAVE_ID, 0, 0, req);
+
+ /* HACKISH, addr and count are not used */
+ req_length -= 4;
+
+ rc = send_msg(ctx, req, req_length);
+ if (rc > 0) {
+ int i;
+ unsigned int offset;
+ uint8_t rsp[MAX_MESSAGE_LENGTH];
+
+ rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION);
+ if (rc == -1)
+ return -1;
+
+ rc = check_confirmation(ctx, req, rsp, rc);
+ if (rc == -1)
+ return -1;
+
+ offset = ctx->backend->header_length + 2;
+
+ /* Byte count, slave id, run indicator status and
+ additional data. Truncate copy to max_dest. */
+ for (i = 0; i < rc && i < max_dest; i++) {
+ dest[i] = rsp[offset + i];
+ }
+ }
+
+ return rc;
+}
+
+void _modbus_init_common(modbus_t *ctx)
+{
+ /* Slave and socket are initialized to -1 */
+ ctx->slave = -1;
+ ctx->s = -1;
+
+ ctx->debug = FALSE;
+ ctx->error_recovery = MODBUS_ERROR_RECOVERY_NONE;
+ ctx->quirks = MODBUS_QUIRK_NONE;
+
+ ctx->response_timeout.tv_sec = 0;
+ ctx->response_timeout.tv_usec = _RESPONSE_TIMEOUT;
+
+ ctx->byte_timeout.tv_sec = 0;
+ ctx->byte_timeout.tv_usec = _BYTE_TIMEOUT;
+
+ ctx->indication_timeout.tv_sec = 0;
+ ctx->indication_timeout.tv_usec = 0;
+}
+
+/* Define the slave number */
+int modbus_set_slave(modbus_t *ctx, int slave)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return ctx->backend->set_slave(ctx, slave);
+}
+
+int modbus_get_slave(modbus_t *ctx)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return ctx->slave;
+}
+
+int modbus_set_error_recovery(modbus_t *ctx, modbus_error_recovery_mode error_recovery)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* The type of modbus_error_recovery_mode is unsigned enum */
+ ctx->error_recovery = (uint8_t) error_recovery;
+ return 0;
+}
+
+// FIXME Doesn't work under Windows RTU
+int modbus_set_socket(modbus_t *ctx, int s)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ ctx->s = s;
+ return 0;
+}
+
+int modbus_get_socket(modbus_t *ctx)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return ctx->s;
+}
+
+/* Get the timeout interval used to wait for a response */
+int modbus_get_response_timeout(modbus_t *ctx, uint32_t *to_sec, uint32_t *to_usec)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ *to_sec = ctx->response_timeout.tv_sec;
+ *to_usec = ctx->response_timeout.tv_usec;
+ return 0;
+}
+
+int modbus_set_response_timeout(modbus_t *ctx, uint32_t to_sec, uint32_t to_usec)
+{
+ if (ctx == NULL || (to_sec == 0 && to_usec == 0) || to_usec > 999999) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ ctx->response_timeout.tv_sec = to_sec;
+ ctx->response_timeout.tv_usec = to_usec;
+ return 0;
+}
+
+/* Get the timeout interval between two consecutive bytes of a message */
+int modbus_get_byte_timeout(modbus_t *ctx, uint32_t *to_sec, uint32_t *to_usec)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ *to_sec = ctx->byte_timeout.tv_sec;
+ *to_usec = ctx->byte_timeout.tv_usec;
+ return 0;
+}
+
+int modbus_set_byte_timeout(modbus_t *ctx, uint32_t to_sec, uint32_t to_usec)
+{
+ /* Byte timeout can be disabled when both values are zero */
+ if (ctx == NULL || to_usec > 999999) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ ctx->byte_timeout.tv_sec = to_sec;
+ ctx->byte_timeout.tv_usec = to_usec;
+ return 0;
+}
+
+/* Get the timeout interval used by the server to wait for an indication from a client
+ */
+int modbus_get_indication_timeout(modbus_t *ctx, uint32_t *to_sec, uint32_t *to_usec)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ *to_sec = ctx->indication_timeout.tv_sec;
+ *to_usec = ctx->indication_timeout.tv_usec;
+ return 0;
+}
+
+int modbus_set_indication_timeout(modbus_t *ctx, uint32_t to_sec, uint32_t to_usec)
+{
+ /* Indication timeout can be disabled when both values are zero */
+ if (ctx == NULL || to_usec > 999999) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ ctx->indication_timeout.tv_sec = to_sec;
+ ctx->indication_timeout.tv_usec = to_usec;
+ return 0;
+}
+
+int modbus_get_header_length(modbus_t *ctx)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return ctx->backend->header_length;
+}
+
+int modbus_enable_quirks(modbus_t *ctx, unsigned int quirks_mask)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* Enable quirks that have a true value at their index in the mask */
+ ctx->quirks |= quirks_mask;
+ return 0;
+}
+
+int modbus_disable_quirks(modbus_t *ctx, unsigned int quirks_mask)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* Disable quirks that have a true value at ther index in the mask */
+ ctx->quirks &= ~quirks_mask;
+ return 0;
+}
+
+int modbus_connect(modbus_t *ctx)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return ctx->backend->connect(ctx);
+}
+
+void modbus_close(modbus_t *ctx)
+{
+ if (ctx == NULL)
+ return;
+
+ ctx->backend->close(ctx);
+}
+
+void modbus_free(modbus_t *ctx)
+{
+ if (ctx == NULL)
+ return;
+
+ ctx->backend->free(ctx);
+}
+
+int modbus_set_debug(modbus_t *ctx, int flag)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ ctx->debug = flag;
+ return 0;
+}
+
+/* Allocates 4 arrays to store bits, input bits, registers and inputs
+ registers. The pointers are stored in modbus_mapping structure.
+
+ The modbus_mapping_new_start_address() function shall return the new allocated
+ structure if successful. Otherwise it shall return NULL and set errno to
+ ENOMEM. */
+modbus_mapping_t *modbus_mapping_new_start_address(unsigned int start_bits,
+ unsigned int nb_bits,
+ unsigned int start_input_bits,
+ unsigned int nb_input_bits,
+ unsigned int start_registers,
+ unsigned int nb_registers,
+ unsigned int start_input_registers,
+ unsigned int nb_input_registers)
+{
+ modbus_mapping_t *mb_mapping;
+
+ mb_mapping = (modbus_mapping_t *) malloc(sizeof(modbus_mapping_t));
+ if (mb_mapping == NULL) {
+ return NULL;
+ }
+
+ /* 0X */
+ mb_mapping->nb_bits = nb_bits;
+ mb_mapping->start_bits = start_bits;
+ if (nb_bits == 0) {
+ mb_mapping->tab_bits = NULL;
+ } else {
+ /* Negative number raises a POSIX error */
+ mb_mapping->tab_bits = (uint8_t *) malloc(nb_bits * sizeof(uint8_t));
+ if (mb_mapping->tab_bits == NULL) {
+ free(mb_mapping);
+ return NULL;
+ }
+ memset(mb_mapping->tab_bits, 0, nb_bits * sizeof(uint8_t));
+ }
+
+ /* 1X */
+ mb_mapping->nb_input_bits = nb_input_bits;
+ mb_mapping->start_input_bits = start_input_bits;
+ if (nb_input_bits == 0) {
+ mb_mapping->tab_input_bits = NULL;
+ } else {
+ mb_mapping->tab_input_bits = (uint8_t *) malloc(nb_input_bits * sizeof(uint8_t));
+ if (mb_mapping->tab_input_bits == NULL) {
+ free(mb_mapping->tab_bits);
+ free(mb_mapping);
+ return NULL;
+ }
+ memset(mb_mapping->tab_input_bits, 0, nb_input_bits * sizeof(uint8_t));
+ }
+
+ /* 4X */
+ mb_mapping->nb_registers = nb_registers;
+ mb_mapping->start_registers = start_registers;
+ if (nb_registers == 0) {
+ mb_mapping->tab_registers = NULL;
+ } else {
+ mb_mapping->tab_registers = (uint16_t *) malloc(nb_registers * sizeof(uint16_t));
+ if (mb_mapping->tab_registers == NULL) {
+ free(mb_mapping->tab_input_bits);
+ free(mb_mapping->tab_bits);
+ free(mb_mapping);
+ return NULL;
+ }
+ memset(mb_mapping->tab_registers, 0, nb_registers * sizeof(uint16_t));
+ }
+
+ /* 3X */
+ mb_mapping->nb_input_registers = nb_input_registers;
+ mb_mapping->start_input_registers = start_input_registers;
+ if (nb_input_registers == 0) {
+ mb_mapping->tab_input_registers = NULL;
+ } else {
+ mb_mapping->tab_input_registers =
+ (uint16_t *) malloc(nb_input_registers * sizeof(uint16_t));
+ if (mb_mapping->tab_input_registers == NULL) {
+ free(mb_mapping->tab_registers);
+ free(mb_mapping->tab_input_bits);
+ free(mb_mapping->tab_bits);
+ free(mb_mapping);
+ return NULL;
+ }
+ memset(mb_mapping->tab_input_registers, 0, nb_input_registers * sizeof(uint16_t));
+ }
+
+ return mb_mapping;
+}
+
+modbus_mapping_t *modbus_mapping_new(int nb_bits,
+ int nb_input_bits,
+ int nb_registers,
+ int nb_input_registers)
+{
+ return modbus_mapping_new_start_address(
+ 0, nb_bits, 0, nb_input_bits, 0, nb_registers, 0, nb_input_registers);
+}
+
+/* Frees the 4 arrays */
+void modbus_mapping_free(modbus_mapping_t *mb_mapping)
+{
+ if (mb_mapping == NULL) {
+ return;
+ }
+
+ free(mb_mapping->tab_input_registers);
+ free(mb_mapping->tab_registers);
+ free(mb_mapping->tab_input_bits);
+ free(mb_mapping->tab_bits);
+ free(mb_mapping);
+}
+
+#ifndef HAVE_STRLCPY
+/*
+ * Function strlcpy was originally developed by
+ * Todd C. Miller to simplify writing secure code.
+ * See ftp://ftp.openbsd.org/pub/OpenBSD/src/lib/libc/string/strlcpy.3
+ * for more information.
+ *
+ * Thank you Ulrich Drepper... not!
+ *
+ * Copy src to string dest of size dest_size. At most dest_size-1 characters
+ * will be copied. Always NUL terminates (unless dest_size == 0). Returns
+ * strlen(src); if retval >= dest_size, truncation occurred.
+ */
+size_t strlcpy(char *dest, const char *src, size_t dest_size)
+{
+ register char *d = dest;
+ register const char *s = src;
+ register size_t n = dest_size;
+
+ /* Copy as many bytes as will fit */
+ if (n != 0 && --n != 0) {
+ do {
+ if ((*d++ = *s++) == 0)
+ break;
+ } while (--n != 0);
+ }
+
+ /* Not enough room in dest, add NUL and traverse rest of src */
+ if (n == 0) {
+ if (dest_size != 0)
+ *d = '\0'; /* NUL-terminate dest */
+ while (*s++)
+ ;
+ }
+
+ return (s - src - 1); /* count does not include NUL */
+}
+#endif
diff --git a/applications/EmsShower/libmodbus/modbus.h b/applications/EmsShower/libmodbus/modbus.h
new file mode 100644
index 0000000..f235e50
--- /dev/null
+++ b/applications/EmsShower/libmodbus/modbus.h
@@ -0,0 +1,350 @@
+/*
+ * Copyright © Stéphane Raimbault
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef MODBUS_H
+#define MODBUS_H
+
+// clang-format off
+/* Add this for macros that defined unix flavor */
+#if (defined(__unix__) || defined(unix)) && !defined(USG)
+# include
+#endif
+
+#ifndef _MSC_VER
+# include
+#else
+# include "stdint.h"
+#endif
+
+#include "modbus-version.h"
+
+#if defined(_MSC_VER)
+# if defined(DLLBUILD)
+/* define DLLBUILD when building the DLL */
+# define MODBUS_API __declspec(dllexport)
+# else
+# define MODBUS_API __declspec(dllimport)
+# endif
+#else
+# define MODBUS_API
+#endif
+
+//添加这几行是因为使用源码编译,而不是使用dll/lib方式,
+#ifdef MODBUS_API
+#undef MODBUS_API
+#endif
+#define MODBUS_API
+//添加完成
+
+
+#ifdef __cplusplus
+# define MODBUS_BEGIN_DECLS extern "C" {
+# define MODBUS_END_DECLS }
+#else
+# define MODBUS_BEGIN_DECLS
+# define MODBUS_END_DECLS
+#endif
+// clang-format on
+
+MODBUS_BEGIN_DECLS
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+#ifndef OFF
+#define OFF 0
+#endif
+
+#ifndef ON
+#define ON 1
+#endif
+
+/* Modbus function codes */
+#define MODBUS_FC_READ_COILS 0x01
+#define MODBUS_FC_READ_DISCRETE_INPUTS 0x02
+#define MODBUS_FC_READ_HOLDING_REGISTERS 0x03
+#define MODBUS_FC_READ_INPUT_REGISTERS 0x04
+#define MODBUS_FC_WRITE_SINGLE_COIL 0x05
+#define MODBUS_FC_WRITE_SINGLE_REGISTER 0x06
+#define MODBUS_FC_READ_EXCEPTION_STATUS 0x07
+#define MODBUS_FC_WRITE_MULTIPLE_COILS 0x0F
+#define MODBUS_FC_WRITE_MULTIPLE_REGISTERS 0x10
+#define MODBUS_FC_REPORT_SLAVE_ID 0x11
+#define MODBUS_FC_MASK_WRITE_REGISTER 0x16
+#define MODBUS_FC_WRITE_AND_READ_REGISTERS 0x17
+
+#define MODBUS_BROADCAST_ADDRESS 0
+
+/* Modbus_Application_Protocol_V1_1b.pdf (chapter 6 section 1 page 12)
+ * Quantity of Coils to read (2 bytes): 1 to 2000 (0x7D0)
+ * (chapter 6 section 11 page 29)
+ * Quantity of Coils to write (2 bytes): 1 to 1968 (0x7B0)
+ */
+#define MODBUS_MAX_READ_BITS 2000
+#define MODBUS_MAX_WRITE_BITS 1968
+
+/* Modbus_Application_Protocol_V1_1b.pdf (chapter 6 section 3 page 15)
+ * Quantity of Registers to read (2 bytes): 1 to 125 (0x7D)
+ * (chapter 6 section 12 page 31)
+ * Quantity of Registers to write (2 bytes) 1 to 123 (0x7B)
+ * (chapter 6 section 17 page 38)
+ * Quantity of Registers to write in R/W registers (2 bytes) 1 to 121 (0x79)
+ */
+#define MODBUS_MAX_READ_REGISTERS 125
+#define MODBUS_MAX_WRITE_REGISTERS 123
+#define MODBUS_MAX_WR_WRITE_REGISTERS 121
+#define MODBUS_MAX_WR_READ_REGISTERS 125
+
+/* The size of the MODBUS PDU is limited by the size constraint inherited from
+ * the first MODBUS implementation on Serial Line network (max. RS485 ADU = 256
+ * bytes). Therefore, MODBUS PDU for serial line communication = 256 - Server
+ * address (1 byte) - CRC (2 bytes) = 253 bytes.
+ */
+#define MODBUS_MAX_PDU_LENGTH 253
+
+/* Consequently:
+ * - RTU MODBUS ADU = 253 bytes + Server address (1 byte) + CRC (2 bytes) = 256
+ * bytes.
+ * - TCP MODBUS ADU = 253 bytes + MBAP (7 bytes) = 260 bytes.
+ * so the maximum of both backend in 260 bytes. This size can used to allocate
+ * an array of bytes to store responses and it will be compatible with the two
+ * backends.
+ */
+#define MODBUS_MAX_ADU_LENGTH 260
+
+/* Random number to avoid errno conflicts */
+#define MODBUS_ENOBASE 112345678
+
+/* Protocol exceptions */
+enum
+{
+ MODBUS_EXCEPTION_ILLEGAL_FUNCTION = 0x01,
+ MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS,
+ MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE,
+ MODBUS_EXCEPTION_SLAVE_OR_SERVER_FAILURE,
+ MODBUS_EXCEPTION_ACKNOWLEDGE,
+ MODBUS_EXCEPTION_SLAVE_OR_SERVER_BUSY,
+ MODBUS_EXCEPTION_NEGATIVE_ACKNOWLEDGE,
+ MODBUS_EXCEPTION_MEMORY_PARITY,
+ MODBUS_EXCEPTION_NOT_DEFINED,
+ MODBUS_EXCEPTION_GATEWAY_PATH,
+ MODBUS_EXCEPTION_GATEWAY_TARGET,
+ MODBUS_EXCEPTION_MAX
+};
+
+#define EMBXILFUN (MODBUS_ENOBASE + MODBUS_EXCEPTION_ILLEGAL_FUNCTION)
+#define EMBXILADD (MODBUS_ENOBASE + MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS)
+#define EMBXILVAL (MODBUS_ENOBASE + MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE)
+#define EMBXSFAIL (MODBUS_ENOBASE + MODBUS_EXCEPTION_SLAVE_OR_SERVER_FAILURE)
+#define EMBXACK (MODBUS_ENOBASE + MODBUS_EXCEPTION_ACKNOWLEDGE)
+#define EMBXSBUSY (MODBUS_ENOBASE + MODBUS_EXCEPTION_SLAVE_OR_SERVER_BUSY)
+#define EMBXNACK (MODBUS_ENOBASE + MODBUS_EXCEPTION_NEGATIVE_ACKNOWLEDGE)
+#define EMBXMEMPAR (MODBUS_ENOBASE + MODBUS_EXCEPTION_MEMORY_PARITY)
+#define EMBXGPATH (MODBUS_ENOBASE + MODBUS_EXCEPTION_GATEWAY_PATH)
+#define EMBXGTAR (MODBUS_ENOBASE + MODBUS_EXCEPTION_GATEWAY_TARGET)
+
+/* Native libmodbus error codes */
+#define EMBBADCRC (EMBXGTAR + 1)
+#define EMBBADDATA (EMBXGTAR + 2)
+#define EMBBADEXC (EMBXGTAR + 3)
+#define EMBUNKEXC (EMBXGTAR + 4)
+#define EMBMDATA (EMBXGTAR + 5)
+#define EMBBADSLAVE (EMBXGTAR + 6)
+
+extern const unsigned int libmodbus_version_major;
+extern const unsigned int libmodbus_version_minor;
+extern const unsigned int libmodbus_version_micro;
+
+typedef struct _modbus modbus_t;
+
+/*! Memory layout in tab_xxx arrays is processor-endianness.
+ When receiving modbus data, it is converted to processor-endianness,
+ see read_registers().
+*/
+typedef struct _modbus_mapping_t
+{
+ int nb_bits;
+ int start_bits;
+ int nb_input_bits;
+ int start_input_bits;
+ int nb_input_registers;
+ int start_input_registers;
+ int nb_registers;
+ int start_registers;
+ uint8_t *tab_bits;
+ uint8_t *tab_input_bits;
+ uint16_t *tab_input_registers;
+ uint16_t *tab_registers;
+} modbus_mapping_t;
+
+typedef enum
+{
+ MODBUS_ERROR_RECOVERY_NONE = 0,
+ MODBUS_ERROR_RECOVERY_LINK = (1 << 1),
+ MODBUS_ERROR_RECOVERY_PROTOCOL = (1 << 2)
+} modbus_error_recovery_mode;
+
+typedef enum
+{
+ MODBUS_QUIRK_NONE = 0,
+ MODBUS_QUIRK_MAX_SLAVE = (1 << 1),
+ MODBUS_QUIRK_REPLY_TO_BROADCAST = (1 << 2),
+ MODBUS_QUIRK_ALL = 0xFF
+} modbus_quirks;
+
+MODBUS_API int modbus_set_slave(modbus_t *ctx, int slave);
+MODBUS_API int modbus_get_slave(modbus_t *ctx);
+MODBUS_API int modbus_set_error_recovery(modbus_t *ctx,
+ modbus_error_recovery_mode error_recovery);
+MODBUS_API int modbus_set_socket(modbus_t *ctx, int s);
+MODBUS_API int modbus_get_socket(modbus_t *ctx);
+
+MODBUS_API int
+modbus_get_response_timeout(modbus_t *ctx, uint32_t *to_sec, uint32_t *to_usec);
+MODBUS_API int
+modbus_set_response_timeout(modbus_t *ctx, uint32_t to_sec, uint32_t to_usec);
+
+MODBUS_API int
+modbus_get_byte_timeout(modbus_t *ctx, uint32_t *to_sec, uint32_t *to_usec);
+MODBUS_API int modbus_set_byte_timeout(modbus_t *ctx, uint32_t to_sec, uint32_t to_usec);
+
+MODBUS_API int
+modbus_get_indication_timeout(modbus_t *ctx, uint32_t *to_sec, uint32_t *to_usec);
+MODBUS_API int
+modbus_set_indication_timeout(modbus_t *ctx, uint32_t to_sec, uint32_t to_usec);
+
+MODBUS_API int modbus_get_header_length(modbus_t *ctx);
+
+MODBUS_API int modbus_connect(modbus_t *ctx);
+MODBUS_API void modbus_close(modbus_t *ctx);
+
+MODBUS_API void modbus_free(modbus_t *ctx);
+
+MODBUS_API int modbus_flush(modbus_t *ctx);
+MODBUS_API int modbus_set_debug(modbus_t *ctx, int flag);
+
+MODBUS_API const char *modbus_strerror(int errnum);
+
+MODBUS_API int modbus_read_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest);
+MODBUS_API int modbus_read_input_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest);
+MODBUS_API int modbus_read_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest);
+MODBUS_API int
+modbus_read_input_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest);
+MODBUS_API int modbus_write_bit(modbus_t *ctx, int coil_addr, int status);
+MODBUS_API int modbus_write_register(modbus_t *ctx, int reg_addr, const uint16_t value);
+MODBUS_API int modbus_write_bits(modbus_t *ctx, int addr, int nb, const uint8_t *data);
+MODBUS_API int
+modbus_write_registers(modbus_t *ctx, int addr, int nb, const uint16_t *data);
+MODBUS_API int
+modbus_mask_write_register(modbus_t *ctx, int addr, uint16_t and_mask, uint16_t or_mask);
+MODBUS_API int modbus_write_and_read_registers(modbus_t *ctx,
+ int write_addr,
+ int write_nb,
+ const uint16_t *src,
+ int read_addr,
+ int read_nb,
+ uint16_t *dest);
+MODBUS_API int modbus_report_slave_id(modbus_t *ctx, int max_dest, uint8_t *dest);
+
+MODBUS_API modbus_mapping_t *
+modbus_mapping_new_start_address(unsigned int start_bits,
+ unsigned int nb_bits,
+ unsigned int start_input_bits,
+ unsigned int nb_input_bits,
+ unsigned int start_registers,
+ unsigned int nb_registers,
+ unsigned int start_input_registers,
+ unsigned int nb_input_registers);
+
+MODBUS_API modbus_mapping_t *modbus_mapping_new(int nb_bits,
+ int nb_input_bits,
+ int nb_registers,
+ int nb_input_registers);
+MODBUS_API void modbus_mapping_free(modbus_mapping_t *mb_mapping);
+
+MODBUS_API int
+modbus_send_raw_request(modbus_t *ctx, const uint8_t *raw_req, int raw_req_length);
+
+MODBUS_API int modbus_send_raw_request_tid(modbus_t *ctx,
+ const uint8_t *raw_req,
+ int raw_req_length,
+ int tid);
+
+MODBUS_API int modbus_receive(modbus_t *ctx, uint8_t *req);
+
+MODBUS_API int modbus_receive_confirmation(modbus_t *ctx, uint8_t *rsp);
+
+MODBUS_API int modbus_reply(modbus_t *ctx,
+ const uint8_t *req,
+ int req_length,
+ modbus_mapping_t *mb_mapping);
+MODBUS_API int
+modbus_reply_exception(modbus_t *ctx, const uint8_t *req, unsigned int exception_code);
+MODBUS_API int modbus_enable_quirks(modbus_t *ctx, unsigned int quirks_mask);
+MODBUS_API int modbus_disable_quirks(modbus_t *ctx, unsigned int quirks_mask);
+
+/**
+ * UTILS FUNCTIONS
+ **/
+
+#define MODBUS_GET_HIGH_BYTE(data) (((data) >> 8) & 0xFF)
+#define MODBUS_GET_LOW_BYTE(data) ((data) & 0xFF)
+#define MODBUS_GET_INT64_FROM_INT16(tab_int16, index) \
+ (((int64_t) tab_int16[(index)] << 48) | ((int64_t) tab_int16[(index) + 1] << 32) | \
+ ((int64_t) tab_int16[(index) + 2] << 16) | (int64_t) tab_int16[(index) + 3])
+#define MODBUS_GET_INT32_FROM_INT16(tab_int16, index) \
+ (((int32_t) tab_int16[(index)] << 16) | (int32_t) tab_int16[(index) + 1])
+#define MODBUS_GET_INT16_FROM_INT8(tab_int8, index) \
+ (((int16_t) tab_int8[(index)] << 8) | (int16_t) tab_int8[(index) + 1])
+#define MODBUS_SET_INT16_TO_INT8(tab_int8, index, value) \
+ do { \
+ ((int8_t *) (tab_int8))[(index)] = (int8_t) ((value) >> 8); \
+ ((int8_t *) (tab_int8))[(index) + 1] = (int8_t) (value); \
+ } while (0)
+#define MODBUS_SET_INT32_TO_INT16(tab_int16, index, value) \
+ do { \
+ ((int16_t *) (tab_int16))[(index)] = (int16_t) ((value) >> 16); \
+ ((int16_t *) (tab_int16))[(index) + 1] = (int16_t) (value); \
+ } while (0)
+#define MODBUS_SET_INT64_TO_INT16(tab_int16, index, value) \
+ do { \
+ ((int16_t *) (tab_int16))[(index)] = (int16_t) ((value) >> 48); \
+ ((int16_t *) (tab_int16))[(index) + 1] = (int16_t) ((value) >> 32); \
+ ((int16_t *) (tab_int16))[(index) + 2] = (int16_t) ((value) >> 16); \
+ ((int16_t *) (tab_int16))[(index) + 3] = (int16_t) (value); \
+ } while (0)
+
+MODBUS_API void modbus_set_bits_from_byte(uint8_t *dest, int idx, const uint8_t value);
+MODBUS_API void modbus_set_bits_from_bytes(uint8_t *dest,
+ int idx,
+ unsigned int nb_bits,
+ const uint8_t *tab_byte);
+MODBUS_API uint8_t modbus_get_byte_from_bits(const uint8_t *src,
+ int idx,
+ unsigned int nb_bits);
+MODBUS_API float modbus_get_float(const uint16_t *src);
+MODBUS_API float modbus_get_float_abcd(const uint16_t *src);
+MODBUS_API float modbus_get_float_dcba(const uint16_t *src);
+MODBUS_API float modbus_get_float_badc(const uint16_t *src);
+MODBUS_API float modbus_get_float_cdab(const uint16_t *src);
+
+MODBUS_API void modbus_set_float(float f, uint16_t *dest);
+MODBUS_API void modbus_set_float_abcd(float f, uint16_t *dest);
+MODBUS_API void modbus_set_float_dcba(float f, uint16_t *dest);
+MODBUS_API void modbus_set_float_badc(float f, uint16_t *dest);
+MODBUS_API void modbus_set_float_cdab(float f, uint16_t *dest);
+
+#include "modbus-rtu.h"
+#include "modbus-tcp.h"
+
+MODBUS_END_DECLS
+
+#endif /* MODBUS_H */
diff --git a/applications/EmsShower/main.cpp b/applications/EmsShower/main.cpp
new file mode 100644
index 0000000..6b8850a
--- /dev/null
+++ b/applications/EmsShower/main.cpp
@@ -0,0 +1,23 @@
+#include "mainwindow.h"
+
+#include
+#include
+#include
+
+int main(int argc, char *argv[])
+{
+ QApplication a(argc, argv);
+
+ QTranslator translator;
+ const QStringList uiLanguages = QLocale::system().uiLanguages();
+ for (const QString &locale : uiLanguages) {
+ const QString baseName = "EmsShower_" + QLocale(locale).name();
+ if (translator.load(":/i18n/" + baseName)) {
+ a.installTranslator(&translator);
+ break;
+ }
+ }
+ MainWindow w;
+ w.show();
+ return a.exec();
+}
diff --git a/applications/EmsShower/mainwindow.cpp b/applications/EmsShower/mainwindow.cpp
new file mode 100644
index 0000000..2c8775b
--- /dev/null
+++ b/applications/EmsShower/mainwindow.cpp
@@ -0,0 +1,359 @@
+#include "mainwindow.h"
+#include "ui_mainwindow.h"
+
+#include
+#include
+#include
+#include
+
+#include "libmodbus/modbus.h"
+
+#define _DEBUG_VSPD_
+
+#define TH08D_TEMPERATURE_EQUIPMENT_81_00_09 40000
+#define TH08D_TEMPERATURE_EQUIPMENT_81_09_06 40009
+
+// 处理数组的槽函数
+void DecodeWorker::processArray(const QVector& array,int slave_id,int start_addr,int quantity,DeviceData* pData)
+{
+ switch (start_addr)
+ {
+ case TH08D_TEMPERATURE_EQUIPMENT_81_00_09:
+ {
+ assert(pData->m_device_type == 81);
+
+ TemperatureData* pTemperature = (TemperatureData*)pData;
+
+ uint16_t value;
+ int index = 0;
+ pTemperature->m_device_online_state = array[index++]; //设备通讯状态
+ pTemperature->TempValue = (float)array[index++] * 1.0f / 10.f; //温度
+ pTemperature->HumidityValue = (float)array[index++] * 1.0f / 10.f; //湿度
+ pTemperature->DewPointValue = (float)array[index++] * 1.0f; //露点
+ value = array[index++]; //跳过温度偏移量
+ value = array[index++]; //跳过湿度偏移量
+ pTemperature->DO= array[index++]; //DO
+ pTemperature->DI1 = array[index++]; //DI1
+ pTemperature->DI2 = array[index++]; //DI2
+ pTemperature->bDecodeAlarm = false;
+ pTemperature->bDecodeTemp = true;
+ }
+ break;
+
+ case TH08D_TEMPERATURE_EQUIPMENT_81_09_06:
+ {
+ assert(pData->m_device_type == 81);
+
+ TemperatureData* pTemperature = (TemperatureData*)pData;
+ pTemperature->bDecodeAlarm = true;
+ pTemperature->bDecodeTemp = false;
+
+ uint16_t value;
+ int index = 0;
+ float temp_threshold = (float)array[index++] * 1.0f / 10.f; //温度阈值
+ float temp_offset = (float)array[index++] * 1.0f / 10.f; //温控偏移量
+ value = array[index++]; //跳过设备状态
+ pTemperature->TempHighAlarm = array[index++]; //高温告警
+ pTemperature->HumidityHighAlarm = array[index++]; //高湿度告警
+ pTemperature->TempLowAlarm = array[index++]; //低温告警
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ //下面是模拟发送进度
+ //for (int i = 0; i < array.size(); ++i)
+ //{
+ // qDebug() << "Worker处理索引:" << i << "值:" << array[i]
+ // << "线程:" << QThread::currentThread();
+ // //QThread::msleep(100); // 模拟耗时处理
+ // emit progress(i);
+ //}
+
+ qDebug() << "Worker thread finished";
+ emit finished();
+}
+
+MainWindow::MainWindow(QWidget *parent)
+ : QMainWindow(parent)
+ , ui(new Ui::MainWindow)
+ ,m_pModbus(nullptr)
+ ,m_pSettings(nullptr)
+{
+ ui->setupUi(this);
+
+ m_version= "1.0.0";
+ m_modbus_type = 1;
+ m_modbus_ip = "127.0.0.1";
+ m_modbus_port = 502;
+ m_modbus_com = "COM1";
+ m_modbus_baund = 9600;
+ m_modbus_data = 1;
+ m_modbus_parity = 0;
+ m_modbus_stop = 1;
+ m_modbus_slave_id = 1;
+
+ InitializeUI();
+ InitializeModbus();
+}
+
+MainWindow::~MainWindow()
+{
+ delete ui;
+
+ if (m_pModbus)
+ {
+ modbus_close(m_pModbus);
+ modbus_free(m_pModbus);
+ m_pModbus = nullptr;
+ }
+
+ for (SlaveItem* ptr : m_SlaveData)
+ {
+ delete ptr;
+ ptr = nullptr;
+ }
+ m_SlaveData.clear();
+}
+
+void MainWindow::getConfiguration(QString iniFilePath)
+{
+ m_pSettings = new QSettings(iniFilePath, QSettings::IniFormat);
+
+ m_version = m_pSettings->value("version/ver").toString();
+ m_modbus_type = m_pSettings->value("modbus/type").toInt();
+
+ m_modbus_ip = m_pSettings->value("modbus/ip").toString();
+ m_modbus_port = m_pSettings->value("modbus/port").toInt();
+
+ m_modbus_com = m_pSettings->value("modbus/com").toString();
+ m_modbus_baund = m_pSettings->value("modbus/baund").toInt();
+ m_modbus_data = m_pSettings->value("modbus/data").toInt();
+ m_modbus_parity = m_pSettings->value("modbus/parity").toInt();
+ m_modbus_stop = m_pSettings->value("modbus/stop").toInt();
+
+ m_modbus_slave_id = m_pSettings->value("slaves/slave_id").toInt();
+
+ QString app = QString("slave_%1/function_code_counts").arg(m_modbus_slave_id);
+
+ int functions_count = m_pSettings->value(app).toInt();
+ for(int i=0; ivalue(app2 + "/equipment_code").toInt();
+ equipmentCode = equipmentCode << 9;
+ equipmentCode &= 0xFE00; // 二进制掩码: 1111 1110 0000 0000(保留高7位)
+
+ QStringList sn_list = m_pSettings->value(app2 + "/serial_number").toString().split(":", Qt::SkipEmptyParts);
+ QStringList quantity_list = m_pSettings->value(app2 + "/read_quantity").toString().split(":", Qt::SkipEmptyParts);
+
+ assert(sn_list.size() == quantity_list.size());
+
+ int j = 0;
+ foreach(const QString sn, sn_list)
+ {
+ SlaveItem* pSlaveData = new SlaveItem();
+ pSlaveData->id = m_modbus_slave_id;
+ pSlaveData->function_code = m_pSettings->value(app2 + "/function_code").toInt();
+ pSlaveData->interval = m_pSettings->value(app2 + "/interval").toInt();
+
+ unsigned short serialNumber = sn.toInt();
+
+ serialNumber &= 0x01FF; // 二进制掩码: 0000 0001 1111 1111(保留低9位)
+
+ // 组合操作:高7位左移9位,低9位直接填充
+ unsigned short address = pSlaveData->start_address = (equipmentCode ) | serialNumber; // 关键位操作
+
+ pSlaveData->start_address = address;
+ pSlaveData->quantity = quantity_list.at(j).toInt();
+ m_SlaveData.push_back(pSlaveData);
+ j++;
+ }
+#else
+ SlaveItem* pSlaveData = new SlaveItem();
+ pSlaveData->id = m_modbus_slave_id;
+ pSlaveData->function_code = m_pSettings->value(app2 + "/function_code").toInt();
+ pSlaveData->interval = m_pSettings->value(app2 + "/interval").toInt();
+ pSlaveData->quantity = m_pSettings->value(app2 + "/quantity").toInt();
+ pSlaveData->start_address = m_pSettings->value(app2 + "/start_addr").toInt();
+ m_SlaveData.push_back(pSlaveData);
+#endif
+ }
+}
+
+bool MainWindow::InitializeUI()
+{
+ return false;
+}
+
+bool MainWindow::InitializeModbus()
+{
+ QString appDir = QCoreApplication::applicationDirPath();
+ QString iniFilePath = appDir + QString::fromStdString("/emsshower.ini");
+
+ getConfiguration(iniFilePath);
+
+ switch (m_modbus_type)
+ {
+ case 0:
+ return InitializeTcp();
+ break;
+
+ default:
+ case 1:
+ return InitializeRtu();
+ break;
+ }
+}
+
+bool MainWindow::InitializeRtu()
+{
+ if(m_pModbus)
+ {
+ modbus_close(m_pModbus);
+ m_pModbus = nullptr;
+ }
+
+ std::string com = std::string("\\\\.\\") + m_modbus_com.toStdString();
+
+ m_pModbus = modbus_new_rtu(com.c_str(), m_modbus_baund, 'N', m_modbus_data, m_modbus_stop);
+
+ int slave_id = m_modbus_slave_id;
+ //modbus_set_debug(m_pModbus, true);
+ modbus_set_slave(m_pModbus, slave_id); //设置modbus从机地址
+ modbus_rtu_set_serial_mode(m_pModbus, MODBUS_RTU_RS485);
+
+ int rc = modbus_connect(m_pModbus);
+ if (rc == -1)
+ return false;
+
+ struct timeval t;
+ t.tv_sec=0;
+ t.tv_usec=1000000; //设置modbus超时时间为1000毫秒
+ modbus_set_response_timeout(m_pModbus, t.tv_usec,t.tv_usec);
+
+ return true;
+}
+
+bool MainWindow::InitializeTcp()
+{
+ if(m_pModbus)
+ {
+ modbus_close(m_pModbus);
+ m_pModbus = nullptr;
+ }
+
+ std::string ip = m_modbus_ip.toStdString();
+ int port = m_modbus_port;
+ m_pModbus = modbus_new_tcp(ip.c_str(), port); //由于是tcp client连接,在同一个程序中相同的端口可以连接多次。
+ if (m_pModbus == nullptr)
+ {
+ return false;
+ }
+
+ int slave_id = m_modbus_slave_id;
+ modbus_set_slave(m_pModbus, slave_id); //从机地址
+
+ int rc = modbus_connect(m_pModbus);
+ if (rc == -1)
+ return false;
+
+ struct timeval t;
+ t.tv_sec=0;
+ t.tv_usec=1000000; //设置modbus超时时间为1000毫秒
+ modbus_set_response_timeout(m_pModbus, t.tv_usec,t.tv_usec);
+
+ return true;
+}
+
+bool MainWindow::readRegister(int addr,int nb,uint16_t* dest)
+{
+ for (auto it = m_SlaveData.begin(); it != m_SlaveData.end(); ++it)
+ {
+ SlaveItem* pItem = (SlaveItem*)(*it);
+
+ uint16_t* tab_reg = new uint16_t[pItem->quantity];
+ int regs = modbus_read_registers(m_pModbus, pItem->start_address, pItem->quantity, tab_reg);
+
+ QVector registers;
+ for (int i = 0; i < regs; ++i)
+ {
+ registers.push_back(tab_reg[i]);
+ }
+
+ delete []tab_reg;
+
+ DeviceData* pDevice = nullptr;
+
+ if (pItem->start_address == TH08D_TEMPERATURE_EQUIPMENT_81_00_09
+ || pItem->start_address == TH08D_TEMPERATURE_EQUIPMENT_81_09_06)
+ pDevice = new TemperatureData();
+
+ startAsyncProcess(registers,pItem->id,pItem->start_address, pItem->quantity, pDevice);
+
+ if (pItem->start_address == TH08D_TEMPERATURE_EQUIPMENT_81_00_09
+ || pItem->start_address == TH08D_TEMPERATURE_EQUIPMENT_81_09_06)
+ {
+ TemperatureData* pData = (TemperatureData*)pDevice;
+ assert(pData);
+ if (pData->bDecodeTemp)
+ {
+ ui->txt_content_1->append(QString(tr("温度:%1\n湿度: %2\n露点: %3\n")).arg(pData->TempValue).arg(pData->HumidityValue).arg(pData->DewPointValue));
+ }
+ if (pData->bDecodeAlarm)
+ {
+ ui->txt_content_1->append(QString(tr("高温告警:%1\n低温告警: %2\n湿度告警: %3\n")).arg(pData->TempHighAlarm).arg(pData->TempLowAlarm).arg(pData->HumidityHighAlarm));
+ }
+ }
+
+ qDebug() << "同步读取地址" << pItem->start_address;
+ }
+ return true;
+}
+
+void MainWindow::startAsyncProcess(const QVector& array,int slave_id,int start_addr,int quantity, DeviceData* pDevice)
+{
+ QThread* thread = new QThread;
+ DecodeWorker* worker = new DecodeWorker;
+ worker->moveToThread(thread);
+
+ worker->setParameters(array, slave_id, start_addr, quantity, pDevice);
+
+ // 连接线程启动信号和槽函数
+ connect(this, &MainWindow::startProcessing, worker, &DecodeWorker::processArray);
+
+ // 工作完成后退出线程
+ connect(worker, &DecodeWorker::finished, thread, &QThread::quit);
+ connect(worker, &DecodeWorker::finished, worker, &DecodeWorker::deleteLater);
+ connect(thread, &QThread::finished, thread, &QThread::deleteLater);
+
+ //// 可选:连接进度信号更新UI
+ //connect(worker, &DecodeWorker::progress, this, [](int index)
+ //{
+ // qDebug() << "进度更新,当前处理到索引:" << index;
+ //});
+
+ thread->start();
+
+ QEventLoop loop;
+ QObject::connect(worker, &DecodeWorker::finished, &loop, &QEventLoop::quit);
+
+ // 发射信号传递参数
+ emit startProcessing(array, slave_id, start_addr, quantity, pDevice);
+
+ // 阻塞,等待线程结束
+ loop.exec();
+
+ qDebug() << "同步读取完成";
+}
+
+
+void MainWindow::on_btn_read_register_clicked()
+{
+ readRegister(0,0,0);
+}
+
diff --git a/applications/EmsShower/mainwindow.h b/applications/EmsShower/mainwindow.h
new file mode 100644
index 0000000..0e45dc8
--- /dev/null
+++ b/applications/EmsShower/mainwindow.h
@@ -0,0 +1,112 @@
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include
+#include
+
+#include "libmodbus/modbus.h"
+#include "slave_define.h"
+
+QT_BEGIN_NAMESPACE
+namespace Ui
+{
+class MainWindow;
+}
+QT_END_NAMESPACE
+
+typedef enum _tagModbusMode : int
+{
+ modeRtu = 0,
+ modeTcp = 1,
+} ModbusMode;
+
+//这是解码类,针对协议进行解码
+class DecodeWorker : public QObject
+{
+ Q_OBJECT
+public:
+ DecodeWorker(QObject* parent = nullptr) : QObject(parent) {}
+ void setParameters(const QVector& array, int slave_id, int start_addr, int quantity, DeviceData* pDevice)
+ {
+ this->array = array;
+ this->slave_id = slave_id;
+ this->start_addr = start_addr;
+ this->quantity = quantity;
+ this->pDevice = pDevice;
+ }
+
+private:
+ QVector array;
+ int slave_id;
+ int start_addr;
+ int quantity;
+ DeviceData* pDevice;
+
+signals:
+ // 处理进度信号
+ void progress(int index);
+ // 完成处理信号
+ void finished();
+
+public slots:
+ // 处理数组的槽函数
+ // 根据从机id、开始地址、寄存器数量三个变量进行辨别和解码处理
+ void processArray(const QVector& array,int slave_id,int start_addr,int quantity,DeviceData* pData);
+};
+
+//处理主界面
+class MainWindow : public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ MainWindow(QWidget *parent = nullptr);
+ ~MainWindow();
+
+
+protected:
+ void getConfiguration(QString iniFilePath);
+ bool InitializeUI();
+ bool InitializeModbus();
+ bool InitializeRtu();
+ bool InitializeTcp();
+
+ bool readRegister(int addr,int nb,uint16_t* dest);
+private:
+ Ui::MainWindow *ui;
+
+protected:
+ modbus_t *m_pModbus;
+ SlaveData m_SlaveData;
+
+protected:
+ QString m_version;
+ int m_modbus_type;
+ QString m_modbus_ip;
+ int m_modbus_port;
+ QString m_modbus_com;
+ int m_modbus_baund;
+ int m_modbus_data;
+ int m_modbus_parity;
+ int m_modbus_stop;
+ int m_modbus_slave_id;
+ QSettings* m_pSettings;
+
+
+protected:
+ //异步处理数据
+ void startAsyncProcess(const QVector& array,int slave_id,int start_addr,int quantity,DeviceData* pData);
+
+private slots:
+ void on_btn_read_register_clicked();
+
+signals:
+ // 处理进度信号
+ void progress(int index);
+ // 完成处理信号
+ void finished();
+
+signals:
+ void startProcessing(const QVector& array, int slave_id, int start_addr, int quantity, DeviceData* pData);
+};
+#endif // MAINWINDOW_H
diff --git a/applications/EmsShower/mainwindow.ui b/applications/EmsShower/mainwindow.ui
new file mode 100644
index 0000000..95d49cc
--- /dev/null
+++ b/applications/EmsShower/mainwindow.ui
@@ -0,0 +1,55 @@
+
+
+ MainWindow
+
+
+
+ 0
+ 0
+ 800
+ 600
+
+
+
+ MainWindow
+
+
+
+
+
+ 50
+ 20
+ 93
+ 29
+
+
+
+ read
+
+
+
+
+
+ 60
+ 100
+ 331
+ 331
+
+
+
+
+
+
+ 410
+ 100
+ 341
+ 331
+
+
+
+
+
+
+
+
+
diff --git a/applications/EmsShower/slave_define.h b/applications/EmsShower/slave_define.h
new file mode 100644
index 0000000..2e27f36
--- /dev/null
+++ b/applications/EmsShower/slave_define.h
@@ -0,0 +1,59 @@
+#ifndef SLAVE_DEFINE_H
+#define SLAVE_DEFINE_H
+
+#include
+
+typedef struct _tagSlave
+{
+ int id;
+ int function_code;
+ int start_address;
+ int quantity;
+ int interval;
+} SlaveItem;
+
+typedef std::vector SlaveData;
+
+class DeviceData
+{
+public:
+ DeviceData()
+ :m_device_type(-1),m_device_online_state(0)
+ {}
+
+ virtual ~DeviceData()
+ {}
+
+public:
+ int m_device_type;
+ int m_device_online_state;
+};
+
+
+class TemperatureData : public DeviceData
+{
+public:
+ TemperatureData()
+ {
+ m_device_type = 81;
+ bDecodeAlarm = false;
+ bDecodeTemp = false;
+ }
+
+ virtual ~TemperatureData() {}
+
+public:
+ bool bDecodeAlarm;
+ bool bDecodeTemp;
+ float TempValue;
+ float HumidityValue;
+ float DewPointValue;
+ int DO;
+ int DI1;
+ int DI2;
+ int TempHighAlarm;
+ int HumidityHighAlarm;
+ int TempLowAlarm;
+};
+
+#endif // SLAVE_DEFINE_H
diff --git a/applications/emsConfigurer/newdeviceformdialog.cpp b/applications/emsConfigurer/newdeviceformdialog.cpp
new file mode 100644
index 0000000..cb7a8d6
--- /dev/null
+++ b/applications/emsConfigurer/newdeviceformdialog.cpp
@@ -0,0 +1,297 @@
+#include "newdeviceformdialog.h"
+#include "ui_newdeviceformdialog.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include "frame_define.h"
+#include "globalparameters.h"
+#include "kutilities.h"
+
+
+NewDeviceFormDialog::NewDeviceFormDialog(QWidget *parent)
+ : QDialog(parent)
+ , ui(new Ui::NewDeviceFormDialog)
+{
+ ui->setupUi(this);
+
+ InitializeUi();
+
+ connect(ui->pb_OK, &QPushButton::clicked, this, &NewDeviceFormDialog::onOkClicked);
+ connect(ui->pb_Cancel, &QPushButton::clicked, this, &NewDeviceFormDialog::onCancelClicked);
+ connect(ui->pb_Import, &QPushButton::clicked, this, &NewDeviceFormDialog::browseFile);
+ connect(ui->cb_device, SIGNAL(currentTextChanged(const QString &)),this, SLOT(onComboBoxTextChanged(const QString &)));
+}
+
+NewDeviceFormDialog::~NewDeviceFormDialog()
+{
+ // 关闭数据库
+ QSqlDatabase::database().close();
+ delete ui;
+}
+
+void NewDeviceFormDialog::onOkClicked()
+{
+
+}
+
+void NewDeviceFormDialog::onCancelClicked()
+{
+}
+
+bool NewDeviceFormDialog::openDatabase(const QString &dbName)
+{
+ // 创建一个 SQLite 数据库连接
+ QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
+
+ // 设置数据库的文件路径
+ db.setDatabaseName(dbName);
+
+ // 尝试打开数据库
+ if (!db.open())
+ {
+ // 如果打开失败,输出错误信息
+ hloge("Error: Failed to open database! %s",db.lastError().text().toStdString().c_str());
+ return false;
+ }
+
+ hlogd("Database opened successfully!");
+
+ return true;
+}
+
+void NewDeviceFormDialog::browseFile()
+{
+ //首先断开连接
+ disconnect(ui->cb_device, SIGNAL(currentTextChanged(const QString &)),this, SLOT(onComboBoxTextChanged(const QString &)));
+
+ // 打开文件对话框,选择文件路径
+ QString filePath = QFileDialog::getOpenFileName(this, tr("Select Configuration File"), "", tr("Database Files (*.db);;All Files (*.*)"));
+
+ // 如果用户选择了文件,更新 QLineEdit 显示路径
+ if (!filePath.isEmpty())
+ {
+ ui->lineEdit->setText(filePath);
+ qDebug() << "Selected file path: " << filePath;
+
+ openDatabase(filePath);
+
+ QSqlQuery query;
+ vecCfgTab_SmartDeviceType SmartDeviceTypes;
+
+ // 查询数据
+ if (!query.exec("SELECT DISTINCT Description FROM CfgTab_SmartDeviceType ORDER BY TypeID ASC"))
+ {
+ hloge("Error: Failed to select data! [%s]",query.lastError().text().toStdString().c_str());
+ }
+ else
+ {
+ QVector devs;
+ while (query.next())
+ {
+ QString s = query.value(0).toString();
+ devs.push_back(s);
+ }
+
+ foreach (QString device, devs)
+ {
+ QString sql = QString("SELECT * FROM CfgTab_SmartDeviceType where Description = '%1' ORDER BY ProtocolVersion desc LIMIT 1").arg(device);
+ if (!query.exec(sql))
+ {
+ hloge("Error: Failed to select data! [%s]",query.lastError().text().toStdString().c_str());
+ }
+ else
+ {
+ // 获取列名到 QSqlRecord
+ QSqlRecord record = query.record();
+ while (query.next())
+ {
+ CfgTabSmartDeviceTypeItem smartDeviceTypesItem;
+ smartDeviceTypesItem.TypeID = query.value(record.indexOf("TypeID")).toInt();
+ smartDeviceTypesItem.Description = query.value(record.indexOf("Description")).toString().toStdString();
+ smartDeviceTypesItem.ClassType = query.value(record.indexOf("ClassType")).toInt();
+ smartDeviceTypesItem.PortConfig = query.value(record.indexOf("PortConfig")).toInt();
+ smartDeviceTypesItem.InterfaceType = query.value(record.indexOf("InterfaceType")).toInt();
+ smartDeviceTypesItem.StationType = query.value(record.indexOf("StationType")).toInt();
+ smartDeviceTypesItem.EquipmentCode = query.value(record.indexOf("EquipmentCode")).toInt();
+ smartDeviceTypesItem.EquipmentModel = query.value(record.indexOf("EquipmentModel")).toString().toStdString();
+ smartDeviceTypesItem.Manufacturer = query.value(record.indexOf("Manufacturer")).toString().toStdString();
+ smartDeviceTypesItem.ProtocolVersion = query.value(record.indexOf("ProtocolVersion")).toString().toStdString();
+ smartDeviceTypesItem.SpecialOption = query.value(record.indexOf("SpecialOption")).toString().toStdString();
+ smartDeviceTypesItem.Notes = query.value(record.indexOf("Notes")).toString().toStdString();
+
+ qDebug() << "Type ID:" << smartDeviceTypesItem.TypeID << ", Description:" << smartDeviceTypesItem.Description.c_str() << ", ProtocolVersion:" << smartDeviceTypesItem.ProtocolVersion.c_str();
+
+ SmartDeviceTypes.emplace_back(smartDeviceTypesItem);
+ }
+ }
+ }
+ }
+
+ //设置串口模式
+ ui->cb_device->setView(new QListView(this));
+ QStandardItemModel* model_baund = new QStandardItemModel(this);
+
+ for (auto it = SmartDeviceTypes.begin(); it != SmartDeviceTypes.end(); it++)
+ {
+ int id = it->TypeID;
+ QString name = QString::fromStdString(it->Description);
+ QString a = QString("%1:%2:%3").arg(id).arg(name).arg(QString::fromStdString(it->ProtocolVersion));
+
+ QStandardItem* item = new QStandardItem(a);
+ item->setData(id);
+ model_baund->appendRow(item);
+ }
+
+ ui->cb_device->setModel(model_baund);
+ ui->cb_device->setCurrentIndex(2);
+ }
+
+ //重新连接槽函数
+ connect(ui->cb_device, SIGNAL(currentTextChanged(const QString &)),this, SLOT(onComboBoxTextChanged(const QString &)));
+}
+
+void NewDeviceFormDialog::onComboBoxTextChanged(const QString &text)
+{
+ qDebug() << "ComboBox text changed to" << text;
+}
+
+void NewDeviceFormDialog::InitializeUi()
+{
+ ui->slave_id->setText("1");
+
+ //设置奇偶校验
+ ui->cb_parity->setView(new QListView(this));
+
+ QStandardItemModel* model_parity = new QStandardItemModel(this);
+
+ vecParityItemData vparity;
+ vparity.push_back(std::make_pair(EMS_PARITY_NONE,tr("None")));
+ vparity.push_back(std::make_pair(EMS_PARITY_ODD,tr("Odd")));
+ vparity.push_back(std::make_pair(EMS_PARITY_EVEN,tr("Even")));
+
+ for (auto it = vparity.begin(); it != vparity.end(); it++)
+ {
+ int id = it->first;
+ QString name = it->second;
+
+ QStandardItem* item = new QStandardItem(name);
+ //QVariant var = QVariant::fromValue(data);
+ item->setData(id);
+ model_parity->appendRow(item);
+ }
+
+ ui->cb_parity->setModel(model_parity);
+
+ //设置数据位
+ ui->cb_data->setView(new QListView(this));
+ QStandardItemModel* model_databits = new QStandardItemModel(this);
+ vecDataBitsItemData vdatabits;
+ vdatabits.push_back(std::make_pair(EMS_DATA_8_BITS,tr("8 Data bits")));
+ vdatabits.push_back(std::make_pair(EMS_DATA_7_BITS,tr("7 Data bits")));
+
+ for (auto it = vdatabits.begin(); it != vdatabits.end(); it++)
+ {
+ int id = it->first;
+ QString name = it->second;
+
+ QStandardItem* item = new QStandardItem(name);
+ item->setData(id);
+ model_databits->appendRow(item);
+ }
+
+ ui->cb_data->setModel(model_databits);
+
+ //设置数据位
+ ui->cb_stop->setView(new QListView(this));
+ QStandardItemModel* model_stopbits = new QStandardItemModel(this);
+ vecDataBitsItemData vstopbits;
+ vstopbits.push_back(std::make_pair(EMS_1_STOP_BITS,tr("1 Stop bit")));
+ vstopbits.push_back(std::make_pair(EMS_2_STOP_BITS,tr("2 Stop bits")));
+
+ for (auto it = vstopbits.begin(); it != vstopbits.end(); it++)
+ {
+ int id = it->first;
+ QString name = it->second;
+
+ QStandardItem* item = new QStandardItem(name);
+ item->setData(id);
+ model_stopbits->appendRow(item);
+ }
+
+ ui->cb_stop->setModel(model_stopbits);
+
+ //设置串口模式
+ ui->cb_baund->setView(new QListView(this));
+ QStandardItemModel* model_baund = new QStandardItemModel(this);
+ vecBaundItemData vbaund;
+ vbaund.push_back(std::make_pair(EMS_BAUND_2400,tr("2400")));
+ vbaund.push_back(std::make_pair(EMS_BAUND_2400,tr("4800")));
+ vbaund.push_back(std::make_pair(EMS_BAUND_2400,tr("9600")));
+ vbaund.push_back(std::make_pair(EMS_BAUND_2400,tr("19200")));
+ vbaund.push_back(std::make_pair(EMS_BAUND_2400,tr("38400")));
+ vbaund.push_back(std::make_pair(EMS_BAUND_2400,tr("57600")));
+ vbaund.push_back(std::make_pair(EMS_BAUND_2400,tr("115200")));
+ vbaund.push_back(std::make_pair(EMS_BAUND_2400,tr("128000")));
+ vbaund.push_back(std::make_pair(EMS_BAUND_2400,tr("153600")));
+ vbaund.push_back(std::make_pair(EMS_BAUND_2400,tr("230400")));
+ vbaund.push_back(std::make_pair(EMS_BAUND_2400,tr("460800")));
+ vbaund.push_back(std::make_pair(EMS_BAUND_2400,tr("921600")));
+
+ for (auto it = vbaund.begin(); it != vbaund.end(); it++)
+ {
+ int id = it->first;
+ QString name = it->second;
+
+ QStandardItem* item = new QStandardItem(name);
+ item->setData(id);
+ model_baund->appendRow(item);
+ }
+
+ ui->cb_baund->setModel(model_baund);
+ ui->cb_baund->setCurrentIndex(2);
+
+ //设置串口类型
+ ui->cb_type->setView(new QListView(this));
+ QStandardItemModel* model_type = new QStandardItemModel(this);
+ vecPortTypeItemData vporttype;
+ vporttype.push_back(std::make_pair(EMS_SERIALPORT_TYPE,tr("Serial Port")));
+
+ for (auto it = vporttype.begin(); it != vporttype.end(); it++)
+ {
+ int id = it->first;
+ QString name = it->second;
+
+ QStandardItem* item = new QStandardItem(name);
+ item->setData(id);
+ model_type->appendRow(item);
+ }
+
+ ui->cb_type->setModel(model_type);
+
+ //设置串口号
+ ui->cb_serialport->setView(new QListView(this));
+
+ QStandardItemModel* model_serialport = new QStandardItemModel(this);
+
+ for (int i=0; i < AppData::getInstance()->SerialPortCount ; i++)
+ {
+ QString name = AppData::getInstance()->SerialPortPrefix + QString("%1").arg(i);
+
+ QStandardItem* item = new QStandardItem(name);
+ //QVariant var = QVariant::fromValue(data);
+ item->setData(name);
+ model_serialport->appendRow(item);
+ }
+
+ ui->cb_serialport->setModel(model_serialport);
+}
diff --git a/applications/emsConfigurer/newdeviceformdialog.h b/applications/emsConfigurer/newdeviceformdialog.h
new file mode 100644
index 0000000..0a8a96c
--- /dev/null
+++ b/applications/emsConfigurer/newdeviceformdialog.h
@@ -0,0 +1,35 @@
+#ifndef NEWDEVICEFORMDIALOG_H
+#define NEWDEVICEFORMDIALOG_H
+
+#include
+
+namespace Ui
+{
+class NewDeviceFormDialog;
+}
+
+class NewDeviceFormDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit NewDeviceFormDialog(QWidget *parent = nullptr);
+ ~NewDeviceFormDialog();
+
+public:
+ bool openDatabase(const QString &dbName);
+
+private slots:
+ void onOkClicked();
+ void onCancelClicked();
+ void browseFile();
+ void onComboBoxTextChanged(const QString &text);
+
+private:
+ Ui::NewDeviceFormDialog *ui;
+
+protected:
+ void InitializeUi();
+};
+
+#endif // NEWDEVICEFORMDIALOG_H
diff --git a/applications/emsConfigurer/newdeviceformdialog.ui b/applications/emsConfigurer/newdeviceformdialog.ui
new file mode 100644
index 0000000..81b0adf
--- /dev/null
+++ b/applications/emsConfigurer/newdeviceformdialog.ui
@@ -0,0 +1,339 @@
+
+
+ NewDeviceFormDialog
+
+
+ Qt::WindowModal
+
+
+
+ 0
+ 0
+ 726
+ 526
+
+
+
+
+ 726
+ 526
+
+
+
+
+ 726
+ 526
+
+
+
+ 设备串口设置
+
+
+
+ :/images/icon.png:/images/icon.png
+
+
+
+
+ 410
+ 450
+ 93
+ 29
+
+
+
+ OK
+
+
+
+
+
+ 510
+ 450
+ 93
+ 29
+
+
+
+ Cancel
+
+
+
+
+
+ 600
+ 50
+ 93
+ 29
+
+
+
+ 导入
+
+
+
+
+
+ 30
+ 50
+ 551
+ 26
+
+
+
+
+
+
+ 30
+ 20
+ 111
+ 20
+
+
+
+ 预定义文件
+
+
+
+
+
+ 30
+ 120
+ 551
+ 26
+
+
+
+
+
+
+ 40
+ 90
+ 63
+ 20
+
+
+
+ 设备类型
+
+
+
+
+
+ 370
+ 210
+ 69
+ 19
+
+
+
+ 从机地址
+
+
+ slave_id
+
+
+
+
+
+ 450
+ 250
+ 201
+ 25
+
+
+
+
+
+
+ 370
+ 250
+ 69
+ 19
+
+
+
+ 波特率
+
+
+ cb_baund
+
+
+
+
+
+ 60
+ 250
+ 69
+ 19
+
+
+
+ 串口号
+
+
+
+
+
+ 370
+ 290
+ 69
+ 19
+
+
+
+ 校验位
+
+
+ cb_parity
+
+
+
+
+
+ 60
+ 330
+ 69
+ 19
+
+
+
+ 停止位
+
+
+ cb_stop
+
+
+
+
+
+ 140
+ 330
+ 181
+ 25
+
+
+
+
+
+
+ 140
+ 250
+ 181
+ 25
+
+
+
+
+
+
+ 140
+ 290
+ 181
+ 25
+
+
+
+
+
+
+ 60
+ 210
+ 69
+ 19
+
+
+
+ 串口类型
+
+
+ cb_type
+
+
+
+
+
+ 60
+ 290
+ 69
+ 19
+
+
+
+ 数据位
+
+
+ cb_data
+
+
+
+
+
+ 450
+ 290
+ 201
+ 25
+
+
+
+
+
+
+ 140
+ 210
+ 181
+ 25
+
+
+
+
+
+
+ 450
+ 210
+ 201
+ 30
+
+
+
+
+
+
+ 30
+ 160
+ 651
+ 231
+
+
+
+ 端口配置
+
+
+ groupBox
+ pb_OK
+ pb_Cancel
+ pb_Import
+ lineEdit
+ label
+ cb_device
+ label_2
+ label_4
+ cb_baund
+ label_6
+ label_5
+ label_8
+ label_9
+ cb_stop
+ cb_serialport
+ cb_data
+ label_7
+ label_10
+ cb_parity
+ cb_type
+ slave_id
+
+
+
+
+
+
diff --git a/applications/examples/qmodbus_demo01/.gitignore b/applications/examples/qmodbus_demo01/.gitignore
new file mode 100644
index 0000000..4a0b530
--- /dev/null
+++ b/applications/examples/qmodbus_demo01/.gitignore
@@ -0,0 +1,74 @@
+# This file is used to ignore files which are generated
+# ----------------------------------------------------------------------------
+
+*~
+*.autosave
+*.a
+*.core
+*.moc
+*.o
+*.obj
+*.orig
+*.rej
+*.so
+*.so.*
+*_pch.h.cpp
+*_resource.rc
+*.qm
+.#*
+*.*#
+core
+!core/
+tags
+.DS_Store
+.directory
+*.debug
+Makefile*
+*.prl
+*.app
+moc_*.cpp
+ui_*.h
+qrc_*.cpp
+Thumbs.db
+*.res
+*.rc
+/.qmake.cache
+/.qmake.stash
+
+# qtcreator generated files
+*.pro.user*
+CMakeLists.txt.user*
+
+# xemacs temporary files
+*.flc
+
+# Vim temporary files
+.*.swp
+
+# Visual Studio generated files
+*.ib_pdb_index
+*.idb
+*.ilk
+*.pdb
+*.sln
+*.suo
+*.vcproj
+*vcproj.*.*.user
+*.ncb
+*.sdf
+*.opensdf
+*.vcxproj
+*vcxproj.*
+
+# MinGW generated files
+*.Debug
+*.Release
+
+# Python byte code
+*.pyc
+
+# Binaries
+# --------
+*.dll
+*.exe
+
diff --git a/applications/examples/qmodbus_demo01/main.cpp b/applications/examples/qmodbus_demo01/main.cpp
new file mode 100644
index 0000000..8349220
--- /dev/null
+++ b/applications/examples/qmodbus_demo01/main.cpp
@@ -0,0 +1,11 @@
+#include "mainwindow.h"
+
+#include
+
+int main(int argc, char *argv[])
+{
+ QApplication a(argc, argv);
+ MainWindow w;
+ w.show();
+ return a.exec();
+}
diff --git a/applications/examples/qmodbus_demo01/qmodbus_demo01.pro b/applications/examples/qmodbus_demo01/qmodbus_demo01.pro
new file mode 100644
index 0000000..d4bd0e3
--- /dev/null
+++ b/applications/examples/qmodbus_demo01/qmodbus_demo01.pro
@@ -0,0 +1,19 @@
+QT = core serialport serialbus
+
+CONFIG += c++17 cmdline
+
+# You can make your code fail to compile if it uses deprecated APIs.
+# In order to do so, uncomment the following line.
+#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
+
+SOURCES += \
+ main.cpp \
+ tempctrl.cpp
+
+# Default rules for deployment.
+qnx: target.path = /tmp/$${TARGET}/bin
+else: unix:!android: target.path = /opt/$${TARGET}/bin
+!isEmpty(target.path): INSTALLS += target
+
+HEADERS += \
+ tempctrl.h
diff --git a/applications/examples/qmodbus_demo01/tempctrl.cpp b/applications/examples/qmodbus_demo01/tempctrl.cpp
new file mode 100644
index 0000000..3147fbc
--- /dev/null
+++ b/applications/examples/qmodbus_demo01/tempctrl.cpp
@@ -0,0 +1,145 @@
+#include "TempCtrl.h"
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#include
+#include
+
+TempCtrl::TempCtrl():
+ m_channelConnected(false), m_channelAddr(0)
+{
+ m_modbusDevice.setConnectionParameter(QModbusDevice::SerialParityParameter, QSerialPort::EvenParity);
+ m_modbusDevice.setConnectionParameter(QModbusDevice::SerialBaudRateParameter, QSerialPort::Baud9600);
+ m_modbusDevice.setConnectionParameter(QModbusDevice::SerialDataBitsParameter, QSerialPort::Data8);
+ m_modbusDevice.setConnectionParameter(QModbusDevice::SerialStopBitsParameter, QSerialPort::OneStop);
+}
+
+TempCtrl::~TempCtrl()
+{
+ disconnectDevice();
+}
+
+TempCtrl* TempCtrl::instance()
+{
+ static TempCtrl* theInstance = new TempCtrl();
+ return theInstance;
+}
+
+bool TempCtrl::connectDevice()
+{
+ disconnectDevice();
+
+ const auto serialPortInfos = QSerialPortInfo::availablePorts();
+ if(!serialPortInfos.empty())
+ {
+ QModbusRequest echoTest(QModbusRequest::Diagnostics, quint16(0x0000), quint16(0x1234));
+
+ bool comportFound(false);
+ for (const QSerialPortInfo &serialPortInfo : serialPortInfos)
+ {
+ m_modbusDevice.setConnectionParameter(QModbusDevice::SerialPortNameParameter, QVariant::fromValue(serialPortInfo.portName()));
+
+ if(m_modbusDevice.connectDevice())
+ {
+ auto curTimePt = std::chrono::steady_clock::now();
+ double duration = 0;
+
+ while(m_modbusDevice.state() != QModbusDevice::ConnectedState && duration < 2.0)
+ {
+ QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 20);
+ std::chrono::duration d = std::chrono::steady_clock::now()-curTimePt;
+ duration = d.count();
+ }
+
+ if(m_modbusDevice.state() == QModbusDevice::ConnectedState)
+ {
+ try
+ {
+ QModbusResponse reply = sendModbusRawRequest(echoTest);
+ if(reply.isValid())
+ {
+ if(reply.data() == QByteArray::fromHex("00001234"))
+ {
+ if(!comportFound)
+ {
+ comportFound = true;
+ }
+ m_channelConnected = true;
+ }
+ }
+ }
+ catch(...)
+ {
+ }
+ }
+
+ if(comportFound)
+ {
+ break;
+ }
+ else
+ {
+ m_modbusDevice.disconnectDevice();
+ }
+ }
+ }
+ }
+
+ m_modbusDevice.setTimeout(500);
+ m_modbusDevice.setNumberOfRetries(3);
+ return m_channelConnected;
+}
+
+void TempCtrl::disconnectDevice()
+{
+ if(m_modbusDevice.state() != QModbusDevice::UnconnectedState)
+ {
+ m_modbusDevice.disconnectDevice();
+ }
+ m_channelConnected = false;
+}
+
+QModbusResponse TempCtrl::sendModbusRawRequest(const QModbusRequest &request) const
+{
+ QModbusReply* reply = m_modbusDevice.sendRawRequest(request, m_channelAddr);
+
+ if(reply)
+ {
+ while(!reply->isFinished())
+ {
+ QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 20);
+ }
+
+ reply->deleteLater();
+
+ if(reply->error() == QModbusDevice::NoError)
+ {
+ auto response = reply->rawResult();
+ std::this_thread::sleep_for(std::chrono::milliseconds(5));
+ return response;
+ }
+ else if (reply->error() == QModbusDevice::ProtocolError)
+ {
+ QString error = "Protocol error: " + reply->errorString();
+ throw std::exception(error.toStdString().c_str());
+ }
+ else if (reply->error() == QModbusDevice::TimeoutError)
+ {
+ throw std::exception("Timeout error");
+ }
+ else
+ {
+ throw std::exception("Unknown error");
+ }
+ }
+ else
+ {
+ return QModbusResponse();
+ }
+}
diff --git a/applications/examples/qmodbus_demo01/tempctrl.h b/applications/examples/qmodbus_demo01/tempctrl.h
new file mode 100644
index 0000000..5c03369
--- /dev/null
+++ b/applications/examples/qmodbus_demo01/tempctrl.h
@@ -0,0 +1,28 @@
+#ifndef TEMPCTRL_H
+#define TEMPCTRL_H
+
+#include
+#include
+
+class TempCtrl
+{
+private:
+ TempCtrl();
+
+public:
+ ~TempCtrl();
+ static TempCtrl* instance();
+
+public:
+ bool connectDevice();
+ void disconnectDevice();
+
+private:
+ QModbusResponse sendModbusRawRequest(const QModbusRequest &request) const;
+
+private:
+ bool m_channelConnected;
+ int m_channelAddr;
+ mutable QModbusRtuSerialClient m_modbusDevice;
+};
+#endif // TEMPCTRL_H
diff --git a/applications/examples/qt5ModbusExample/.gitignore b/applications/examples/qt5ModbusExample/.gitignore
new file mode 100644
index 0000000..4a0b530
--- /dev/null
+++ b/applications/examples/qt5ModbusExample/.gitignore
@@ -0,0 +1,74 @@
+# This file is used to ignore files which are generated
+# ----------------------------------------------------------------------------
+
+*~
+*.autosave
+*.a
+*.core
+*.moc
+*.o
+*.obj
+*.orig
+*.rej
+*.so
+*.so.*
+*_pch.h.cpp
+*_resource.rc
+*.qm
+.#*
+*.*#
+core
+!core/
+tags
+.DS_Store
+.directory
+*.debug
+Makefile*
+*.prl
+*.app
+moc_*.cpp
+ui_*.h
+qrc_*.cpp
+Thumbs.db
+*.res
+*.rc
+/.qmake.cache
+/.qmake.stash
+
+# qtcreator generated files
+*.pro.user*
+CMakeLists.txt.user*
+
+# xemacs temporary files
+*.flc
+
+# Vim temporary files
+.*.swp
+
+# Visual Studio generated files
+*.ib_pdb_index
+*.idb
+*.ilk
+*.pdb
+*.sln
+*.suo
+*.vcproj
+*vcproj.*.*.user
+*.ncb
+*.sdf
+*.opensdf
+*.vcxproj
+*vcxproj.*
+
+# MinGW generated files
+*.Debug
+*.Release
+
+# Python byte code
+*.pyc
+
+# Binaries
+# --------
+*.dll
+*.exe
+
diff --git a/applications/examples/qt5ModbusExample/libmodbus/config.h b/applications/examples/qt5ModbusExample/libmodbus/config.h
new file mode 100644
index 0000000..a5e4998
--- /dev/null
+++ b/applications/examples/qt5ModbusExample/libmodbus/config.h
@@ -0,0 +1,164 @@
+/* config.h. Generated from config.h.in by configure. */
+/* config.h.in. Generated from configure.ac by autoheader. */
+
+/* Define to 1 if you have the header file. */
+/* #undef HAVE_ARPA_INET_H */
+
+/* Define to 1 if you have the declaration of `TIOCSRS485', and to 0 if you
+ don't. */
+/* #undef HAVE_DECL_TIOCSRS485 */
+
+/* Define to 1 if you have the declaration of `__CYGWIN__', and to 0 if you
+ don't. */
+/* #undef HAVE_DECL___CYGWIN__ */
+
+/* Define to 1 if you have the header file. */
+/* #undef HAVE_DLFCN_H */
+
+/* Define to 1 if you have the header file. */
+#define HAVE_ERRNO_H 1
+
+/* Define to 1 if you have the header file. */
+#define HAVE_FCNTL_H 1
+
+/* Define to 1 if you have the `fork' function. */
+/* #undef HAVE_FORK */
+
+/* Define to 1 if you have the `getaddrinfo' function. */
+/* #undef HAVE_GETADDRINFO */
+
+/* Define to 1 if you have the `gettimeofday' function. */
+/* #undef HAVE_GETTIMEOFDAY */
+
+/* Define to 1 if you have the header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the header file. */
+#define HAVE_LIMITS_H 1
+
+/* Define to 1 if you have the header file. */
+/* #undef HAVE_LINUX_SERIAL_H */
+
+/* Define to 1 if you have the header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the `memset' function. */
+#define HAVE_MEMSET 1
+
+/* Define to 1 if you have the header file. */
+/* #undef HAVE_NETDB_H */
+
+/* Define to 1 if you have the header file. */
+/* #undef HAVE_NETINET_IN_H */
+
+/* Define to 1 if you have the header file. */
+/* #undef HAVE_NETINET_TCP_H */
+
+/* Define to 1 if you have the `select' function. */
+/* #undef HAVE_SELECT */
+
+/* Define to 1 if you have the `socket' function. */
+/* #undef HAVE_SOCKET */
+
+/* Define to 1 if you have the header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the `strerror' function. */
+#define HAVE_STRERROR 1
+
+/* Define to 1 if you have the header file. */
+/* #undef HAVE_STRINGS_H */
+
+/* Define to 1 if you have the header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the `strlcpy' function. */
+/* #undef HAVE_STRLCPY */
+
+/* Define to 1 if you have the header file. */
+/* #undef HAVE_SYS_IOCTL_H */
+
+/* Define to 1 if you have the header file. */
+/* #undef HAVE_SYS_SOCKET_H */
+
+/* Define to 1 if you have the header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the header file. */
+/* #undef HAVE_SYS_TIME_H */
+
+/* Define to 1 if you have the header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the header file. */
+/* #undef HAVE_TERMIOS_H */
+
+/* Define to 1 if you have the header file. */
+#define HAVE_TIME_H 1
+
+/* Define to 1 if you have the header file. */
+/* #undef HAVE_UNISTD_H */
+
+/* Define to 1 if you have the `vfork' function. */
+/* #undef HAVE_VFORK */
+
+/* Define to 1 if you have the header file. */
+/* #undef HAVE_VFORK_H */
+
+/* Define to 1 if you have the header file. */
+#define HAVE_WINSOCK2_H 1
+
+/* Define to 1 if `fork' works. */
+/* #undef HAVE_WORKING_FORK */
+
+/* Define to 1 if `vfork' works. */
+/* #undef HAVE_WORKING_VFORK */
+
+/* Define to the sub-directory in which libtool stores uninstalled libraries.
+ */
+/* #undef LT_OBJDIR */
+
+/* Name of package */
+#define PACKAGE "libmodbus"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT "https://github.com/stephane/libmodbus/issues"
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "libmodbus"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "libmodbus 3.1.11"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "libmodbus"
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL ""
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "3.1.11"
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Define to 1 if you can safely include both and . */
+/* #undef TIME_WITH_SYS_TIME */
+
+/* Version number of package */
+#define VERSION "3.1.11"
+
+/* Define to empty if `const' does not conform to ANSI C. */
+/* #undef const */
+
+/* Define to `int' if does not define. */
+/* #undef pid_t */
+
+/* Define to `unsigned int' if does not define. */
+/* #undef size_t */
+
+/* Define as `fork' if `vfork' does not work. */
+#define vfork fork
diff --git a/applications/examples/qt5ModbusExample/libmodbus/modbus-data.c b/applications/examples/qt5ModbusExample/libmodbus/modbus-data.c
new file mode 100644
index 0000000..3a1872d
--- /dev/null
+++ b/applications/examples/qt5ModbusExample/libmodbus/modbus-data.c
@@ -0,0 +1,241 @@
+/*
+ * Copyright © Stéphane Raimbault
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include
+
+// clang-format off
+#ifndef _MSC_VER
+# include
+#else
+# include "stdint.h"
+#endif
+
+#include
+#include
+
+#if defined(_WIN32)
+# include
+#else
+# include
+#endif
+
+#include "libmodbus/config.h"
+
+#include "modbus.h"
+
+// clang-format on
+
+/* Sets many bits from a single byte value (all 8 bits of the byte value are
+ set) */
+void modbus_set_bits_from_byte(uint8_t *dest, int idx, const uint8_t value)
+{
+ int i;
+
+ for (i = 0; i < 8; i++) {
+ dest[idx + i] = (value & (1 << i)) ? 1 : 0;
+ }
+}
+
+/* Sets many bits from a table of bytes (only the bits between idx and
+ idx + nb_bits are set) */
+void modbus_set_bits_from_bytes(uint8_t *dest,
+ int idx,
+ unsigned int nb_bits,
+ const uint8_t *tab_byte)
+{
+ unsigned int i;
+ int shift = 0;
+
+ for (i = idx; i < idx + nb_bits; i++) {
+ dest[i] = tab_byte[(i - idx) / 8] & (1 << shift) ? 1 : 0;
+ /* gcc doesn't like: shift = (++shift) % 8; */
+ shift++;
+ shift %= 8;
+ }
+}
+
+/* Gets the byte value from many bits.
+ To obtain a full byte, set nb_bits to 8. */
+uint8_t modbus_get_byte_from_bits(const uint8_t *src, int idx, unsigned int nb_bits)
+{
+ unsigned int i;
+ uint8_t value = 0;
+
+ if (nb_bits > 8) {
+ /* Assert is ignored if NDEBUG is set */
+ assert(nb_bits < 8);
+ nb_bits = 8;
+ }
+
+ for (i = 0; i < nb_bits; i++) {
+ value |= (src[idx + i] << i);
+ }
+
+ return value;
+}
+
+/* Get a float from 4 bytes (Modbus) without any conversion (ABCD) */
+float modbus_get_float_abcd(const uint16_t *src)
+{
+ float f;
+ uint32_t i;
+ uint8_t a, b, c, d;
+
+ // Mind: src contains 16-bit numbers in processor-endianness, hence
+ // we use shift operations and do not access memory directly
+ a = (src[0] >> 8) & 0xFF; // high byte of first word
+ b = (src[0] >> 0) & 0xFF; // low byte of first word
+ c = (src[1] >> 8) & 0xFF; // high byte of second word
+ d = (src[1] >> 0) & 0xFF; // low byte of second word
+
+ // we assemble 32bit integer always in abcd order via shift operations
+ i = (a << 24) | (b << 16) | (c << 8) | (d << 0);
+ memcpy(&f, &i, 4);
+
+ return f;
+}
+
+/* Get a float from 4 bytes (Modbus) in inversed format (DCBA) */
+float modbus_get_float_dcba(const uint16_t *src)
+{
+ float f;
+ uint32_t i;
+ uint8_t a, b, c, d;
+
+ // byte order is defined when reading from src: dcba
+ d = (src[0] >> 8) & 0xFF;
+ c = (src[0] >> 0) & 0xFF;
+ b = (src[1] >> 8) & 0xFF;
+ a = (src[1] >> 0) & 0xFF;
+
+ // we assemble 32bit integer always in abcd order via shift operations
+ i = (a << 24) | (b << 16) | (c << 8) | (d << 0);
+ memcpy(&f, &i, 4);
+
+ return f;
+}
+
+/* Get a float from 4 bytes (Modbus) with swapped bytes (BADC) */
+float modbus_get_float_badc(const uint16_t *src)
+{
+ float f;
+ uint32_t i;
+ uint8_t a, b, c, d;
+
+ // byte order is defined when reading from src: badc
+ b = (src[0] >> 8) & 0xFF;
+ a = (src[0] >> 0) & 0xFF;
+ d = (src[1] >> 8) & 0xFF;
+ c = (src[1] >> 0) & 0xFF;
+
+ // we assemble 32bit integer always in abcd order via shift operations
+ i = (a << 24) | (b << 16) | (c << 8) | (d << 0);
+ memcpy(&f, &i, 4);
+
+ return f;
+}
+
+/* Get a float from 4 bytes (Modbus) with swapped words (CDAB) */
+float modbus_get_float_cdab(const uint16_t *src)
+{
+ float f;
+ uint32_t i;
+ uint8_t a, b, c, d;
+
+ // byte order is defined when reading from src: cdab
+ c = (src[0] >> 8) & 0xFF;
+ d = (src[0] >> 0) & 0xFF;
+ a = (src[1] >> 8) & 0xFF;
+ b = (src[1] >> 0) & 0xFF;
+
+ // we assemble 32bit integer always in abcd order via shift operations
+ i = (a << 24) | (b << 16) | (c << 8) | (d << 0);
+ memcpy(&f, &i, 4);
+
+ return f;
+}
+
+/* DEPRECATED - Get a float from 4 bytes in sort of Modbus format */
+float modbus_get_float(const uint16_t *src)
+{
+ return modbus_get_float_cdab(src);
+}
+
+/* Set a float to 4 bytes for Modbus w/o any conversion (ABCD) */
+void modbus_set_float_abcd(float f, uint16_t *dest)
+{
+ // The straight-forward type conversion won't work because of type-punned pointer aliasing warning
+ // uint32_t i = *(uint32_t*)(&f);
+ float * fptr = &f;
+ uint32_t * iptr = (uint32_t *)fptr;
+ uint32_t i = *iptr;
+ uint8_t a, b, c, d;
+
+ a = (i >> 24) & 0xFF;
+ b = (i >> 16) & 0xFF;
+ c = (i >> 8) & 0xFF;
+ d = (i >> 0) & 0xFF;
+
+ dest[0] = (a << 8) | b;
+ dest[1] = (c << 8) | d;
+}
+
+/* Set a float to 4 bytes for Modbus with byte and word swap conversion (DCBA) */
+void modbus_set_float_dcba(float f, uint16_t *dest)
+{
+ float * fptr = &f;
+ uint32_t * iptr = (uint32_t *)fptr;
+ uint32_t i = *iptr;
+ uint8_t a, b, c, d;
+
+ a = (i >> 24) & 0xFF;
+ b = (i >> 16) & 0xFF;
+ c = (i >> 8) & 0xFF;
+ d = (i >> 0) & 0xFF;
+
+ dest[0] = (d << 8) | c;
+ dest[1] = (b << 8) | a;
+}
+
+/* Set a float to 4 bytes for Modbus with byte swap conversion (BADC) */
+void modbus_set_float_badc(float f, uint16_t *dest)
+{
+ float * fptr = &f;
+ uint32_t * iptr = (uint32_t *)fptr;
+ uint32_t i = *iptr;
+ uint8_t a, b, c, d;
+
+ a = (i >> 24) & 0xFF;
+ b = (i >> 16) & 0xFF;
+ c = (i >> 8) & 0xFF;
+ d = (i >> 0) & 0xFF;
+
+ dest[0] = (b << 8) | a;
+ dest[1] = (d << 8) | c;
+}
+
+/* Set a float to 4 bytes for Modbus with word swap conversion (CDAB) */
+void modbus_set_float_cdab(float f, uint16_t *dest)
+{
+ float * fptr = &f;
+ uint32_t * iptr = (uint32_t *)fptr;
+ uint32_t i = *iptr;
+ uint8_t a, b, c, d;
+
+ a = (i >> 24) & 0xFF;
+ b = (i >> 16) & 0xFF;
+ c = (i >> 8) & 0xFF;
+ d = (i >> 0) & 0xFF;
+
+ dest[0] = (c << 8) | d;
+ dest[1] = (a << 8) | b;
+}
+
+/* DEPRECATED - Set a float to 4 bytes in a sort of Modbus format! */
+void modbus_set_float(float f, uint16_t *dest)
+{
+ modbus_set_float_cdab(f, dest);
+}
diff --git a/applications/examples/qt5ModbusExample/libmodbus/modbus-private.h b/applications/examples/qt5ModbusExample/libmodbus/modbus-private.h
new file mode 100644
index 0000000..1b48269
--- /dev/null
+++ b/applications/examples/qt5ModbusExample/libmodbus/modbus-private.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright © Stéphane Raimbault
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef MODBUS_PRIVATE_H
+#define MODBUS_PRIVATE_H
+
+// clang-format off
+#ifndef _MSC_VER
+# include
+# include
+#else
+# include "stdint.h"
+# include
+typedef int ssize_t;
+#endif
+// clang-format on
+#include "libmodbus/config.h"
+#include
+
+#include "modbus.h"
+
+MODBUS_BEGIN_DECLS
+
+/* It's not really the minimal length (the real one is report slave ID
+ * in RTU (4 bytes)) but it's a convenient size to use in RTU or TCP
+ * communications to read many values or write a single one.
+ * Maximum between :
+ * - HEADER_LENGTH_TCP (7) + function (1) + address (2) + number (2)
+ * - HEADER_LENGTH_RTU (1) + function (1) + address (2) + number (2) + CRC (2)
+ */
+#define _MIN_REQ_LENGTH 12
+
+#define _REPORT_SLAVE_ID 180
+
+#define _MODBUS_EXCEPTION_RSP_LENGTH 5
+
+/* Timeouts in microsecond (0.5 s) */
+#define _RESPONSE_TIMEOUT 500000
+#define _BYTE_TIMEOUT 500000
+
+typedef enum
+{
+ _MODBUS_BACKEND_TYPE_RTU = 0,
+ _MODBUS_BACKEND_TYPE_TCP
+} modbus_backend_type_t;
+
+/*
+ * ---------- Request Indication ----------
+ * | Client | ---------------------->| Server |
+ * ---------- Confirmation Response ----------
+ */
+typedef enum
+{
+ /* Request message on the server side */
+ MSG_INDICATION,
+ /* Request message on the client side */
+ MSG_CONFIRMATION
+} msg_type_t;
+
+/* This structure reduces the number of params in functions and so
+ * optimizes the speed of execution (~ 37%). */
+typedef struct _sft
+{
+ int slave;
+ int function;
+ int t_id;
+} sft_t;
+
+typedef struct _modbus_backend
+{
+ unsigned int backend_type;
+ unsigned int header_length;
+ unsigned int checksum_length;
+ unsigned int max_adu_length;
+ int (*set_slave)(modbus_t *ctx, int slave);
+ int (*build_request_basis)(
+ modbus_t *ctx, int function, int addr, int nb, uint8_t *req);
+ int (*build_response_basis)(sft_t *sft, uint8_t *rsp);
+ int (*get_response_tid)(const uint8_t *req);
+ int (*send_msg_pre)(uint8_t *req, int req_length);
+ ssize_t (*send)(modbus_t *ctx, const uint8_t *req, int req_length);
+ int (*receive)(modbus_t *ctx, uint8_t *req);
+ ssize_t (*recv)(modbus_t *ctx, uint8_t *rsp, int rsp_length);
+ int (*check_integrity)(modbus_t *ctx, uint8_t *msg, const int msg_length);
+ int (*pre_check_confirmation)(modbus_t *ctx,
+ const uint8_t *req,
+ const uint8_t *rsp,
+ int rsp_length);
+ int (*connect)(modbus_t *ctx);
+ unsigned int (*is_connected)(modbus_t *ctx);
+ void (*close)(modbus_t *ctx);
+ int (*flush)(modbus_t *ctx);
+ int (*select)(modbus_t *ctx, fd_set *rset, struct timeval *tv, int msg_length);
+ void (*free)(modbus_t *ctx);
+} modbus_backend_t;
+
+struct _modbus
+{
+ /* Slave address */
+ int slave;
+ /* Socket or file descriptor */
+ int s;
+ int debug;
+ int error_recovery;
+ int quirks;
+ struct timeval response_timeout;
+ struct timeval byte_timeout;
+ struct timeval indication_timeout;
+ const modbus_backend_t *backend;
+ void *backend_data;
+};
+
+void _modbus_init_common(modbus_t *ctx);
+void _error_print(modbus_t *ctx, const char *context);
+int _modbus_receive_msg(modbus_t *ctx, uint8_t *msg, msg_type_t msg_type);
+
+#ifndef HAVE_STRLCPY
+size_t strlcpy(char *dest, const char *src, size_t dest_size);
+#endif
+
+MODBUS_END_DECLS
+
+#endif /* MODBUS_PRIVATE_H */
diff --git a/applications/examples/qt5ModbusExample/libmodbus/modbus-rtu-private.h b/applications/examples/qt5ModbusExample/libmodbus/modbus-rtu-private.h
new file mode 100644
index 0000000..01e6a91
--- /dev/null
+++ b/applications/examples/qt5ModbusExample/libmodbus/modbus-rtu-private.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright © Stéphane Raimbault
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef MODBUS_RTU_PRIVATE_H
+#define MODBUS_RTU_PRIVATE_H
+
+#ifndef _MSC_VER
+#include
+#else
+#include "stdint.h"
+#endif
+
+#if defined(_WIN32)
+#include
+#else
+#include
+#endif
+
+#define _MODBUS_RTU_HEADER_LENGTH 1
+#define _MODBUS_RTU_PRESET_REQ_LENGTH 6
+#define _MODBUS_RTU_PRESET_RSP_LENGTH 2
+
+#define _MODBUS_RTU_CHECKSUM_LENGTH 2
+
+#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_rtu {
+ /* 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
+ /* To handle many slaves on the same link */
+ int confirmation_to_ignore;
+} modbus_rtu_t;
+
+#endif /* MODBUS_RTU_PRIVATE_H */
diff --git a/applications/examples/qt5ModbusExample/libmodbus/modbus-rtu.c b/applications/examples/qt5ModbusExample/libmodbus/modbus-rtu.c
new file mode 100644
index 0000000..ebef934
--- /dev/null
+++ b/applications/examples/qt5ModbusExample/libmodbus/modbus-rtu.c
@@ -0,0 +1,1288 @@
+/*
+ * Copyright © Stéphane Raimbault
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include
+#include
+#include
+#include
+#include
+#ifndef _MSC_VER
+#include
+#endif
+#include "modbus-private.h"
+#include
+
+#include "modbus-rtu-private.h"
+#include "modbus-rtu.h"
+
+#if HAVE_DECL_TIOCSRS485 || HAVE_DECL_TIOCM_RTS
+#include
+#endif
+
+#if HAVE_DECL_TIOCSRS485
+#include
+#endif
+
+/* Table of CRC values for high-order byte */
+static const uint8_t table_crc_hi[] = {
+ 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
+ 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
+ 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1,
+ 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
+ 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
+ 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
+ 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1,
+ 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
+ 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
+ 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
+ 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
+ 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
+ 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
+ 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
+ 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
+ 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
+ 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
+ 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
+ 0x00, 0xC1, 0x81, 0x40};
+
+/* Table of CRC values for low-order byte */
+static const uint8_t table_crc_lo[] = {
+ 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5,
+ 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B,
+ 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE,
+ 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6,
+ 0xD2, 0x12, 0x13, 0xD3, 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,
+ 0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D,
+ 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8,
+ 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C,
+ 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21,
+ 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,
+ 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A,
+ 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA,
+ 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, 0x77, 0xB7,
+ 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91, 0x51,
+ 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
+ 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98,
+ 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D,
+ 0x4C, 0x8C, 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83,
+ 0x41, 0x81, 0x80, 0x40};
+
+/* 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)
+{
+ int max_slave = (ctx->quirks & MODBUS_QUIRK_MAX_SLAVE) ? 255 : 247;
+
+ /* Broadcast address is 0 (MODBUS_BROADCAST_ADDRESS) */
+ if (slave >= 0 && slave <= max_slave) {
+ ctx->slave = slave;
+ } else {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Builds a RTU request header */
+static int _modbus_rtu_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_RTU_PRESET_REQ_LENGTH;
+}
+
+/* Builds a RTU response header */
+static int _modbus_rtu_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_rtu_listen */
+ rsp[0] = sft->slave;
+ rsp[1] = sft->function;
+
+ return _MODBUS_RTU_PRESET_RSP_LENGTH;
+}
+
+static uint16_t crc16(uint8_t *buffer, uint16_t buffer_length)
+{
+ uint8_t crc_hi = 0xFF; /* high CRC byte initialized */
+ uint8_t crc_lo = 0xFF; /* low CRC byte initialized */
+ unsigned int i; /* will index into CRC lookup */
+
+ /* pass through message buffer */
+ while (buffer_length--) {
+ i = crc_lo ^ *buffer++; /* calculate the CRC */
+ crc_lo = crc_hi ^ table_crc_hi[i];
+ crc_hi = table_crc_lo[i];
+ }
+
+ return (crc_hi << 8 | crc_lo);
+}
+
+static int _modbus_rtu_get_response_tid(const uint8_t *req)
+{
+ /* No TID */
+ return 0;
+}
+
+static int _modbus_rtu_send_msg_pre(uint8_t *req, int req_length)
+{
+ uint16_t crc = crc16(req, req_length);
+
+ /* According to the MODBUS specs (p. 14), the low order byte of the CRC comes
+ * first in the RTU message */
+ req[req_length++] = crc & 0x00FF;
+ req[req_length++] = crc >> 8;
+
+ 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_rtu_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_rtu_send(modbus_t *ctx, const uint8_t *req, int req_length)
+{
+#if defined(_WIN32)
+ modbus_rtu_t *ctx_rtu = ctx->backend_data;
+ DWORD n_bytes = 0;
+ return (WriteFile(ctx_rtu->w_ser.fd, req, req_length, &n_bytes, NULL))
+ ? (ssize_t) n_bytes
+ : -1;
+#else
+#if HAVE_DECL_TIOCM_RTS
+ modbus_rtu_t *ctx_rtu = ctx->backend_data;
+ if (ctx_rtu->rts != MODBUS_RTU_RTS_NONE) {
+ ssize_t size;
+
+ if (ctx->debug) {
+ fprintf(stderr, "Sending request using RTS signal\n");
+ }
+
+ ctx_rtu->set_rts(ctx, ctx_rtu->rts == MODBUS_RTU_RTS_UP);
+ usleep(ctx_rtu->rts_delay);
+
+ size = write(ctx->s, req, req_length);
+
+ usleep(ctx_rtu->onebyte_time * req_length + ctx_rtu->rts_delay);
+ ctx_rtu->set_rts(ctx, ctx_rtu->rts != MODBUS_RTU_RTS_UP);
+
+ return size;
+ } else {
+#endif
+ return write(ctx->s, req, req_length);
+#if HAVE_DECL_TIOCM_RTS
+ }
+#endif
+#endif
+}
+
+static int _modbus_rtu_receive(modbus_t *ctx, uint8_t *req)
+{
+ int rc;
+ modbus_rtu_t *ctx_rtu = ctx->backend_data;
+
+ if (ctx_rtu->confirmation_to_ignore) {
+ _modbus_receive_msg(ctx, req, MSG_CONFIRMATION);
+ /* Ignore errors and reset the flag */
+ ctx_rtu->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_rtu->confirmation_to_ignore = TRUE;
+ }
+ }
+ return rc;
+}
+
+static ssize_t _modbus_rtu_recv(modbus_t *ctx, uint8_t *rsp, int rsp_length)
+{
+#if defined(_WIN32)
+ return win32_ser_read(&((modbus_rtu_t *) ctx->backend_data)->w_ser, rsp, rsp_length);
+#else
+ return read(ctx->s, rsp, rsp_length);
+#endif
+}
+
+static int _modbus_rtu_flush(modbus_t *);
+
+static int _modbus_rtu_pre_check_confirmation(modbus_t *ctx,
+ const uint8_t *req,
+ const uint8_t *rsp,
+ int rsp_length)
+{
+ /* 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 if the message is ignored and the
+ message length if the CRC is valid. Otherwise it shall return -1 and set
+ errno to EMBBADCRC. */
+static int _modbus_rtu_check_integrity(modbus_t *ctx, uint8_t *msg, const int msg_length)
+{
+ uint16_t crc_calculated;
+ uint16_t crc_received;
+ int slave = msg[0];
+
+ crc_calculated = crc16(msg, msg_length - 2);
+ crc_received = (msg[msg_length - 1] << 8) | msg[msg_length - 2];
+
+ /* Check CRC of msg */
+ if (crc_calculated != crc_received) {
+ 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_rtu_flush(ctx);
+ }
+ errno = EMBBADCRC;
+ return -1;
+ }
+
+ /* Filter on the Modbus unit identifier (slave) in RTU mode */
+ 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;
+ }
+
+ return msg_length;
+}
+
+/* Sets up a serial port for RTU communications */
+#if defined(_WIN32)
+static int _modbus_rtu_connect(modbus_t *ctx)
+{
+ DCB dcb;
+ modbus_rtu_t *ctx_rtu = ctx->backend_data;
+
+ if (ctx->debug) {
+ printf("Opening %s at %d bauds (%c, %d, %d)\n",
+ ctx_rtu->device,
+ ctx_rtu->baud,
+ ctx_rtu->parity,
+ ctx_rtu->data_bit,
+ ctx_rtu->stop_bit);
+ }
+
+ /* Some references here:
+ * http://msdn.microsoft.com/en-us/library/aa450602.aspx
+ */
+ win32_ser_init(&ctx_rtu->w_ser);
+
+ /* ctx_rtu->device should contain a string like "COMxx:" xx being a decimal
+ * number */
+ ctx_rtu->w_ser.fd = CreateFileA(
+ ctx_rtu->device, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
+
+ /* Error checking */
+ if (ctx_rtu->w_ser.fd == INVALID_HANDLE_VALUE) {
+ if (ctx->debug) {
+ fprintf(stderr,
+ "ERROR Can't open the device %s (LastError %d)\n",
+ ctx_rtu->device,
+ (int) GetLastError());
+ }
+ return -1;
+ }
+
+ /* Save params */
+ ctx_rtu->old_dcb.DCBlength = sizeof(DCB);
+ if (!GetCommState(ctx_rtu->w_ser.fd, &ctx_rtu->old_dcb)) {
+ if (ctx->debug) {
+ fprintf(stderr,
+ "ERROR Error getting configuration (LastError %d)\n",
+ (int) GetLastError());
+ }
+ CloseHandle(ctx_rtu->w_ser.fd);
+ ctx_rtu->w_ser.fd = INVALID_HANDLE_VALUE;
+ return -1;
+ }
+
+ /* Build new configuration (starting from current settings) */
+ dcb = ctx_rtu->old_dcb;
+
+ /* Speed setting */
+ dcb.BaudRate = ctx_rtu->baud;
+
+ /* Data bits */
+ switch (ctx_rtu->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_rtu->stop_bit == 1)
+ dcb.StopBits = ONESTOPBIT;
+ else /* 2 */
+ dcb.StopBits = TWOSTOPBITS;
+
+ /* Parity */
+ if (ctx_rtu->parity == 'N') {
+ dcb.Parity = NOPARITY;
+ dcb.fParity = FALSE;
+ } else if (ctx_rtu->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_rtu->w_ser.fd, &dcb)) {
+ if (ctx->debug) {
+ fprintf(stderr,
+ "ERROR Error setting new configuration (LastError %d)\n",
+ (int) GetLastError());
+ }
+ CloseHandle(ctx_rtu->w_ser.fd);
+ ctx_rtu->w_ser.fd = INVALID_HANDLE_VALUE;
+ return -1;
+ }
+
+ return 0;
+}
+#else
+
+static speed_t _get_termios_speed(int baud, int debug)
+{
+ speed_t speed;
+
+ switch (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 (debug) {
+ fprintf(stderr, "WARNING Unknown baud rate %d (B9600 used)\n", baud);
+ }
+ }
+
+ return speed;
+}
+
+/* POSIX */
+static int _modbus_rtu_connect(modbus_t *ctx)
+{
+ struct termios tios;
+ int flags;
+ speed_t speed;
+ modbus_rtu_t *ctx_rtu = ctx->backend_data;
+
+ if (ctx->debug) {
+ printf("Opening %s at %d bauds (%c, %d, %d)\n",
+ ctx_rtu->device,
+ ctx_rtu->baud,
+ ctx_rtu->parity,
+ ctx_rtu->data_bit,
+ ctx_rtu->stop_bit);
+ }
+
+ /* 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
+ NONBLOCK option is set on the file via open or fcntl */
+ flags = O_RDWR | O_NOCTTY | O_NONBLOCK | O_EXCL;
+#ifdef O_CLOEXEC
+ flags |= O_CLOEXEC;
+#endif
+
+ ctx->s = open(ctx_rtu->device, flags);
+ if (ctx->s < 0) {
+ if (ctx->debug) {
+ fprintf(stderr,
+ "ERROR Can't open the device %s (%s)\n",
+ ctx_rtu->device,
+ strerror(errno));
+ }
+ return -1;
+ }
+
+ /* Save */
+ tcgetattr(ctx->s, &ctx_rtu->old_tios);
+
+ memset(&tios, 0, sizeof(struct termios));
+
+ /* C_ISPEED Input baud (new interface)
+ C_OSPEED Output baud (new interface)
+ */
+
+ /* Set the baud rate */
+
+ /*
+ On MacOS, constants of baud rates are equal to the integer in argument but
+ that's not the case under Linux so we have to find the corresponding
+ constant. Until the code is upgraded to termios2, the list of possible
+ values is limited (no 14400 for example).
+ */
+ if (9600 == B9600) {
+ speed = ctx_rtu->baud;
+ } else {
+ speed = _get_termios_speed(ctx_rtu->baud, ctx->debug);
+ }
+
+ 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_rtu->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_rtu->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_rtu->parity == 'N') {
+ /* None */
+ tios.c_cflag &= ~PARENB;
+ } else if (ctx_rtu->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_rtu->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 output */
+ 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 NONBLOCK
+ 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
+ NONBLOCK option is set on the port with open or fcntl.
+ */
+ /* Unused because we use open with the NONBLOCK 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;
+ }
+
+ return 0;
+}
+#endif
+
+// FIXME Temporary solution before rewriting Windows RTU backend
+static unsigned int _modbus_rtu_is_connected(modbus_t *ctx)
+{
+#if defined(_WIN32)
+ modbus_rtu_t *ctx_rtu = ctx->backend_data;
+
+ /* Check if file handle is valid */
+ return ctx_rtu->w_ser.fd != INVALID_HANDLE_VALUE;
+#else
+ return ctx->s >= 0;
+#endif
+}
+
+int modbus_rtu_set_serial_mode(modbus_t *ctx, int mode)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) {
+#if HAVE_DECL_TIOCSRS485
+ modbus_rtu_t *ctx_rtu = ctx->backend_data;
+ struct serial_rs485 rs485conf;
+
+ if (mode == MODBUS_RTU_RS485) {
+ // Get
+ if (ioctl(ctx->s, TIOCGRS485, &rs485conf) < 0) {
+ return -1;
+ }
+ // Set
+ rs485conf.flags |= SER_RS485_ENABLED;
+ if (ioctl(ctx->s, TIOCSRS485, &rs485conf) < 0) {
+ return -1;
+ }
+
+ ctx_rtu->serial_mode = MODBUS_RTU_RS485;
+ return 0;
+ } else if (mode == MODBUS_RTU_RS232) {
+ /* Turn off RS485 mode only if required */
+ if (ctx_rtu->serial_mode == MODBUS_RTU_RS485) {
+ /* The ioctl call is avoided because it can fail on some RS232 ports */
+ if (ioctl(ctx->s, TIOCGRS485, &rs485conf) < 0) {
+ return -1;
+ }
+ rs485conf.flags &= ~SER_RS485_ENABLED;
+ if (ioctl(ctx->s, TIOCSRS485, &rs485conf) < 0) {
+ return -1;
+ }
+ }
+ ctx_rtu->serial_mode = MODBUS_RTU_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_rtu_get_serial_mode(modbus_t *ctx)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) {
+#if HAVE_DECL_TIOCSRS485
+ modbus_rtu_t *ctx_rtu = ctx->backend_data;
+ return ctx_rtu->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_rtu_get_rts(modbus_t *ctx)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) {
+#if HAVE_DECL_TIOCM_RTS
+ modbus_rtu_t *ctx_rtu = ctx->backend_data;
+ return ctx_rtu->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_rtu_set_rts(modbus_t *ctx, int mode)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) {
+#if HAVE_DECL_TIOCM_RTS
+ modbus_rtu_t *ctx_rtu = ctx->backend_data;
+
+ if (mode == MODBUS_RTU_RTS_NONE || mode == MODBUS_RTU_RTS_UP ||
+ mode == MODBUS_RTU_RTS_DOWN) {
+ ctx_rtu->rts = mode;
+
+ /* Set the RTS bit in order to not reserve the RS485 bus */
+ ctx_rtu->set_rts(ctx, ctx_rtu->rts != MODBUS_RTU_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_rtu_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_RTU) {
+#if HAVE_DECL_TIOCM_RTS
+ modbus_rtu_t *ctx_rtu = ctx->backend_data;
+ ctx_rtu->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_rtu_get_rts_delay(modbus_t *ctx)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) {
+#if HAVE_DECL_TIOCM_RTS
+ modbus_rtu_t *ctx_rtu;
+ ctx_rtu = (modbus_rtu_t *) ctx->backend_data;
+ return ctx_rtu->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_rtu_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_RTU) {
+#if HAVE_DECL_TIOCM_RTS
+ modbus_rtu_t *ctx_rtu;
+ ctx_rtu = (modbus_rtu_t *) ctx->backend_data;
+ ctx_rtu->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;
+ }
+}
+
+static void _modbus_rtu_close(modbus_t *ctx)
+{
+ /* Restore line settings and close file descriptor in RTU mode */
+ modbus_rtu_t *ctx_rtu = ctx->backend_data;
+
+#if defined(_WIN32)
+ /* Revert settings */
+ if (!SetCommState(ctx_rtu->w_ser.fd, &ctx_rtu->old_dcb) && ctx->debug) {
+ fprintf(stderr,
+ "ERROR Couldn't revert to configuration (LastError %d)\n",
+ (int) GetLastError());
+ }
+
+ if (!CloseHandle(ctx_rtu->w_ser.fd) && ctx->debug) {
+ fprintf(stderr,
+ "ERROR Error while closing handle (LastError %d)\n",
+ (int) GetLastError());
+ }
+#else
+ if (ctx->s >= 0) {
+ tcsetattr(ctx->s, TCSANOW, &ctx_rtu->old_tios);
+ close(ctx->s);
+ ctx->s = -1;
+ }
+#endif
+}
+
+static int _modbus_rtu_flush(modbus_t *ctx)
+{
+#if defined(_WIN32)
+ modbus_rtu_t *ctx_rtu = ctx->backend_data;
+ ctx_rtu->w_ser.n_bytes = 0;
+ return (PurgeComm(ctx_rtu->w_ser.fd, PURGE_RXCLEAR) == FALSE);
+#else
+ return tcflush(ctx->s, TCIOFLUSH);
+#endif
+}
+
+static int
+_modbus_rtu_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_rtu_t *) ctx->backend_data)->w_ser, length_to_read, tv);
+ if (s_rc == 0) {
+ errno = ETIMEDOUT;
+ return -1;
+ }
+
+ if (s_rc < 0) {
+ 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_rtu_free(modbus_t *ctx)
+{
+ if (ctx->backend_data) {
+ free(((modbus_rtu_t *) ctx->backend_data)->device);
+ free(ctx->backend_data);
+ }
+
+ free(ctx);
+}
+
+// clang-format off
+const modbus_backend_t _modbus_rtu_backend = {
+ _MODBUS_BACKEND_TYPE_RTU,
+ _MODBUS_RTU_HEADER_LENGTH,
+ _MODBUS_RTU_CHECKSUM_LENGTH,
+ MODBUS_RTU_MAX_ADU_LENGTH,
+ _modbus_set_slave,
+ _modbus_rtu_build_request_basis,
+ _modbus_rtu_build_response_basis,
+ _modbus_rtu_get_response_tid,
+ _modbus_rtu_send_msg_pre,
+ _modbus_rtu_send,
+ _modbus_rtu_receive,
+ _modbus_rtu_recv,
+ _modbus_rtu_check_integrity,
+ _modbus_rtu_pre_check_confirmation,
+ _modbus_rtu_connect,
+ _modbus_rtu_is_connected,
+ _modbus_rtu_close,
+ _modbus_rtu_flush,
+ _modbus_rtu_select,
+ _modbus_rtu_free
+};
+
+// clang-format on
+
+modbus_t *
+modbus_new_rtu(const char *device, int baud, char parity, int data_bit, int stop_bit)
+{
+ modbus_t *ctx;
+ modbus_rtu_t *ctx_rtu;
+
+ /* 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;
+ }
+
+ ctx = (modbus_t *) malloc(sizeof(modbus_t));
+ if (ctx == NULL) {
+ return NULL;
+ }
+
+ _modbus_init_common(ctx);
+ ctx->backend = &_modbus_rtu_backend;
+ ctx->backend_data = (modbus_rtu_t *) malloc(sizeof(modbus_rtu_t));
+ if (ctx->backend_data == NULL) {
+ modbus_free(ctx);
+ errno = ENOMEM;
+ return NULL;
+ }
+ ctx_rtu = (modbus_rtu_t *) ctx->backend_data;
+
+ /* Device name and \0 */
+ ctx_rtu->device = (char *) malloc((strlen(device) + 1) * sizeof(char));
+ if (ctx_rtu->device == NULL) {
+ modbus_free(ctx);
+ errno = ENOMEM;
+ return NULL;
+ }
+
+#if defined(_WIN32)
+ strcpy_s(ctx_rtu->device, strlen(device) + 1, device);
+#else
+ strcpy(ctx_rtu->device, device);
+#endif
+
+ ctx_rtu->baud = baud;
+ if (parity == 'N' || parity == 'E' || parity == 'O') {
+ ctx_rtu->parity = parity;
+ } else {
+ modbus_free(ctx);
+ errno = EINVAL;
+ return NULL;
+ }
+ ctx_rtu->data_bit = data_bit;
+ ctx_rtu->stop_bit = stop_bit;
+
+#if HAVE_DECL_TIOCSRS485
+ /* The RS232 mode has been set by default */
+ ctx_rtu->serial_mode = MODBUS_RTU_RS232;
+#endif
+
+#if HAVE_DECL_TIOCM_RTS
+ /* The RTS use has been set by default */
+ ctx_rtu->rts = MODBUS_RTU_RTS_NONE;
+
+ /* Calculate estimated time in micro second to send one byte */
+ ctx_rtu->onebyte_time =
+ 1000000 * (1 + data_bit + (parity == 'N' ? 0 : 1) + stop_bit) / baud;
+
+ /* The internal function is used by default to set RTS */
+ ctx_rtu->set_rts = _modbus_rtu_ioctl_rts;
+
+ /* The delay before and after transmission when toggling the RTS pin */
+ ctx_rtu->rts_delay = ctx_rtu->onebyte_time;
+#endif
+
+ ctx_rtu->confirmation_to_ignore = FALSE;
+
+ return ctx;
+}
diff --git a/applications/examples/qt5ModbusExample/libmodbus/modbus-rtu.h b/applications/examples/qt5ModbusExample/libmodbus/modbus-rtu.h
new file mode 100644
index 0000000..8e89e73
--- /dev/null
+++ b/applications/examples/qt5ModbusExample/libmodbus/modbus-rtu.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright © Stéphane Raimbault
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef MODBUS_RTU_H
+#define MODBUS_RTU_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_RTU_MAX_ADU_LENGTH 256
+
+MODBUS_API modbus_t *
+modbus_new_rtu(const char *device, int baud, char parity, int data_bit, int stop_bit);
+
+#define MODBUS_RTU_RS232 0
+#define MODBUS_RTU_RS485 1
+
+MODBUS_API int modbus_rtu_set_serial_mode(modbus_t *ctx, int mode);
+MODBUS_API int modbus_rtu_get_serial_mode(modbus_t *ctx);
+
+#define MODBUS_RTU_RTS_NONE 0
+#define MODBUS_RTU_RTS_UP 1
+#define MODBUS_RTU_RTS_DOWN 2
+
+MODBUS_API int modbus_rtu_set_rts(modbus_t *ctx, int mode);
+MODBUS_API int modbus_rtu_get_rts(modbus_t *ctx);
+
+MODBUS_API int modbus_rtu_set_custom_rts(modbus_t *ctx,
+ void (*set_rts)(modbus_t *ctx, int on));
+
+MODBUS_API int modbus_rtu_set_rts_delay(modbus_t *ctx, int us);
+MODBUS_API int modbus_rtu_get_rts_delay(modbus_t *ctx);
+
+MODBUS_END_DECLS
+
+#endif /* MODBUS_RTU_H */
diff --git a/applications/examples/qt5ModbusExample/libmodbus/modbus-tcp-private.h b/applications/examples/qt5ModbusExample/libmodbus/modbus-tcp-private.h
new file mode 100644
index 0000000..faffc21
--- /dev/null
+++ b/applications/examples/qt5ModbusExample/libmodbus/modbus-tcp-private.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright © Stéphane Raimbault
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef MODBUS_TCP_PRIVATE_H
+#define MODBUS_TCP_PRIVATE_H
+
+#define _MODBUS_TCP_HEADER_LENGTH 7
+#define _MODBUS_TCP_PRESET_REQ_LENGTH 12
+#define _MODBUS_TCP_PRESET_RSP_LENGTH 8
+
+#define _MODBUS_TCP_CHECKSUM_LENGTH 0
+
+/* In both structures, the transaction ID must be placed on first position
+ to have a quick access not dependent of the TCP backend */
+typedef struct _modbus_tcp {
+ /* Extract from MODBUS Messaging on TCP/IP Implementation Guide V1.0b
+ (page 23/46):
+ The transaction identifier is used to associate the future response
+ with the request. This identifier is unique on each TCP connection. */
+ uint16_t t_id;
+ /* TCP port */
+ int port;
+ /* IP address */
+ char ip[16];
+} modbus_tcp_t;
+
+typedef struct _modbus_tcp_pi {
+ /* Transaction ID */
+ uint16_t t_id;
+ /* TCP port */
+ int port;
+ /* Node */
+ char *node;
+ /* Service */
+ char *service;
+} modbus_tcp_pi_t;
+
+#endif /* MODBUS_TCP_PRIVATE_H */
diff --git a/applications/examples/qt5ModbusExample/libmodbus/modbus-tcp.c b/applications/examples/qt5ModbusExample/libmodbus/modbus-tcp.c
new file mode 100644
index 0000000..8e5e37c
--- /dev/null
+++ b/applications/examples/qt5ModbusExample/libmodbus/modbus-tcp.c
@@ -0,0 +1,1004 @@
+/*
+ * Copyright © Stéphane Raimbault
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+// clang-format off
+#if defined(_WIN32)
+# define OS_WIN32
+/* ws2_32.dll has getaddrinfo and freeaddrinfo on Windows XP and later.
+ * minwg32 headers check WINVER before allowing the use of these */
+# ifndef WINVER
+# define WINVER 0x0501
+# endif
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+#ifndef _MSC_VER
+#include
+#endif
+#include
+#include
+
+#if defined(_WIN32)
+/* Already set in modbus-tcp.h but it seems order matters in VS2005 */
+# include
+# include
+# define SHUT_RDWR 2
+# define close closesocket
+# define strdup _strdup
+#else
+# include
+# include
+
+#if defined(__OpenBSD__) || (defined(__FreeBSD__) && __FreeBSD__ < 5)
+# define OS_BSD
+# include
+#endif
+
+#ifdef HAVE_NETINET_IN_H
+#include
+#endif /* HAVE_NETINET_IN_H */
+#ifdef HAVE_NETINET_IP_H
+#include
+#endif /* HAVE_NETINET_IP_H */
+# include
+# include
+# include
+#endif
+
+#if !defined(MSG_NOSIGNAL)
+#define MSG_NOSIGNAL 0
+#endif
+
+#if defined(_AIX) && !defined(MSG_DONTWAIT)
+#define MSG_DONTWAIT MSG_NONBLOCK
+#endif
+// clang-format on
+
+#include "modbus-private.h"
+
+#include "modbus-tcp-private.h"
+#include "modbus-tcp.h"
+
+#ifdef OS_WIN32
+static int _modbus_tcp_init_win32(void)
+{
+ /* Initialise Windows Socket API */
+ WSADATA wsaData;
+
+ if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
+ fprintf(stderr,
+ "WSAStartup() returned error code %d\n",
+ (unsigned int) GetLastError());
+ errno = EIO;
+ return -1;
+ }
+ return 0;
+}
+#endif
+
+static int _modbus_set_slave(modbus_t *ctx, int slave)
+{
+ int max_slave = (ctx->quirks & MODBUS_QUIRK_MAX_SLAVE) ? 255 : 247;
+
+ /* Broadcast address is 0 (MODBUS_BROADCAST_ADDRESS) */
+ if (slave >= 0 && slave <= max_slave) {
+ ctx->slave = slave;
+ } else if (slave == MODBUS_TCP_SLAVE) {
+ /* The special value MODBUS_TCP_SLAVE (0xFF) can be used in TCP mode to
+ * restore the default value. */
+ ctx->slave = slave;
+ } else {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Builds a TCP request header */
+static int _modbus_tcp_build_request_basis(
+ modbus_t *ctx, int function, int addr, int nb, uint8_t *req)
+{
+ modbus_tcp_t *ctx_tcp = ctx->backend_data;
+
+ /* Increase transaction ID */
+ if (ctx_tcp->t_id < UINT16_MAX)
+ ctx_tcp->t_id++;
+ else
+ ctx_tcp->t_id = 0;
+ req[0] = ctx_tcp->t_id >> 8;
+ req[1] = ctx_tcp->t_id & 0x00ff;
+
+ /* Protocol Modbus */
+ req[2] = 0;
+ req[3] = 0;
+
+ /* Length will be defined later by set_req_length_tcp at offsets 4
+ and 5 */
+
+ req[6] = ctx->slave;
+ req[7] = function;
+ req[8] = addr >> 8;
+ req[9] = addr & 0x00ff;
+ req[10] = nb >> 8;
+ req[11] = nb & 0x00ff;
+
+ return _MODBUS_TCP_PRESET_REQ_LENGTH;
+}
+
+/* Builds a TCP response header */
+static int _modbus_tcp_build_response_basis(sft_t *sft, uint8_t *rsp)
+{
+ /* Extract from MODBUS Messaging on TCP/IP Implementation
+ Guide V1.0b (page 23/46):
+ The transaction identifier is used to associate the future
+ response with the request. */
+ rsp[0] = sft->t_id >> 8;
+ rsp[1] = sft->t_id & 0x00ff;
+
+ /* Protocol Modbus */
+ rsp[2] = 0;
+ rsp[3] = 0;
+
+ /* Length will be set later by send_msg (4 and 5) */
+
+ /* The slave ID is copied from the indication */
+ rsp[6] = sft->slave;
+ rsp[7] = sft->function;
+
+ return _MODBUS_TCP_PRESET_RSP_LENGTH;
+}
+
+static int _modbus_tcp_get_response_tid(const uint8_t *req)
+{
+ return (req[0] << 8) + req[1];
+}
+
+static int _modbus_tcp_send_msg_pre(uint8_t *req, int req_length)
+{
+ /* Subtract the header length to the message length */
+ int mbap_length = req_length - 6;
+
+ req[4] = mbap_length >> 8;
+ req[5] = mbap_length & 0x00FF;
+
+ return req_length;
+}
+
+static ssize_t _modbus_tcp_send(modbus_t *ctx, const uint8_t *req, int req_length)
+{
+ /* MSG_NOSIGNAL
+ Requests not to send SIGPIPE on errors on stream oriented
+ sockets when the other end breaks the connection. The EPIPE
+ error is still returned. */
+ return send(ctx->s, (const char *) req, req_length, MSG_NOSIGNAL);
+}
+
+static int _modbus_tcp_receive(modbus_t *ctx, uint8_t *req)
+{
+ return _modbus_receive_msg(ctx, req, MSG_INDICATION);
+}
+
+static ssize_t _modbus_tcp_recv(modbus_t *ctx, uint8_t *rsp, int rsp_length)
+{
+ return recv(ctx->s, (char *) rsp, rsp_length, 0);
+}
+
+static int _modbus_tcp_check_integrity(modbus_t *ctx, uint8_t *msg, const int msg_length)
+{
+ return msg_length;
+}
+
+static int _modbus_tcp_pre_check_confirmation(modbus_t *ctx,
+ const uint8_t *req,
+ const uint8_t *rsp,
+ int rsp_length)
+{
+ unsigned int protocol_id;
+ /* Check transaction ID */
+ if (req[0] != rsp[0] || req[1] != rsp[1]) {
+ if (ctx->debug) {
+ fprintf(stderr,
+ "Invalid transaction ID received 0x%X (not 0x%X)\n",
+ (rsp[0] << 8) + rsp[1],
+ (req[0] << 8) + req[1]);
+ }
+ errno = EMBBADDATA;
+ return -1;
+ }
+
+ /* Check protocol ID */
+ protocol_id = (rsp[2] << 8) + rsp[3];
+ if (protocol_id != 0x0) {
+ if (ctx->debug) {
+ fprintf(stderr, "Invalid protocol ID received 0x%X (not 0x0)\n", protocol_id);
+ }
+ errno = EMBBADDATA;
+ return -1;
+ }
+
+ return 0;
+}
+
+static int _modbus_tcp_set_ipv4_options(int s)
+{
+ int rc;
+ int option;
+
+ /* Set the TCP no delay flag */
+ /* SOL_TCP = IPPROTO_TCP */
+ option = 1;
+ rc = setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &option, sizeof(int));
+ if (rc == -1) {
+ return -1;
+ }
+
+ /* If the OS does not offer SOCK_NONBLOCK, fall back to setting FIONBIO to
+ * make sockets non-blocking */
+ /* Do not care about the return value, this is optional */
+#if !defined(SOCK_NONBLOCK) && defined(FIONBIO)
+#ifdef OS_WIN32
+ {
+ /* Setting FIONBIO expects an unsigned long according to MSDN */
+ u_long loption = 1;
+ ioctlsocket(s, FIONBIO, &loption);
+ }
+#else
+ option = 1;
+ ioctl(s, FIONBIO, &option);
+#endif
+#endif
+
+#ifndef OS_WIN32
+ /**
+ * Cygwin defines IPTOS_LOWDELAY but can't handle that flag so it's
+ * necessary to workaround that problem.
+ **/
+ /* Set the IP low delay option */
+ option = IPTOS_LOWDELAY;
+ rc = setsockopt(s, IPPROTO_IP, IP_TOS, &option, sizeof(int));
+ if (rc == -1) {
+ return -1;
+ }
+#endif
+
+ return 0;
+}
+
+static int _connect(int sockfd,
+ const struct sockaddr *addr,
+ socklen_t addrlen,
+ const struct timeval *ro_tv)
+{
+ int rc = connect(sockfd, addr, addrlen);
+
+#ifdef OS_WIN32
+ int wsaError = 0;
+ if (rc == -1) {
+ wsaError = WSAGetLastError();
+ }
+
+ if (wsaError == WSAEWOULDBLOCK || wsaError == WSAEINPROGRESS) {
+#else
+ if (rc == -1 && errno == EINPROGRESS) {
+#endif
+ fd_set wset;
+ int optval;
+ socklen_t optlen = sizeof(optval);
+ struct timeval tv = *ro_tv;
+
+ /* Wait to be available in writing */
+ FD_ZERO(&wset);
+ FD_SET(sockfd, &wset);
+ rc = select(sockfd + 1, NULL, &wset, NULL, &tv);
+ if (rc < 0) {
+ /* Fail */
+ return -1;
+ }
+
+ if (rc == 0) {
+ /* Timeout */
+ errno = ETIMEDOUT;
+ return -1;
+ }
+
+ /* The connection is established if SO_ERROR and optval are set to 0 */
+ rc = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void *) &optval, &optlen);
+ if (rc == 0 && optval == 0) {
+ return 0;
+ } else {
+ errno = ECONNREFUSED;
+ return -1;
+ }
+ }
+ return rc;
+}
+
+/* Establishes a modbus TCP connection with a Modbus server. */
+static int _modbus_tcp_connect(modbus_t *ctx)
+{
+ int rc;
+ /* Specialized version of sockaddr for Internet socket address (same size) */
+ struct sockaddr_in addr;
+ modbus_tcp_t *ctx_tcp = ctx->backend_data;
+ int flags = SOCK_STREAM;
+
+#ifdef OS_WIN32
+ if (_modbus_tcp_init_win32() == -1) {
+ return -1;
+ }
+#endif
+
+#ifdef SOCK_CLOEXEC
+ flags |= SOCK_CLOEXEC;
+#endif
+
+#ifdef SOCK_NONBLOCK
+ flags |= SOCK_NONBLOCK;
+#endif
+
+ ctx->s = socket(PF_INET, flags, 0);
+ if (ctx->s < 0) {
+ return -1;
+ }
+
+ rc = _modbus_tcp_set_ipv4_options(ctx->s);
+ if (rc == -1) {
+ close(ctx->s);
+ ctx->s = -1;
+ return -1;
+ }
+
+ if (ctx->debug) {
+ printf("Connecting to %s:%d\n", ctx_tcp->ip, ctx_tcp->port);
+ }
+
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(ctx_tcp->port);
+ rc = inet_pton(addr.sin_family, ctx_tcp->ip, &(addr.sin_addr));
+ if (rc <= 0) {
+ if (ctx->debug) {
+ fprintf(stderr, "Invalid IP address: %s\n", ctx_tcp->ip);
+ }
+ close(ctx->s);
+ ctx->s = -1;
+ return -1;
+ }
+
+ rc =
+ _connect(ctx->s, (struct sockaddr *) &addr, sizeof(addr), &ctx->response_timeout);
+ if (rc == -1) {
+ close(ctx->s);
+ ctx->s = -1;
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Establishes a modbus TCP PI connection with a Modbus server. */
+static int _modbus_tcp_pi_connect(modbus_t *ctx)
+{
+ int rc;
+ struct addrinfo *ai_list;
+ struct addrinfo *ai_ptr;
+ struct addrinfo ai_hints;
+ modbus_tcp_pi_t *ctx_tcp_pi = ctx->backend_data;
+
+#ifdef OS_WIN32
+ if (_modbus_tcp_init_win32() == -1) {
+ return -1;
+ }
+#endif
+
+ memset(&ai_hints, 0, sizeof(ai_hints));
+#ifdef AI_ADDRCONFIG
+ ai_hints.ai_flags |= AI_ADDRCONFIG;
+#endif
+ ai_hints.ai_family = AF_UNSPEC;
+ ai_hints.ai_socktype = SOCK_STREAM;
+ ai_hints.ai_addr = NULL;
+ ai_hints.ai_canonname = NULL;
+ ai_hints.ai_next = NULL;
+
+ ai_list = NULL;
+ rc = getaddrinfo(ctx_tcp_pi->node, ctx_tcp_pi->service, &ai_hints, &ai_list);
+ if (rc != 0) {
+ if (ctx->debug) {
+#ifdef HAVE_GAI_STRERROR
+ fprintf(stderr, "Error returned by getaddrinfo: %s\n", gai_strerror(rc));
+#else
+ fprintf(stderr, "Error returned by getaddrinfo: %d\n", rc);
+#endif
+ }
+ freeaddrinfo(ai_list);
+ errno = ECONNREFUSED;
+ return -1;
+ }
+
+ for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next) {
+ int flags = ai_ptr->ai_socktype;
+ int s;
+
+#ifdef SOCK_CLOEXEC
+ flags |= SOCK_CLOEXEC;
+#endif
+
+#ifdef SOCK_NONBLOCK
+ flags |= SOCK_NONBLOCK;
+#endif
+
+ s = socket(ai_ptr->ai_family, flags, ai_ptr->ai_protocol);
+ if (s < 0)
+ continue;
+
+ if (ai_ptr->ai_family == AF_INET)
+ _modbus_tcp_set_ipv4_options(s);
+
+ if (ctx->debug) {
+ printf("Connecting to [%s]:%s\n", ctx_tcp_pi->node, ctx_tcp_pi->service);
+ }
+
+ rc = _connect(s, ai_ptr->ai_addr, ai_ptr->ai_addrlen, &ctx->response_timeout);
+ if (rc == -1) {
+ close(s);
+ continue;
+ }
+
+ ctx->s = s;
+ break;
+ }
+
+ freeaddrinfo(ai_list);
+
+ if (ctx->s < 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static unsigned int _modbus_tcp_is_connected(modbus_t *ctx)
+{
+ return ctx->s >= 0;
+}
+
+/* Closes the network connection and socket in TCP mode */
+static void _modbus_tcp_close(modbus_t *ctx)
+{
+ if (ctx->s >= 0) {
+ shutdown(ctx->s, SHUT_RDWR);
+ close(ctx->s);
+ ctx->s = -1;
+ }
+}
+
+static int _modbus_tcp_flush(modbus_t *ctx)
+{
+ int rc;
+ // Use an unsigned 16-bit integer to reduce overflow risk. The flush function
+ // is not expected to handle huge amounts of data (> 2GB).
+ uint16_t rc_sum = 0;
+
+ do {
+ /* Extract the garbage from the socket */
+ char devnull[MODBUS_TCP_MAX_ADU_LENGTH];
+#ifndef OS_WIN32
+ rc = recv(ctx->s, devnull, MODBUS_TCP_MAX_ADU_LENGTH, MSG_DONTWAIT);
+#else
+ /* On Win32, it's a bit more complicated to not wait */
+ fd_set rset;
+ struct timeval tv;
+
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ FD_ZERO(&rset);
+ FD_SET(ctx->s, &rset);
+ rc = select(ctx->s + 1, &rset, NULL, NULL, &tv);
+ if (rc == -1) {
+ return -1;
+ }
+
+ if (rc == 1) {
+ /* There is data to flush */
+ rc = recv(ctx->s, devnull, MODBUS_TCP_MAX_ADU_LENGTH, 0);
+ }
+#endif
+ if (rc > 0) {
+ // Check for overflow before adding
+ if (rc_sum <= UINT16_MAX - rc) {
+ rc_sum += rc;
+ } else {
+ // Handle overflow
+ ctx->error_recovery = MODBUS_ERROR_RECOVERY_PROTOCOL;
+ errno = EOVERFLOW;
+ return -1;
+ }
+ }
+ } while (rc == MODBUS_TCP_MAX_ADU_LENGTH);
+
+ return rc_sum;
+}
+
+/* Listens for any request from one or many modbus masters in TCP */
+int modbus_tcp_listen(modbus_t *ctx, int nb_connection)
+{
+ int new_s;
+ int enable;
+ int flags;
+ struct sockaddr_in addr;
+ modbus_tcp_t *ctx_tcp;
+ int rc;
+
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ ctx_tcp = ctx->backend_data;
+
+#ifdef OS_WIN32
+ if (_modbus_tcp_init_win32() == -1) {
+ return -1;
+ }
+#endif
+
+ flags = SOCK_STREAM;
+
+#ifdef SOCK_CLOEXEC
+ flags |= SOCK_CLOEXEC;
+#endif
+
+ new_s = socket(PF_INET, flags, IPPROTO_TCP);
+ if (new_s == -1) {
+ return -1;
+ }
+
+ enable = 1;
+ if (setsockopt(new_s, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) == -1) {
+ close(new_s);
+ return -1;
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ /* If the modbus port is < to 1024, we need the setuid root. */
+ addr.sin_port = htons(ctx_tcp->port);
+ if (ctx_tcp->ip[0] == '0') {
+ /* Listen any addresses */
+ addr.sin_addr.s_addr = htonl(INADDR_ANY);
+ } else {
+ /* Listen only specified IP address */
+ rc = inet_pton(addr.sin_family, ctx_tcp->ip, &(addr.sin_addr));
+ if (rc <= 0) {
+ if (ctx->debug) {
+ fprintf(stderr, "Invalid IP address: %s\n", ctx_tcp->ip);
+ }
+ close(new_s);
+ return -1;
+ }
+ }
+
+ if (bind(new_s, (struct sockaddr *) &addr, sizeof(addr)) == -1) {
+ close(new_s);
+ return -1;
+ }
+
+ if (listen(new_s, nb_connection) == -1) {
+ close(new_s);
+ return -1;
+ }
+
+ return new_s;
+}
+
+int modbus_tcp_pi_listen(modbus_t *ctx, int nb_connection)
+{
+ int rc;
+ struct addrinfo *ai_list;
+ struct addrinfo *ai_ptr;
+ struct addrinfo ai_hints;
+ const char *node;
+ const char *service;
+ int new_s;
+ modbus_tcp_pi_t *ctx_tcp_pi;
+
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ ctx_tcp_pi = ctx->backend_data;
+
+#ifdef OS_WIN32
+ if (_modbus_tcp_init_win32() == -1) {
+ return -1;
+ }
+#endif
+
+ if (ctx_tcp_pi->node[0] == 0) {
+ node = NULL; /* == any */
+ } else {
+ node = ctx_tcp_pi->node;
+ }
+
+ if (ctx_tcp_pi->service[0] == 0) {
+ service = "502";
+ } else {
+ service = ctx_tcp_pi->service;
+ }
+
+ memset(&ai_hints, 0, sizeof(ai_hints));
+ /* If node is not NULL, than the AI_PASSIVE flag is ignored. */
+ ai_hints.ai_flags |= AI_PASSIVE;
+#ifdef AI_ADDRCONFIG
+ ai_hints.ai_flags |= AI_ADDRCONFIG;
+#endif
+ ai_hints.ai_family = AF_UNSPEC;
+ ai_hints.ai_socktype = SOCK_STREAM;
+ ai_hints.ai_addr = NULL;
+ ai_hints.ai_canonname = NULL;
+ ai_hints.ai_next = NULL;
+
+ ai_list = NULL;
+ rc = getaddrinfo(node, service, &ai_hints, &ai_list);
+ if (rc != 0) {
+ if (ctx->debug) {
+#ifdef HAVE_GAI_STRERROR
+ fprintf(stderr, "Error returned by getaddrinfo: %s\n", gai_strerror(rc));
+#else
+ fprintf(stderr, "Error returned by getaddrinfo: %d\n", rc);
+#endif
+ }
+ freeaddrinfo(ai_list);
+ errno = ECONNREFUSED;
+ return -1;
+ }
+
+ new_s = -1;
+ for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next) {
+ int flags = ai_ptr->ai_socktype;
+ int s;
+
+#ifdef SOCK_CLOEXEC
+ flags |= SOCK_CLOEXEC;
+#endif
+
+ s = socket(ai_ptr->ai_family, flags, ai_ptr->ai_protocol);
+ if (s < 0) {
+ if (ctx->debug) {
+ perror("socket");
+ }
+ continue;
+ } else {
+ int enable = 1;
+ rc = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable));
+ if (rc != 0) {
+ close(s);
+ if (ctx->debug) {
+ perror("setsockopt");
+ }
+ continue;
+ }
+ }
+
+ rc = bind(s, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
+ if (rc != 0) {
+ close(s);
+ if (ctx->debug) {
+ perror("bind");
+ }
+ continue;
+ }
+
+ rc = listen(s, nb_connection);
+ if (rc != 0) {
+ close(s);
+ if (ctx->debug) {
+ perror("listen");
+ }
+ continue;
+ }
+
+ new_s = s;
+ break;
+ }
+ freeaddrinfo(ai_list);
+
+ if (new_s < 0) {
+ return -1;
+ }
+
+ return new_s;
+}
+
+int modbus_tcp_accept(modbus_t *ctx, int *s)
+{
+ struct sockaddr_in addr;
+ socklen_t addrlen;
+
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ addrlen = sizeof(addr);
+#ifdef HAVE_ACCEPT4
+ /* Inherit socket flags and use accept4 call */
+ ctx->s = accept4(*s, (struct sockaddr *) &addr, &addrlen, SOCK_CLOEXEC);
+#else
+ ctx->s = accept(*s, (struct sockaddr *) &addr, &addrlen);
+#endif
+
+ if (ctx->s < 0) {
+ return -1;
+ }
+
+ if (ctx->debug) {
+ char buf[INET_ADDRSTRLEN];
+ if (inet_ntop(AF_INET, &(addr.sin_addr), buf, INET_ADDRSTRLEN) == NULL) {
+ fprintf(stderr, "Client connection accepted from unparsable IP.\n");
+ } else {
+ printf("Client connection accepted from %s.\n", buf);
+ }
+ }
+
+ return ctx->s;
+}
+
+int modbus_tcp_pi_accept(modbus_t *ctx, int *s)
+{
+ struct sockaddr_in6 addr;
+ socklen_t addrlen;
+
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ addrlen = sizeof(addr);
+#ifdef HAVE_ACCEPT4
+ /* Inherit socket flags and use accept4 call */
+ ctx->s = accept4(*s, (struct sockaddr *) &addr, &addrlen, SOCK_CLOEXEC);
+#else
+ ctx->s = accept(*s, (struct sockaddr *) &addr, &addrlen);
+#endif
+
+ if (ctx->s < 0) {
+ return -1;
+ }
+
+ if (ctx->debug) {
+ char buf[INET6_ADDRSTRLEN];
+ if (inet_ntop(AF_INET6, &(addr.sin6_addr), buf, INET6_ADDRSTRLEN) == NULL) {
+ fprintf(stderr, "Client connection accepted from unparsable IP.\n");
+ } else {
+ printf("Client connection accepted from %s.\n", buf);
+ }
+ }
+
+ return ctx->s;
+}
+
+static int
+_modbus_tcp_select(modbus_t *ctx, fd_set *rset, struct timeval *tv, int length_to_read)
+{
+ int s_rc;
+ 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) {
+ errno = ETIMEDOUT;
+ return -1;
+ }
+
+ return s_rc;
+}
+
+static void _modbus_tcp_free(modbus_t *ctx)
+{
+ if (ctx->backend_data) {
+ free(ctx->backend_data);
+ }
+ free(ctx);
+}
+
+static void _modbus_tcp_pi_free(modbus_t *ctx)
+{
+ if (ctx->backend_data) {
+ modbus_tcp_pi_t *ctx_tcp_pi = ctx->backend_data;
+ free(ctx_tcp_pi->node);
+ free(ctx_tcp_pi->service);
+ free(ctx->backend_data);
+ }
+
+ free(ctx);
+}
+
+// clang-format off
+const modbus_backend_t _modbus_tcp_backend = {
+ _MODBUS_BACKEND_TYPE_TCP,
+ _MODBUS_TCP_HEADER_LENGTH,
+ _MODBUS_TCP_CHECKSUM_LENGTH,
+ MODBUS_TCP_MAX_ADU_LENGTH,
+ _modbus_set_slave,
+ _modbus_tcp_build_request_basis,
+ _modbus_tcp_build_response_basis,
+ _modbus_tcp_get_response_tid,
+ _modbus_tcp_send_msg_pre,
+ _modbus_tcp_send,
+ _modbus_tcp_receive,
+ _modbus_tcp_recv,
+ _modbus_tcp_check_integrity,
+ _modbus_tcp_pre_check_confirmation,
+ _modbus_tcp_connect,
+ _modbus_tcp_is_connected,
+ _modbus_tcp_close,
+ _modbus_tcp_flush,
+ _modbus_tcp_select,
+ _modbus_tcp_free
+};
+
+const modbus_backend_t _modbus_tcp_pi_backend = {
+ _MODBUS_BACKEND_TYPE_TCP,
+ _MODBUS_TCP_HEADER_LENGTH,
+ _MODBUS_TCP_CHECKSUM_LENGTH,
+ MODBUS_TCP_MAX_ADU_LENGTH,
+ _modbus_set_slave,
+ _modbus_tcp_build_request_basis,
+ _modbus_tcp_build_response_basis,
+ _modbus_tcp_get_response_tid,
+ _modbus_tcp_send_msg_pre,
+ _modbus_tcp_send,
+ _modbus_tcp_receive,
+ _modbus_tcp_recv,
+ _modbus_tcp_check_integrity,
+ _modbus_tcp_pre_check_confirmation,
+ _modbus_tcp_pi_connect,
+ _modbus_tcp_is_connected,
+ _modbus_tcp_close,
+ _modbus_tcp_flush,
+ _modbus_tcp_select,
+ _modbus_tcp_pi_free
+};
+
+// clang-format on
+
+modbus_t *modbus_new_tcp(const char *ip, int port)
+{
+ modbus_t *ctx;
+ modbus_tcp_t *ctx_tcp;
+ size_t dest_size;
+ size_t ret_size;
+
+#if defined(OS_BSD)
+ /* MSG_NOSIGNAL is unsupported on *BSD so we install an ignore
+ handler for SIGPIPE. */
+ struct sigaction sa;
+
+ sa.sa_handler = SIG_IGN;
+ if (sigaction(SIGPIPE, &sa, NULL) < 0) {
+ /* The debug flag can't be set here... */
+ fprintf(stderr, "Could not install SIGPIPE handler.\n");
+ return NULL;
+ }
+#endif
+
+ ctx = (modbus_t *) malloc(sizeof(modbus_t));
+ if (ctx == NULL) {
+ return NULL;
+ }
+ _modbus_init_common(ctx);
+
+ /* Could be changed after to reach a remote serial Modbus device */
+ ctx->slave = MODBUS_TCP_SLAVE;
+
+ ctx->backend = &_modbus_tcp_backend;
+
+ ctx->backend_data = (modbus_tcp_t *) malloc(sizeof(modbus_tcp_t));
+ if (ctx->backend_data == NULL) {
+ modbus_free(ctx);
+ errno = ENOMEM;
+ return NULL;
+ }
+ ctx_tcp = (modbus_tcp_t *) ctx->backend_data;
+
+ if (ip != NULL) {
+ dest_size = sizeof(char) * 16;
+ ret_size = strlcpy(ctx_tcp->ip, ip, dest_size);
+ if (ret_size == 0) {
+ fprintf(stderr, "The IP string is empty\n");
+ modbus_free(ctx);
+ errno = EINVAL;
+ return NULL;
+ }
+
+ if (ret_size >= dest_size) {
+ fprintf(stderr, "The IP string has been truncated\n");
+ modbus_free(ctx);
+ errno = EINVAL;
+ return NULL;
+ }
+ } else {
+ ctx_tcp->ip[0] = '0';
+ }
+ ctx_tcp->port = port;
+ ctx_tcp->t_id = 0;
+
+ return ctx;
+}
+
+modbus_t *modbus_new_tcp_pi(const char *node, const char *service)
+{
+ modbus_t *ctx;
+ modbus_tcp_pi_t *ctx_tcp_pi;
+
+ ctx = (modbus_t *) malloc(sizeof(modbus_t));
+ if (ctx == NULL) {
+ return NULL;
+ }
+ _modbus_init_common(ctx);
+
+ /* Could be changed after to reach a remote serial Modbus device */
+ ctx->slave = MODBUS_TCP_SLAVE;
+
+ ctx->backend = &_modbus_tcp_pi_backend;
+
+ ctx->backend_data = (modbus_tcp_pi_t *) malloc(sizeof(modbus_tcp_pi_t));
+ if (ctx->backend_data == NULL) {
+ modbus_free(ctx);
+ errno = ENOMEM;
+ return NULL;
+ }
+ ctx_tcp_pi = (modbus_tcp_pi_t *) ctx->backend_data;
+ ctx_tcp_pi->node = NULL;
+ ctx_tcp_pi->service = NULL;
+
+ if (node != NULL) {
+ ctx_tcp_pi->node = strdup(node);
+ } else {
+ /* The node argument can be empty to indicate any hosts */
+ ctx_tcp_pi->node = strdup("");
+ }
+
+ if (ctx_tcp_pi->node == NULL) {
+ modbus_free(ctx);
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ if (service != NULL && service[0] != '\0') {
+ ctx_tcp_pi->service = strdup(service);
+ } else {
+ /* Default Modbus port number */
+ ctx_tcp_pi->service = strdup("502");
+ }
+
+ if (ctx_tcp_pi->service == NULL) {
+ modbus_free(ctx);
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ ctx_tcp_pi->t_id = 0;
+
+ return ctx;
+}
diff --git a/applications/examples/qt5ModbusExample/libmodbus/modbus-tcp.h b/applications/examples/qt5ModbusExample/libmodbus/modbus-tcp.h
new file mode 100644
index 0000000..768d38c
--- /dev/null
+++ b/applications/examples/qt5ModbusExample/libmodbus/modbus-tcp.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright © Stéphane Raimbault
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef MODBUS_TCP_H
+#define MODBUS_TCP_H
+
+#include "modbus.h"
+
+MODBUS_BEGIN_DECLS
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
+/* Win32 with MinGW, supplement to */
+#include
+#if !defined(ECONNRESET)
+#define ECONNRESET WSAECONNRESET
+#endif
+#if !defined(ECONNREFUSED)
+#define ECONNREFUSED WSAECONNREFUSED
+#endif
+#if !defined(ETIMEDOUT)
+#define ETIMEDOUT WSAETIMEDOUT
+#endif
+#if !defined(ENOPROTOOPT)
+#define ENOPROTOOPT WSAENOPROTOOPT
+#endif
+#if !defined(EINPROGRESS)
+#define EINPROGRESS WSAEINPROGRESS
+#endif
+#endif
+
+#define MODBUS_TCP_DEFAULT_PORT 502
+#define MODBUS_TCP_SLAVE 0xFF
+
+/* Modbus_Application_Protocol_V1_1b.pdf Chapter 4 Section 1 Page 5
+ * TCP MODBUS ADU = 253 bytes + MBAP (7 bytes) = 260 bytes
+ */
+#define MODBUS_TCP_MAX_ADU_LENGTH 260
+
+MODBUS_API modbus_t *modbus_new_tcp(const char *ip_address, int port);
+MODBUS_API int modbus_tcp_listen(modbus_t *ctx, int nb_connection);
+MODBUS_API int modbus_tcp_accept(modbus_t *ctx, int *s);
+
+MODBUS_API modbus_t *modbus_new_tcp_pi(const char *node, const char *service);
+MODBUS_API int modbus_tcp_pi_listen(modbus_t *ctx, int nb_connection);
+MODBUS_API int modbus_tcp_pi_accept(modbus_t *ctx, int *s);
+
+MODBUS_END_DECLS
+
+#endif /* MODBUS_TCP_H */
diff --git a/applications/examples/qt5ModbusExample/libmodbus/modbus-version.h b/applications/examples/qt5ModbusExample/libmodbus/modbus-version.h
new file mode 100644
index 0000000..72939ec
--- /dev/null
+++ b/applications/examples/qt5ModbusExample/libmodbus/modbus-version.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright © Stéphane Raimbault
+ *
+ * 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_VERSION_H
+#define MODBUS_VERSION_H
+
+/* The major version, (1, if %LIBMODBUS_VERSION is 1.2.3) */
+#define LIBMODBUS_VERSION_MAJOR (3)
+
+/* The minor version (2, if %LIBMODBUS_VERSION is 1.2.3) */
+#define LIBMODBUS_VERSION_MINOR (1)
+
+/* The micro version (3, if %LIBMODBUS_VERSION is 1.2.3) */
+#define LIBMODBUS_VERSION_MICRO (11)
+
+/* The full version, like 1.2.3 */
+#define LIBMODBUS_VERSION 3.1.11
+
+/* The full version, in string form (suited for string concatenation)
+ */
+#define LIBMODBUS_VERSION_STRING "3.1.11"
+
+/* Numerically encoded version, eg. v1.2.3 is 0x010203 */
+#define LIBMODBUS_VERSION_HEX \
+ ((LIBMODBUS_VERSION_MAJOR << 16) | (LIBMODBUS_VERSION_MINOR << 8) | \
+ (LIBMODBUS_VERSION_MICRO << 0))
+
+/* Evaluates to True if the version is greater than @major, @minor and @micro
+ */
+#define LIBMODBUS_VERSION_CHECK(major, minor, micro) \
+ (LIBMODBUS_VERSION_MAJOR > (major) || \
+ (LIBMODBUS_VERSION_MAJOR == (major) && LIBMODBUS_VERSION_MINOR > (minor)) || \
+ (LIBMODBUS_VERSION_MAJOR == (major) && LIBMODBUS_VERSION_MINOR == (minor) && \
+ LIBMODBUS_VERSION_MICRO >= (micro)))
+
+#endif /* MODBUS_VERSION_H */
diff --git a/applications/examples/qt5ModbusExample/libmodbus/modbus.c b/applications/examples/qt5ModbusExample/libmodbus/modbus.c
new file mode 100644
index 0000000..9f9f4ae
--- /dev/null
+++ b/applications/examples/qt5ModbusExample/libmodbus/modbus.c
@@ -0,0 +1,2128 @@
+/*
+ * Copyright © Stéphane Raimbault
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library implements the Modbus protocol.
+ * http://libmodbus.org/
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#ifndef _MSC_VER
+#include
+#endif
+
+#include "libmodbus/config.h"
+
+#include "modbus-private.h"
+#include "modbus.h"
+
+/* Internal use */
+#define MSG_LENGTH_UNDEFINED -1
+
+/* Exported version */
+const unsigned int libmodbus_version_major = LIBMODBUS_VERSION_MAJOR;
+const unsigned int libmodbus_version_minor = LIBMODBUS_VERSION_MINOR;
+const unsigned int libmodbus_version_micro = LIBMODBUS_VERSION_MICRO;
+
+/* Max between RTU and TCP max adu length (so TCP) */
+#define MAX_MESSAGE_LENGTH 260
+
+/* 3 steps are used to parse the query */
+typedef enum {
+ _STEP_FUNCTION,
+ _STEP_META,
+ _STEP_DATA
+} _step_t;
+
+const char *modbus_strerror(int errnum)
+{
+ switch (errnum) {
+ case EMBXILFUN:
+ return "Illegal function";
+ case EMBXILADD:
+ return "Illegal data address";
+ case EMBXILVAL:
+ return "Illegal data value";
+ case EMBXSFAIL:
+ return "Slave device or server failure";
+ case EMBXACK:
+ return "Acknowledge";
+ case EMBXSBUSY:
+ return "Slave device or server is busy";
+ case EMBXNACK:
+ return "Negative acknowledge";
+ case EMBXMEMPAR:
+ return "Memory parity error";
+ case EMBXGPATH:
+ return "Gateway path unavailable";
+ case EMBXGTAR:
+ return "Target device failed to respond";
+ case EMBBADCRC:
+ return "Invalid CRC";
+ case EMBBADDATA:
+ return "Invalid data";
+ case EMBBADEXC:
+ return "Invalid exception code";
+ case EMBMDATA:
+ return "Too many data";
+ case EMBBADSLAVE:
+ return "Response not from requested slave";
+ default:
+ return strerror(errnum);
+ }
+}
+
+void _error_print(modbus_t *ctx, const char *context)
+{
+ if (ctx->debug) {
+ fprintf(stderr, "ERROR %s", modbus_strerror(errno));
+ if (context != NULL) {
+ fprintf(stderr, ": %s\n", context);
+ } else {
+ fprintf(stderr, "\n");
+ }
+ }
+}
+
+static void _sleep_response_timeout(modbus_t *ctx)
+{
+ /* Response timeout is always positive */
+#ifdef _WIN32
+ /* usleep doesn't exist on Windows */
+ Sleep((ctx->response_timeout.tv_sec * 1000) + (ctx->response_timeout.tv_usec / 1000));
+#else
+ /* usleep source code */
+ struct timespec request, remaining;
+ request.tv_sec = ctx->response_timeout.tv_sec;
+ request.tv_nsec = ((long int) ctx->response_timeout.tv_usec) * 1000;
+ while (nanosleep(&request, &remaining) == -1 && errno == EINTR) {
+ request = remaining;
+ }
+#endif
+}
+
+int modbus_flush(modbus_t *ctx)
+{
+ int rc;
+
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ rc = ctx->backend->flush(ctx);
+ if (rc != -1 && ctx->debug) {
+ /* Not all backends are able to return the number of bytes flushed */
+ printf("Bytes flushed (%d)\n", rc);
+ }
+ return rc;
+}
+
+/* Computes the length of the expected response including checksum */
+static unsigned int compute_response_length_from_request(modbus_t *ctx, uint8_t *req)
+{
+ int length;
+ const int offset = ctx->backend->header_length;
+
+ switch (req[offset]) {
+ case MODBUS_FC_READ_COILS:
+ case MODBUS_FC_READ_DISCRETE_INPUTS: {
+ /* Header + nb values (code from write_bits) */
+ int nb = (req[offset + 3] << 8) | req[offset + 4];
+ length = 2 + (nb / 8) + ((nb % 8) ? 1 : 0);
+ } break;
+ case MODBUS_FC_WRITE_AND_READ_REGISTERS:
+ case MODBUS_FC_READ_HOLDING_REGISTERS:
+ case MODBUS_FC_READ_INPUT_REGISTERS:
+ /* Header + 2 * nb values */
+ length = 2 + 2 * (req[offset + 3] << 8 | req[offset + 4]);
+ break;
+ case MODBUS_FC_READ_EXCEPTION_STATUS:
+ length = 3;
+ break;
+ case MODBUS_FC_REPORT_SLAVE_ID:
+ /* The response is device specific (the header provides the
+ length) */
+ return MSG_LENGTH_UNDEFINED;
+ case MODBUS_FC_MASK_WRITE_REGISTER:
+ length = 7;
+ break;
+ default:
+ length = 5;
+ }
+
+ return offset + length + ctx->backend->checksum_length;
+}
+
+/* Sends a request/response */
+static int send_msg(modbus_t *ctx, uint8_t *msg, int msg_length)
+{
+ int rc;
+ int i;
+
+ msg_length = ctx->backend->send_msg_pre(msg, msg_length);
+
+ if (ctx->debug) {
+ for (i = 0; i < msg_length; i++)
+ printf("[%.2X]", msg[i]);
+ printf("\n");
+ }
+
+ /* In recovery mode, the write command will be issued until to be
+ successful! Disabled by default. */
+ do {
+ rc = ctx->backend->send(ctx, msg, msg_length);
+ if (rc == -1) {
+ _error_print(ctx, NULL);
+ if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK) {
+#ifdef _WIN32
+ const int wsa_err = WSAGetLastError();
+ if (wsa_err == WSAENETRESET || wsa_err == WSAENOTCONN ||
+ wsa_err == WSAENOTSOCK || wsa_err == WSAESHUTDOWN ||
+ wsa_err == WSAEHOSTUNREACH || wsa_err == WSAECONNABORTED ||
+ wsa_err == WSAECONNRESET || wsa_err == WSAETIMEDOUT) {
+ modbus_close(ctx);
+ _sleep_response_timeout(ctx);
+ modbus_connect(ctx);
+ } else {
+ _sleep_response_timeout(ctx);
+ modbus_flush(ctx);
+ }
+#else
+ int saved_errno = errno;
+
+ if ((errno == EBADF || errno == ECONNRESET || errno == EPIPE)) {
+ modbus_close(ctx);
+ _sleep_response_timeout(ctx);
+ modbus_connect(ctx);
+ } else {
+ _sleep_response_timeout(ctx);
+ modbus_flush(ctx);
+ }
+ errno = saved_errno;
+#endif
+ }
+ }
+ } while ((ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK) && rc == -1);
+
+ if (rc > 0 && rc != msg_length) {
+ errno = EMBBADDATA;
+ return -1;
+ }
+
+ return rc;
+}
+
+int modbus_send_raw_request_tid(modbus_t *ctx,
+ const uint8_t *raw_req,
+ int raw_req_length,
+ int tid)
+{
+ sft_t sft;
+ uint8_t req[MAX_MESSAGE_LENGTH];
+ int req_length;
+
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (raw_req_length < 2 || raw_req_length > (MODBUS_MAX_PDU_LENGTH + 1)) {
+ /* The raw request must contain function and slave at least and
+ must not be longer than the maximum pdu length plus the slave
+ address. */
+ errno = EINVAL;
+ return -1;
+ }
+
+ sft.slave = raw_req[0];
+ sft.function = raw_req[1];
+ /* The t_id is left to zero */
+ sft.t_id = tid;
+ /* This response function only set the header so it's convenient here */
+ req_length = ctx->backend->build_response_basis(&sft, req);
+
+ if (raw_req_length > 2) {
+ /* Copy data after function code */
+ memcpy(req + req_length, raw_req + 2, raw_req_length - 2);
+ req_length += raw_req_length - 2;
+ }
+
+ return send_msg(ctx, req, req_length);
+}
+
+int modbus_send_raw_request(modbus_t *ctx, const uint8_t *raw_req, int raw_req_length)
+{
+ return modbus_send_raw_request_tid(ctx, raw_req, raw_req_length, 0);
+}
+
+/*
+ * ---------- Request Indication ----------
+ * | Client | ---------------------->| Server |
+ * ---------- Confirmation Response ----------
+ */
+
+/* Computes the length to read after the function received */
+static uint8_t compute_meta_length_after_function(int function, msg_type_t msg_type)
+{
+ int length;
+
+ if (msg_type == MSG_INDICATION) {
+ if (function <= MODBUS_FC_WRITE_SINGLE_REGISTER) {
+ length = 4;
+ } else if (function == MODBUS_FC_WRITE_MULTIPLE_COILS ||
+ function == MODBUS_FC_WRITE_MULTIPLE_REGISTERS) {
+ length = 5;
+ } else if (function == MODBUS_FC_MASK_WRITE_REGISTER) {
+ length = 6;
+ } else if (function == MODBUS_FC_WRITE_AND_READ_REGISTERS) {
+ length = 9;
+ } else {
+ /* MODBUS_FC_READ_EXCEPTION_STATUS, MODBUS_FC_REPORT_SLAVE_ID */
+ length = 0;
+ }
+ } else {
+ /* MSG_CONFIRMATION */
+ switch (function) {
+ case MODBUS_FC_WRITE_SINGLE_COIL:
+ case MODBUS_FC_WRITE_SINGLE_REGISTER:
+ case MODBUS_FC_WRITE_MULTIPLE_COILS:
+ case MODBUS_FC_WRITE_MULTIPLE_REGISTERS:
+ length = 4;
+ break;
+ case MODBUS_FC_MASK_WRITE_REGISTER:
+ length = 6;
+ break;
+ default:
+ length = 1;
+ }
+ }
+
+ return length;
+}
+
+/* Computes the length to read after the meta information (address, count, etc) */
+static int
+compute_data_length_after_meta(modbus_t *ctx, uint8_t *msg, msg_type_t msg_type)
+{
+ int function = msg[ctx->backend->header_length];
+ int length;
+
+ if (msg_type == MSG_INDICATION) {
+ switch (function) {
+ case MODBUS_FC_WRITE_MULTIPLE_COILS:
+ case MODBUS_FC_WRITE_MULTIPLE_REGISTERS:
+ length = msg[ctx->backend->header_length + 5];
+ break;
+ case MODBUS_FC_WRITE_AND_READ_REGISTERS:
+ length = msg[ctx->backend->header_length + 9];
+ break;
+ default:
+ length = 0;
+ }
+ } else {
+ /* MSG_CONFIRMATION */
+ if (function <= MODBUS_FC_READ_INPUT_REGISTERS ||
+ function == MODBUS_FC_REPORT_SLAVE_ID ||
+ function == MODBUS_FC_WRITE_AND_READ_REGISTERS) {
+ length = msg[ctx->backend->header_length + 1];
+ } else {
+ length = 0;
+ }
+ }
+
+ length += ctx->backend->checksum_length;
+
+ return length;
+}
+
+/* Waits a response from a modbus server or a request from a modbus client.
+ This function blocks if there is no replies (3 timeouts).
+
+ The function shall return the number of received characters and the received
+ message in an array of uint8_t if successful. Otherwise it shall return -1
+ and errno is set to one of the values defined below:
+ - ECONNRESET
+ - EMBBADDATA
+ - ETIMEDOUT
+ - read() or recv() error codes
+*/
+
+int _modbus_receive_msg(modbus_t *ctx, uint8_t *msg, msg_type_t msg_type)
+{
+ int rc;
+ fd_set rset;
+ struct timeval tv;
+ struct timeval *p_tv;
+ unsigned int length_to_read;
+ int msg_length = 0;
+ _step_t step;
+#ifdef _WIN32
+ int wsa_err;
+#endif
+
+ if (ctx->debug) {
+ if (msg_type == MSG_INDICATION) {
+ printf("Waiting for an indication...\n");
+ } else {
+ printf("Waiting for a confirmation...\n");
+ }
+ }
+
+ if (!ctx->backend->is_connected(ctx)) {
+ if (ctx->debug) {
+ fprintf(stderr, "ERROR The connection is not established.\n");
+ }
+ return -1;
+ }
+
+ /* Add a file descriptor to the set */
+ FD_ZERO(&rset);
+ FD_SET(ctx->s, &rset);
+
+ /* We need to analyse the message step by step. At the first step, we want
+ * to reach the function code because all packets contain this
+ * information. */
+ step = _STEP_FUNCTION;
+ length_to_read = ctx->backend->header_length + 1;
+
+ if (msg_type == MSG_INDICATION) {
+ /* Wait for a message, we don't know when the message will be received */
+ if (ctx->indication_timeout.tv_sec == 0 && ctx->indication_timeout.tv_usec == 0) {
+ /* By default, the indication timeout isn't set */
+ p_tv = NULL;
+ } else {
+ /* Wait for an indication (name of a received request by a server, see schema)
+ */
+ tv.tv_sec = ctx->indication_timeout.tv_sec;
+ tv.tv_usec = ctx->indication_timeout.tv_usec;
+ p_tv = &tv;
+ }
+ } else {
+ tv.tv_sec = ctx->response_timeout.tv_sec;
+ tv.tv_usec = ctx->response_timeout.tv_usec;
+ p_tv = &tv;
+ }
+
+ while (length_to_read != 0) {
+ rc = ctx->backend->select(ctx, &rset, p_tv, length_to_read);
+ if (rc == -1) {
+ _error_print(ctx, "select");
+ if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK) {
+#ifdef _WIN32
+ wsa_err = WSAGetLastError();
+
+ // no equivalent to ETIMEDOUT when select fails on Windows
+ if (wsa_err == WSAENETDOWN || wsa_err == WSAENOTSOCK) {
+ modbus_close(ctx);
+ modbus_connect(ctx);
+ }
+#else
+ int saved_errno = errno;
+
+ if (errno == ETIMEDOUT) {
+ _sleep_response_timeout(ctx);
+ modbus_flush(ctx);
+ } else if (errno == EBADF) {
+ modbus_close(ctx);
+ modbus_connect(ctx);
+ }
+ errno = saved_errno;
+#endif
+ }
+ return -1;
+ }
+
+ rc = ctx->backend->recv(ctx, msg + msg_length, length_to_read);
+ if (rc == 0) {
+ errno = ECONNRESET;
+ rc = -1;
+ }
+
+ if (rc == -1) {
+ _error_print(ctx, "read");
+#ifdef _WIN32
+ wsa_err = WSAGetLastError();
+ if ((ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK) &&
+ (wsa_err == WSAENOTCONN || wsa_err == WSAENETRESET ||
+ wsa_err == WSAENOTSOCK || wsa_err == WSAESHUTDOWN ||
+ wsa_err == WSAECONNABORTED || wsa_err == WSAETIMEDOUT ||
+ wsa_err == WSAECONNRESET)) {
+ modbus_close(ctx);
+ modbus_connect(ctx);
+ }
+#else
+ if ((ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK) &&
+ (errno == ECONNRESET || errno == ECONNREFUSED || errno == EBADF)) {
+ int saved_errno = errno;
+ modbus_close(ctx);
+ modbus_connect(ctx);
+ /* Could be removed by previous calls */
+ errno = saved_errno;
+ }
+#endif
+ return -1;
+ }
+
+ /* Display the hex code of each character received */
+ if (ctx->debug) {
+ int i;
+ for (i = 0; i < rc; i++)
+ printf("<%.2X>", msg[msg_length + i]);
+ }
+
+ /* Sums bytes received */
+ msg_length += rc;
+ /* Computes remaining bytes */
+ length_to_read -= rc;
+
+ if (length_to_read == 0) {
+ switch (step) {
+ case _STEP_FUNCTION:
+ /* Function code position */
+ length_to_read = compute_meta_length_after_function(
+ msg[ctx->backend->header_length], msg_type);
+ if (length_to_read != 0) {
+ step = _STEP_META;
+ break;
+ } /* else switches straight to the next step */
+ case _STEP_META:
+ length_to_read = compute_data_length_after_meta(ctx, msg, msg_type);
+ if ((msg_length + length_to_read) > ctx->backend->max_adu_length) {
+ errno = EMBBADDATA;
+ _error_print(ctx, "too many data");
+ return -1;
+ }
+ step = _STEP_DATA;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (length_to_read > 0 &&
+ (ctx->byte_timeout.tv_sec > 0 || ctx->byte_timeout.tv_usec > 0)) {
+ /* If there is no character in the buffer, the allowed timeout
+ interval between two consecutive bytes is defined by
+ byte_timeout */
+ tv.tv_sec = ctx->byte_timeout.tv_sec;
+ tv.tv_usec = ctx->byte_timeout.tv_usec;
+ p_tv = &tv;
+ }
+ /* else timeout isn't set again, the full response must be read before
+ expiration of response timeout (for CONFIRMATION only) */
+ }
+
+ if (ctx->debug)
+ printf("\n");
+
+ return ctx->backend->check_integrity(ctx, msg, msg_length);
+}
+
+/* Receive the request from a modbus master */
+int modbus_receive(modbus_t *ctx, uint8_t *req)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return ctx->backend->receive(ctx, req);
+}
+
+/* Receives the confirmation.
+
+ The function shall store the read response in rsp and return the number of
+ values (bits or words). Otherwise, its shall return -1 and errno is set.
+
+ The function doesn't check the confirmation is the expected response to the
+ initial request.
+*/
+int modbus_receive_confirmation(modbus_t *ctx, uint8_t *rsp)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION);
+}
+
+static int check_confirmation(modbus_t *ctx, uint8_t *req, uint8_t *rsp, int rsp_length)
+{
+ int rc;
+ int rsp_length_computed;
+ const unsigned int offset = ctx->backend->header_length;
+ const int function = rsp[offset];
+
+ if (ctx->backend->pre_check_confirmation) {
+ rc = ctx->backend->pre_check_confirmation(ctx, req, rsp, rsp_length);
+ if (rc == -1) {
+ if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_PROTOCOL) {
+ _sleep_response_timeout(ctx);
+ modbus_flush(ctx);
+ }
+ return -1;
+ }
+ }
+
+ rsp_length_computed = compute_response_length_from_request(ctx, req);
+
+ /* Exception code */
+ if (function >= 0x80) {
+ if (rsp_length == (int) (offset + 2 + ctx->backend->checksum_length) &&
+ req[offset] == (rsp[offset] - 0x80)) {
+ /* Valid exception code received */
+
+ int exception_code = rsp[offset + 1];
+ if (exception_code < MODBUS_EXCEPTION_MAX) {
+ errno = MODBUS_ENOBASE + exception_code;
+ } else {
+ errno = EMBBADEXC;
+ }
+ _error_print(ctx, NULL);
+ return -1;
+ } else {
+ errno = EMBBADEXC;
+ _error_print(ctx, NULL);
+ return -1;
+ }
+ }
+
+ /* Check length */
+ if ((rsp_length == rsp_length_computed ||
+ rsp_length_computed == MSG_LENGTH_UNDEFINED) &&
+ function < 0x80) {
+ int req_nb_value;
+ int rsp_nb_value;
+ int resp_addr_ok = TRUE;
+ int resp_data_ok = TRUE;
+
+ /* Check function code */
+ if (function != req[offset]) {
+ if (ctx->debug) {
+ fprintf(
+ stderr,
+ "Received function not corresponding to the request (0x%X != 0x%X)\n",
+ function,
+ req[offset]);
+ }
+ if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_PROTOCOL) {
+ _sleep_response_timeout(ctx);
+ modbus_flush(ctx);
+ }
+ errno = EMBBADDATA;
+ return -1;
+ }
+
+ /* Check the number of values is corresponding to the request */
+ switch (function) {
+ case MODBUS_FC_READ_COILS:
+ case MODBUS_FC_READ_DISCRETE_INPUTS:
+ /* Read functions, 8 values in a byte (nb
+ * of values in the request and byte count in
+ * the response. */
+ req_nb_value = (req[offset + 3] << 8) + req[offset + 4];
+ req_nb_value = (req_nb_value / 8) + ((req_nb_value % 8) ? 1 : 0);
+ rsp_nb_value = rsp[offset + 1];
+ break;
+ case MODBUS_FC_WRITE_AND_READ_REGISTERS:
+ case MODBUS_FC_READ_HOLDING_REGISTERS:
+ case MODBUS_FC_READ_INPUT_REGISTERS:
+ /* Read functions 1 value = 2 bytes */
+ req_nb_value = (req[offset + 3] << 8) + req[offset + 4];
+ rsp_nb_value = (rsp[offset + 1] / 2);
+ break;
+ case MODBUS_FC_WRITE_MULTIPLE_COILS:
+ case MODBUS_FC_WRITE_MULTIPLE_REGISTERS:
+ /* address in request and response must be equal */
+ if ((req[offset + 1] != rsp[offset + 1]) ||
+ (req[offset + 2] != rsp[offset + 2])) {
+ resp_addr_ok = FALSE;
+ }
+ /* N Write functions */
+ req_nb_value = (req[offset + 3] << 8) + req[offset + 4];
+ rsp_nb_value = (rsp[offset + 3] << 8) | rsp[offset + 4];
+ break;
+ case MODBUS_FC_REPORT_SLAVE_ID:
+ /* Report slave ID (bytes received) */
+ req_nb_value = rsp_nb_value = rsp[offset + 1];
+ break;
+ case MODBUS_FC_WRITE_SINGLE_COIL:
+ case MODBUS_FC_WRITE_SINGLE_REGISTER:
+ /* address in request and response must be equal */
+ if ((req[offset + 1] != rsp[offset + 1]) ||
+ (req[offset + 2] != rsp[offset + 2])) {
+ resp_addr_ok = FALSE;
+ }
+ /* data in request and response must be equal */
+ if ((req[offset + 3] != rsp[offset + 3]) ||
+ (req[offset + 4] != rsp[offset + 4])) {
+ resp_data_ok = FALSE;
+ }
+ /* 1 Write functions & others */
+ req_nb_value = rsp_nb_value = 1;
+ break;
+ default:
+ /* 1 Write functions & others */
+ req_nb_value = rsp_nb_value = 1;
+ break;
+ }
+
+ if ((req_nb_value == rsp_nb_value) && (resp_addr_ok == TRUE) &&
+ (resp_data_ok == TRUE)) {
+ rc = rsp_nb_value;
+ } else {
+ if (ctx->debug) {
+ fprintf(stderr,
+ "Received data not corresponding to the request (%d != %d)\n",
+ rsp_nb_value,
+ req_nb_value);
+ }
+
+ if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_PROTOCOL) {
+ _sleep_response_timeout(ctx);
+ modbus_flush(ctx);
+ }
+
+ errno = EMBBADDATA;
+ rc = -1;
+ }
+ } else {
+ if (ctx->debug) {
+ fprintf(
+ stderr,
+ "Message length not corresponding to the computed length (%d != %d)\n",
+ rsp_length,
+ rsp_length_computed);
+ }
+ if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_PROTOCOL) {
+ _sleep_response_timeout(ctx);
+ modbus_flush(ctx);
+ }
+ errno = EMBBADDATA;
+ rc = -1;
+ }
+
+ return rc;
+}
+
+static int
+response_io_status(uint8_t *tab_io_status, int address, int nb, uint8_t *rsp, int offset)
+{
+ int shift = 0;
+ /* Instead of byte (not allowed in Win32) */
+ int one_byte = 0;
+ int i;
+
+ for (i = address; i < address + nb; i++) {
+ one_byte |= tab_io_status[i] << shift;
+ if (shift == 7) {
+ /* Byte is full */
+ rsp[offset++] = one_byte;
+ one_byte = shift = 0;
+ } else {
+ shift++;
+ }
+ }
+
+ if (shift != 0)
+ rsp[offset++] = one_byte;
+
+ return offset;
+}
+
+/* Build the exception response */
+static int response_exception(modbus_t *ctx,
+ sft_t *sft,
+ int exception_code,
+ uint8_t *rsp,
+ unsigned int to_flush,
+ const char *template,
+ ...)
+{
+ int rsp_length;
+
+ /* Print debug message */
+ if (ctx->debug) {
+ va_list ap;
+
+ va_start(ap, template);
+ vfprintf(stderr, template, ap);
+ va_end(ap);
+ }
+
+ /* Flush if required */
+ if (to_flush) {
+ _sleep_response_timeout(ctx);
+ modbus_flush(ctx);
+ }
+
+ /* Build exception response */
+ sft->function = sft->function + 0x80;
+ rsp_length = ctx->backend->build_response_basis(sft, rsp);
+ rsp[rsp_length++] = exception_code;
+
+ return rsp_length;
+}
+
+/* Send a response to the received request.
+ Analyses the request and constructs a response.
+
+ If an error occurs, this function construct the response
+ accordingly.
+*/
+int modbus_reply(modbus_t *ctx,
+ const uint8_t *req,
+ int req_length,
+ modbus_mapping_t *mb_mapping)
+{
+ unsigned int offset;
+ int slave;
+ int function;
+ uint16_t address;
+ uint8_t rsp[MAX_MESSAGE_LENGTH];
+ int rsp_length = 0;
+ sft_t sft;
+
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ offset = ctx->backend->header_length;
+ slave = req[offset - 1];
+ function = req[offset];
+ address = (req[offset + 1] << 8) + req[offset + 2];
+
+ sft.slave = slave;
+ sft.function = function;
+ sft.t_id = ctx->backend->get_response_tid(req);
+
+ /* Data are flushed on illegal number of values errors. */
+ switch (function) {
+ case MODBUS_FC_READ_COILS:
+ case MODBUS_FC_READ_DISCRETE_INPUTS: {
+ unsigned int is_input = (function == MODBUS_FC_READ_DISCRETE_INPUTS);
+ int start_bits = is_input ? mb_mapping->start_input_bits : mb_mapping->start_bits;
+ int nb_bits = is_input ? mb_mapping->nb_input_bits : mb_mapping->nb_bits;
+ uint8_t *tab_bits = is_input ? mb_mapping->tab_input_bits : mb_mapping->tab_bits;
+ const char *const name = is_input ? "read_input_bits" : "read_bits";
+ int nb = (req[offset + 3] << 8) + req[offset + 4];
+ /* The mapping can be shifted to reduce memory consumption and it
+ doesn't always start at address zero. */
+ int mapping_address = address - start_bits;
+
+ if (nb < 1 || MODBUS_MAX_READ_BITS < nb) {
+ rsp_length = response_exception(ctx,
+ &sft,
+ MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE,
+ rsp,
+ TRUE,
+ "Illegal nb of values %d in %s (max %d)\n",
+ nb,
+ name,
+ MODBUS_MAX_READ_BITS);
+ } else if (mapping_address < 0 || (mapping_address + nb) > nb_bits) {
+ rsp_length = response_exception(ctx,
+ &sft,
+ MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS,
+ rsp,
+ FALSE,
+ "Illegal data address 0x%0X in %s\n",
+ mapping_address < 0 ? address : address + nb,
+ name);
+ } else {
+ rsp_length = ctx->backend->build_response_basis(&sft, rsp);
+ rsp[rsp_length++] = (nb / 8) + ((nb % 8) ? 1 : 0);
+ rsp_length =
+ response_io_status(tab_bits, mapping_address, nb, rsp, rsp_length);
+ }
+ } break;
+ case MODBUS_FC_READ_HOLDING_REGISTERS:
+ case MODBUS_FC_READ_INPUT_REGISTERS: {
+ unsigned int is_input = (function == MODBUS_FC_READ_INPUT_REGISTERS);
+ int start_registers =
+ is_input ? mb_mapping->start_input_registers : mb_mapping->start_registers;
+ int nb_registers =
+ is_input ? mb_mapping->nb_input_registers : mb_mapping->nb_registers;
+ uint16_t *tab_registers =
+ is_input ? mb_mapping->tab_input_registers : mb_mapping->tab_registers;
+ const char *const name = is_input ? "read_input_registers" : "read_registers";
+ int nb = (req[offset + 3] << 8) + req[offset + 4];
+ /* The mapping can be shifted to reduce memory consumption and it
+ doesn't always start at address zero. */
+ int mapping_address = address - start_registers;
+
+ if (nb < 1 || MODBUS_MAX_READ_REGISTERS < nb) {
+ rsp_length = response_exception(ctx,
+ &sft,
+ MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE,
+ rsp,
+ TRUE,
+ "Illegal nb of values %d in %s (max %d)\n",
+ nb,
+ name,
+ MODBUS_MAX_READ_REGISTERS);
+ } else if (mapping_address < 0 || (mapping_address + nb) > nb_registers) {
+ rsp_length = response_exception(ctx,
+ &sft,
+ MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS,
+ rsp,
+ FALSE,
+ "Illegal data address 0x%0X in %s\n",
+ mapping_address < 0 ? address : address + nb,
+ name);
+ } else {
+ int i;
+
+ rsp_length = ctx->backend->build_response_basis(&sft, rsp);
+ rsp[rsp_length++] = nb << 1;
+ for (i = mapping_address; i < mapping_address + nb; i++) {
+ rsp[rsp_length++] = tab_registers[i] >> 8;
+ rsp[rsp_length++] = tab_registers[i] & 0xFF;
+ }
+ }
+ } break;
+ case MODBUS_FC_WRITE_SINGLE_COIL: {
+ int mapping_address = address - mb_mapping->start_bits;
+
+ if (mapping_address < 0 || mapping_address >= mb_mapping->nb_bits) {
+ rsp_length = response_exception(ctx,
+ &sft,
+ MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS,
+ rsp,
+ FALSE,
+ "Illegal data address 0x%0X in write bit\n",
+ address);
+ break;
+ }
+
+ /* This check is only done here to ensure using memcpy is safe. */
+ rsp_length = compute_response_length_from_request(ctx, (uint8_t *) req);
+ if (rsp_length != req_length) {
+ /* Bad use of modbus_reply */
+ rsp_length = response_exception(
+ ctx,
+ &sft,
+ MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE,
+ rsp,
+ FALSE,
+ "Invalid request length in modbus_reply to write bit (%d)\n",
+ req_length);
+ break;
+ }
+
+ /* Don't copy the CRC, if any, it will be computed later (even if identical to the
+ * request) */
+ rsp_length -= ctx->backend->checksum_length;
+
+ int data = (req[offset + 3] << 8) + req[offset + 4];
+ if (data == 0xFF00 || data == 0x0) {
+ /* Apply the change to mapping */
+ mb_mapping->tab_bits[mapping_address] = data ? ON : OFF;
+ /* Prepare response */
+ memcpy(rsp, req, rsp_length);
+ } else {
+ rsp_length = response_exception(
+ ctx,
+ &sft,
+ MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE,
+ rsp,
+ FALSE,
+ "Illegal data value 0x%0X in write_bit request at address %0X\n",
+ data,
+ address);
+ }
+ } break;
+ case MODBUS_FC_WRITE_SINGLE_REGISTER: {
+ int mapping_address = address - mb_mapping->start_registers;
+
+ if (mapping_address < 0 || mapping_address >= mb_mapping->nb_registers) {
+ rsp_length =
+ response_exception(ctx,
+ &sft,
+ MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS,
+ rsp,
+ FALSE,
+ "Illegal data address 0x%0X in write_register\n",
+ address);
+ break;
+ }
+
+ rsp_length = compute_response_length_from_request(ctx, (uint8_t *) req);
+ if (rsp_length != req_length) {
+ /* Bad use of modbus_reply */
+ rsp_length = response_exception(
+ ctx,
+ &sft,
+ MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE,
+ rsp,
+ FALSE,
+ "Invalid request length in modbus_reply to write register (%d)\n",
+ req_length);
+ break;
+ }
+ int data = (req[offset + 3] << 8) + req[offset + 4];
+
+ mb_mapping->tab_registers[mapping_address] = data;
+
+ rsp_length -= ctx->backend->checksum_length;
+ memcpy(rsp, req, rsp_length);
+ } break;
+ case MODBUS_FC_WRITE_MULTIPLE_COILS: {
+ int nb = (req[offset + 3] << 8) + req[offset + 4];
+ int nb_bits = req[offset + 5];
+ int mapping_address = address - mb_mapping->start_bits;
+
+ if (nb < 1 || MODBUS_MAX_WRITE_BITS < nb || nb_bits * 8 < nb) {
+ /* May be the indication has been truncated on reading because of
+ * invalid address (eg. nb is 0 but the request contains values to
+ * write) so it's necessary to flush. */
+ rsp_length =
+ response_exception(ctx,
+ &sft,
+ MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE,
+ rsp,
+ TRUE,
+ "Illegal number of values %d in write_bits (max %d)\n",
+ nb,
+ MODBUS_MAX_WRITE_BITS);
+ } else if (mapping_address < 0 || (mapping_address + nb) > mb_mapping->nb_bits) {
+ rsp_length = response_exception(ctx,
+ &sft,
+ MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS,
+ rsp,
+ FALSE,
+ "Illegal data address 0x%0X in write_bits\n",
+ mapping_address < 0 ? address : address + nb);
+ } else {
+ /* 6 = byte count */
+ modbus_set_bits_from_bytes(
+ mb_mapping->tab_bits, mapping_address, nb, &req[offset + 6]);
+
+ rsp_length = ctx->backend->build_response_basis(&sft, rsp);
+ /* 4 to copy the bit address (2) and the quantity of bits */
+ memcpy(rsp + rsp_length, req + rsp_length, 4);
+ rsp_length += 4;
+ }
+ } break;
+ case MODBUS_FC_WRITE_MULTIPLE_REGISTERS: {
+ int nb = (req[offset + 3] << 8) + req[offset + 4];
+ int nb_bytes = req[offset + 5];
+ int mapping_address = address - mb_mapping->start_registers;
+
+ if (nb < 1 || MODBUS_MAX_WRITE_REGISTERS < nb || nb_bytes != nb * 2) {
+ rsp_length = response_exception(
+ ctx,
+ &sft,
+ MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE,
+ rsp,
+ TRUE,
+ "Illegal number of values %d in write_registers (max %d)\n",
+ nb,
+ MODBUS_MAX_WRITE_REGISTERS);
+ } else if (mapping_address < 0 ||
+ (mapping_address + nb) > mb_mapping->nb_registers) {
+ rsp_length =
+ response_exception(ctx,
+ &sft,
+ MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS,
+ rsp,
+ FALSE,
+ "Illegal data address 0x%0X in write_registers\n",
+ mapping_address < 0 ? address : address + nb);
+ } else {
+ int i, j;
+ for (i = mapping_address, j = 6; i < mapping_address + nb; i++, j += 2) {
+ /* 6 and 7 = first value */
+ mb_mapping->tab_registers[i] =
+ (req[offset + j] << 8) + req[offset + j + 1];
+ }
+
+ rsp_length = ctx->backend->build_response_basis(&sft, rsp);
+ /* 4 to copy the address (2) and the no. of registers */
+ memcpy(rsp + rsp_length, req + rsp_length, 4);
+ rsp_length += 4;
+ }
+ } break;
+ case MODBUS_FC_REPORT_SLAVE_ID: {
+ int str_len;
+ int byte_count_pos;
+
+ rsp_length = ctx->backend->build_response_basis(&sft, rsp);
+ /* Skip byte count for now */
+ byte_count_pos = rsp_length++;
+ rsp[rsp_length++] = _REPORT_SLAVE_ID;
+ /* Run indicator status to ON */
+ rsp[rsp_length++] = 0xFF;
+ /* LMB + length of LIBMODBUS_VERSION_STRING */
+ str_len = 3 + strlen(LIBMODBUS_VERSION_STRING);
+ memcpy(rsp + rsp_length, "LMB" LIBMODBUS_VERSION_STRING, str_len);
+ rsp_length += str_len;
+ rsp[byte_count_pos] = rsp_length - byte_count_pos - 1;
+ } break;
+ case MODBUS_FC_READ_EXCEPTION_STATUS:
+ if (ctx->debug) {
+ fprintf(stderr, "FIXME Not implemented\n");
+ }
+ errno = ENOPROTOOPT;
+ return -1;
+ break;
+ case MODBUS_FC_MASK_WRITE_REGISTER: {
+ int mapping_address = address - mb_mapping->start_registers;
+
+ if (mapping_address < 0 || mapping_address >= mb_mapping->nb_registers) {
+ rsp_length =
+ response_exception(ctx,
+ &sft,
+ MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS,
+ rsp,
+ FALSE,
+ "Illegal data address 0x%0X in write_register\n",
+ address);
+ } else {
+ uint16_t data = mb_mapping->tab_registers[mapping_address];
+ uint16_t and = (req[offset + 3] << 8) + req[offset + 4];
+ uint16_t or = (req[offset + 5] << 8) + req[offset + 6];
+
+ data = (data & and) | (or &(~and) );
+ mb_mapping->tab_registers[mapping_address] = data;
+
+ rsp_length = compute_response_length_from_request(ctx, (uint8_t *) req);
+ if (rsp_length != req_length) {
+ /* Bad use of modbus_reply */
+ rsp_length = response_exception(ctx,
+ &sft,
+ MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE,
+ rsp,
+ FALSE,
+ "Invalid request length in modbus_reply "
+ "to mask write register (%d)\n",
+ req_length);
+ break;
+ }
+
+ rsp_length -= ctx->backend->checksum_length;
+ memcpy(rsp, req, rsp_length);
+ }
+ } break;
+ case MODBUS_FC_WRITE_AND_READ_REGISTERS: {
+ int nb = (req[offset + 3] << 8) + req[offset + 4];
+ uint16_t address_write = (req[offset + 5] << 8) + req[offset + 6];
+ int nb_write = (req[offset + 7] << 8) + req[offset + 8];
+ int nb_write_bytes = req[offset + 9];
+ int mapping_address = address - mb_mapping->start_registers;
+ int mapping_address_write = address_write - mb_mapping->start_registers;
+
+ if (nb_write < 1 || MODBUS_MAX_WR_WRITE_REGISTERS < nb_write || nb < 1 ||
+ MODBUS_MAX_WR_READ_REGISTERS < nb || nb_write_bytes != nb_write * 2) {
+ rsp_length = response_exception(
+ ctx,
+ &sft,
+ MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE,
+ rsp,
+ TRUE,
+ "Illegal nb of values (W%d, R%d) in write_and_read_registers (max W%d, "
+ "R%d)\n",
+ nb_write,
+ nb,
+ MODBUS_MAX_WR_WRITE_REGISTERS,
+ MODBUS_MAX_WR_READ_REGISTERS);
+ } else if (mapping_address < 0 ||
+ (mapping_address + nb) > mb_mapping->nb_registers ||
+ mapping_address_write < 0 ||
+ (mapping_address_write + nb_write) > mb_mapping->nb_registers) {
+ rsp_length = response_exception(
+ ctx,
+ &sft,
+ MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS,
+ rsp,
+ FALSE,
+ "Illegal data read address 0x%0X or write address 0x%0X "
+ "write_and_read_registers\n",
+ mapping_address < 0 ? address : address + nb,
+ mapping_address_write < 0 ? address_write : address_write + nb_write);
+ } else {
+ int i, j;
+ rsp_length = ctx->backend->build_response_basis(&sft, rsp);
+ rsp[rsp_length++] = nb << 1;
+
+ /* Write first.
+ 10 and 11 are the offset of the first values to write */
+ for (i = mapping_address_write, j = 10; i < mapping_address_write + nb_write;
+ i++, j += 2) {
+ mb_mapping->tab_registers[i] =
+ (req[offset + j] << 8) + req[offset + j + 1];
+ }
+
+ /* and read the data for the response */
+ for (i = mapping_address; i < mapping_address + nb; i++) {
+ rsp[rsp_length++] = mb_mapping->tab_registers[i] >> 8;
+ rsp[rsp_length++] = mb_mapping->tab_registers[i] & 0xFF;
+ }
+ }
+ } break;
+
+ default:
+ rsp_length = response_exception(ctx,
+ &sft,
+ MODBUS_EXCEPTION_ILLEGAL_FUNCTION,
+ rsp,
+ TRUE,
+ "Unknown Modbus function code: 0x%0X\n",
+ function);
+ break;
+ }
+
+ /* Suppress any responses in RTU when the request was a broadcast, excepted when
+ * quirk is enabled. */
+ if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU &&
+ slave == MODBUS_BROADCAST_ADDRESS &&
+ !(ctx->quirks & MODBUS_QUIRK_REPLY_TO_BROADCAST)) {
+ return 0;
+ }
+ return send_msg(ctx, rsp, rsp_length);
+}
+
+int modbus_reply_exception(modbus_t *ctx, const uint8_t *req, unsigned int exception_code)
+{
+ unsigned int offset;
+ int slave;
+ int function;
+ uint8_t rsp[MAX_MESSAGE_LENGTH];
+ int rsp_length;
+ sft_t sft;
+
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ offset = ctx->backend->header_length;
+ slave = req[offset - 1];
+ function = req[offset];
+
+ sft.slave = slave;
+ sft.function = function + 0x80;
+ sft.t_id = ctx->backend->get_response_tid(req);
+ rsp_length = ctx->backend->build_response_basis(&sft, rsp);
+
+ /* Positive exception code */
+ if (exception_code < MODBUS_EXCEPTION_MAX) {
+ rsp[rsp_length++] = exception_code;
+ return send_msg(ctx, rsp, rsp_length);
+ } else {
+ errno = EINVAL;
+ return -1;
+ }
+}
+
+/* Reads IO status */
+static int read_io_status(modbus_t *ctx, int function, int addr, int nb, uint8_t *dest)
+{
+ int rc;
+ int req_length;
+
+ uint8_t req[_MIN_REQ_LENGTH];
+ uint8_t rsp[MAX_MESSAGE_LENGTH];
+
+ req_length = ctx->backend->build_request_basis(ctx, function, addr, nb, req);
+
+ rc = send_msg(ctx, req, req_length);
+ if (rc > 0) {
+ int temp, bit;
+ int pos = 0;
+ unsigned int offset;
+ unsigned int offset_end;
+ unsigned int i;
+
+ rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION);
+ if (rc == -1)
+ return -1;
+
+ rc = check_confirmation(ctx, req, rsp, rc);
+ if (rc == -1)
+ return -1;
+
+ offset = ctx->backend->header_length + 2;
+ offset_end = offset + rc;
+ for (i = offset; i < offset_end; i++) {
+ /* Shift reg hi_byte to temp */
+ temp = rsp[i];
+
+ for (bit = 0x01; (bit & 0xff) && (pos < nb);) {
+ dest[pos++] = (temp & bit) ? TRUE : FALSE;
+ bit = bit << 1;
+ }
+ }
+ }
+
+ return rc;
+}
+
+/* Reads the boolean status of bits and sets the array elements
+ in the destination to TRUE or FALSE (single bits). */
+int modbus_read_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest)
+{
+ int rc;
+
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (nb > MODBUS_MAX_READ_BITS) {
+ if (ctx->debug) {
+ fprintf(stderr,
+ "ERROR Too many bits requested (%d > %d)\n",
+ nb,
+ MODBUS_MAX_READ_BITS);
+ }
+ errno = EMBMDATA;
+ return -1;
+ }
+
+ rc = read_io_status(ctx, MODBUS_FC_READ_COILS, addr, nb, dest);
+
+ if (rc == -1)
+ return -1;
+ else
+ return nb;
+}
+
+/* Same as modbus_read_bits but reads the remote device input table */
+int modbus_read_input_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest)
+{
+ int rc;
+
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (nb > MODBUS_MAX_READ_BITS) {
+ if (ctx->debug) {
+ fprintf(stderr,
+ "ERROR Too many discrete inputs requested (%d > %d)\n",
+ nb,
+ MODBUS_MAX_READ_BITS);
+ }
+ errno = EMBMDATA;
+ return -1;
+ }
+
+ rc = read_io_status(ctx, MODBUS_FC_READ_DISCRETE_INPUTS, addr, nb, dest);
+
+ if (rc == -1)
+ return -1;
+ else
+ return nb;
+}
+
+/* Reads the data from a remote device and put that data into an array */
+static int read_registers(modbus_t *ctx, int function, int addr, int nb, uint16_t *dest)
+{
+ int rc;
+ int req_length;
+ uint8_t req[_MIN_REQ_LENGTH];
+ uint8_t rsp[MAX_MESSAGE_LENGTH];
+
+ if (nb > MODBUS_MAX_READ_REGISTERS) {
+ if (ctx->debug) {
+ fprintf(stderr,
+ "ERROR Too many registers requested (%d > %d)\n",
+ nb,
+ MODBUS_MAX_READ_REGISTERS);
+ }
+ errno = EMBMDATA;
+ return -1;
+ }
+
+ req_length = ctx->backend->build_request_basis(ctx, function, addr, nb, req);
+
+ rc = send_msg(ctx, req, req_length);
+ if (rc > 0) {
+ unsigned int offset;
+ int i;
+
+ rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION);
+ if (rc == -1)
+ return -1;
+
+ rc = check_confirmation(ctx, req, rsp, rc);
+ if (rc == -1)
+ return -1;
+
+ offset = ctx->backend->header_length;
+
+ for (i = 0; i < rc; i++) {
+ /* shift reg hi_byte to temp OR with lo_byte */
+ dest[i] = (rsp[offset + 2 + (i << 1)] << 8) | rsp[offset + 3 + (i << 1)];
+ }
+ }
+
+ return rc;
+}
+
+/* Reads the holding registers of remote device and put the data into an
+ array */
+int modbus_read_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest)
+{
+ int status;
+
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (nb > MODBUS_MAX_READ_REGISTERS) {
+ if (ctx->debug) {
+ fprintf(stderr,
+ "ERROR Too many registers requested (%d > %d)\n",
+ nb,
+ MODBUS_MAX_READ_REGISTERS);
+ }
+ errno = EMBMDATA;
+ return -1;
+ }
+
+ status = read_registers(ctx, MODBUS_FC_READ_HOLDING_REGISTERS, addr, nb, dest);
+ return status;
+}
+
+/* Reads the input registers of remote device and put the data into an array */
+int modbus_read_input_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest)
+{
+ int status;
+
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (nb > MODBUS_MAX_READ_REGISTERS) {
+ if (ctx->debug) {
+ fprintf(stderr,
+ "ERROR Too many input registers requested (%d > %d)\n",
+ nb,
+ MODBUS_MAX_READ_REGISTERS);
+ }
+ errno = EMBMDATA;
+ return -1;
+ }
+
+ status = read_registers(ctx, MODBUS_FC_READ_INPUT_REGISTERS, addr, nb, dest);
+
+ return status;
+}
+
+/* Write a value to the specified register of the remote device.
+ Used by write_bit and write_register */
+static int write_single(modbus_t *ctx, int function, int addr, const uint16_t value)
+{
+ int rc;
+ int req_length;
+ uint8_t req[_MIN_REQ_LENGTH];
+
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ req_length = ctx->backend->build_request_basis(ctx, function, addr, (int) value, req);
+
+ rc = send_msg(ctx, req, req_length);
+ if (rc > 0) {
+ /* Used by write_bit and write_register */
+ uint8_t rsp[MAX_MESSAGE_LENGTH];
+
+ rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION);
+ if (rc == -1)
+ return -1;
+
+ rc = check_confirmation(ctx, req, rsp, rc);
+ }
+
+ return rc;
+}
+
+/* Turns ON or OFF a single bit of the remote device */
+int modbus_write_bit(modbus_t *ctx, int addr, int status)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return write_single(ctx, MODBUS_FC_WRITE_SINGLE_COIL, addr, status ? 0xFF00 : 0);
+}
+
+/* Writes a value in one register of the remote device */
+int modbus_write_register(modbus_t *ctx, int addr, const uint16_t value)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return write_single(ctx, MODBUS_FC_WRITE_SINGLE_REGISTER, addr, value);
+}
+
+/* Write the bits of the array in the remote device */
+int modbus_write_bits(modbus_t *ctx, int addr, int nb, const uint8_t *src)
+{
+ int rc;
+ int i;
+ int byte_count;
+ int req_length;
+ int bit_check = 0;
+ int pos = 0;
+ uint8_t req[MAX_MESSAGE_LENGTH];
+
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (nb > MODBUS_MAX_WRITE_BITS) {
+ if (ctx->debug) {
+ fprintf(stderr,
+ "ERROR Writing too many bits (%d > %d)\n",
+ nb,
+ MODBUS_MAX_WRITE_BITS);
+ }
+ errno = EMBMDATA;
+ return -1;
+ }
+
+ req_length = ctx->backend->build_request_basis(
+ ctx, MODBUS_FC_WRITE_MULTIPLE_COILS, addr, nb, req);
+ byte_count = (nb / 8) + ((nb % 8) ? 1 : 0);
+ req[req_length++] = byte_count;
+
+ for (i = 0; i < byte_count; i++) {
+ int bit;
+
+ bit = 0x01;
+ req[req_length] = 0;
+
+ while ((bit & 0xFF) && (bit_check++ < nb)) {
+ if (src[pos++])
+ req[req_length] |= bit;
+ else
+ req[req_length] &= ~bit;
+
+ bit = bit << 1;
+ }
+ req_length++;
+ }
+
+ rc = send_msg(ctx, req, req_length);
+ if (rc > 0) {
+ uint8_t rsp[MAX_MESSAGE_LENGTH];
+
+ rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION);
+ if (rc == -1)
+ return -1;
+
+ rc = check_confirmation(ctx, req, rsp, rc);
+ }
+
+ return rc;
+}
+
+/* Write the values from the array to the registers of the remote device */
+int modbus_write_registers(modbus_t *ctx, int addr, int nb, const uint16_t *src)
+{
+ int rc;
+ int i;
+ int req_length;
+ int byte_count;
+ uint8_t req[MAX_MESSAGE_LENGTH];
+
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (nb > MODBUS_MAX_WRITE_REGISTERS) {
+ if (ctx->debug) {
+ fprintf(stderr,
+ "ERROR Trying to write to too many registers (%d > %d)\n",
+ nb,
+ MODBUS_MAX_WRITE_REGISTERS);
+ }
+ errno = EMBMDATA;
+ return -1;
+ }
+
+ req_length = ctx->backend->build_request_basis(
+ ctx, MODBUS_FC_WRITE_MULTIPLE_REGISTERS, addr, nb, req);
+ byte_count = nb * 2;
+ req[req_length++] = byte_count;
+
+ for (i = 0; i < nb; i++) {
+ req[req_length++] = src[i] >> 8;
+ req[req_length++] = src[i] & 0x00FF;
+ }
+
+ rc = send_msg(ctx, req, req_length);
+ if (rc > 0) {
+ uint8_t rsp[MAX_MESSAGE_LENGTH];
+
+ rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION);
+ if (rc == -1)
+ return -1;
+
+ rc = check_confirmation(ctx, req, rsp, rc);
+ }
+
+ return rc;
+}
+
+int modbus_mask_write_register(modbus_t *ctx,
+ int addr,
+ uint16_t and_mask,
+ uint16_t or_mask)
+{
+ int rc;
+ int req_length;
+ /* The request length can not exceed _MIN_REQ_LENGTH - 2 and 4 bytes to
+ * store the masks. The ugly substraction is there to remove the 'nb' value
+ * (2 bytes) which is not used. */
+ uint8_t req[_MIN_REQ_LENGTH + 2];
+
+ req_length = ctx->backend->build_request_basis(
+ ctx, MODBUS_FC_MASK_WRITE_REGISTER, addr, 0, req);
+
+ /* HACKISH, count is not used */
+ req_length -= 2;
+
+ req[req_length++] = and_mask >> 8;
+ req[req_length++] = and_mask & 0x00ff;
+ req[req_length++] = or_mask >> 8;
+ req[req_length++] = or_mask & 0x00ff;
+
+ rc = send_msg(ctx, req, req_length);
+ if (rc > 0) {
+ /* Used by write_bit and write_register */
+ uint8_t rsp[MAX_MESSAGE_LENGTH];
+
+ rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION);
+ if (rc == -1)
+ return -1;
+
+ rc = check_confirmation(ctx, req, rsp, rc);
+ }
+
+ return rc;
+}
+
+/* Write multiple registers from src array to remote device and read multiple
+ registers from remote device to dest array. */
+int modbus_write_and_read_registers(modbus_t *ctx,
+ int write_addr,
+ int write_nb,
+ const uint16_t *src,
+ int read_addr,
+ int read_nb,
+ uint16_t *dest)
+
+{
+ int rc;
+ int req_length;
+ int i;
+ int byte_count;
+ uint8_t req[MAX_MESSAGE_LENGTH];
+ uint8_t rsp[MAX_MESSAGE_LENGTH];
+
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (write_nb > MODBUS_MAX_WR_WRITE_REGISTERS) {
+ if (ctx->debug) {
+ fprintf(stderr,
+ "ERROR Too many registers to write (%d > %d)\n",
+ write_nb,
+ MODBUS_MAX_WR_WRITE_REGISTERS);
+ }
+ errno = EMBMDATA;
+ return -1;
+ }
+
+ if (read_nb > MODBUS_MAX_WR_READ_REGISTERS) {
+ if (ctx->debug) {
+ fprintf(stderr,
+ "ERROR Too many registers requested (%d > %d)\n",
+ read_nb,
+ MODBUS_MAX_WR_READ_REGISTERS);
+ }
+ errno = EMBMDATA;
+ return -1;
+ }
+ req_length = ctx->backend->build_request_basis(
+ ctx, MODBUS_FC_WRITE_AND_READ_REGISTERS, read_addr, read_nb, req);
+
+ req[req_length++] = write_addr >> 8;
+ req[req_length++] = write_addr & 0x00ff;
+ req[req_length++] = write_nb >> 8;
+ req[req_length++] = write_nb & 0x00ff;
+ byte_count = write_nb * 2;
+ req[req_length++] = byte_count;
+
+ for (i = 0; i < write_nb; i++) {
+ req[req_length++] = src[i] >> 8;
+ req[req_length++] = src[i] & 0x00FF;
+ }
+
+ rc = send_msg(ctx, req, req_length);
+ if (rc > 0) {
+ unsigned int offset;
+
+ rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION);
+ if (rc == -1)
+ return -1;
+
+ rc = check_confirmation(ctx, req, rsp, rc);
+ if (rc == -1)
+ return -1;
+
+ offset = ctx->backend->header_length;
+ for (i = 0; i < rc; i++) {
+ /* shift reg hi_byte to temp OR with lo_byte */
+ dest[i] = (rsp[offset + 2 + (i << 1)] << 8) | rsp[offset + 3 + (i << 1)];
+ }
+ }
+
+ return rc;
+}
+
+/* Send a request to get the slave ID of the device (only available in serial
+ communication). */
+int modbus_report_slave_id(modbus_t *ctx, int max_dest, uint8_t *dest)
+{
+ int rc;
+ int req_length;
+ uint8_t req[_MIN_REQ_LENGTH];
+
+ if (ctx == NULL || max_dest <= 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ req_length =
+ ctx->backend->build_request_basis(ctx, MODBUS_FC_REPORT_SLAVE_ID, 0, 0, req);
+
+ /* HACKISH, addr and count are not used */
+ req_length -= 4;
+
+ rc = send_msg(ctx, req, req_length);
+ if (rc > 0) {
+ int i;
+ unsigned int offset;
+ uint8_t rsp[MAX_MESSAGE_LENGTH];
+
+ rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION);
+ if (rc == -1)
+ return -1;
+
+ rc = check_confirmation(ctx, req, rsp, rc);
+ if (rc == -1)
+ return -1;
+
+ offset = ctx->backend->header_length + 2;
+
+ /* Byte count, slave id, run indicator status and
+ additional data. Truncate copy to max_dest. */
+ for (i = 0; i < rc && i < max_dest; i++) {
+ dest[i] = rsp[offset + i];
+ }
+ }
+
+ return rc;
+}
+
+void _modbus_init_common(modbus_t *ctx)
+{
+ /* Slave and socket are initialized to -1 */
+ ctx->slave = -1;
+ ctx->s = -1;
+
+ ctx->debug = FALSE;
+ ctx->error_recovery = MODBUS_ERROR_RECOVERY_NONE;
+ ctx->quirks = MODBUS_QUIRK_NONE;
+
+ ctx->response_timeout.tv_sec = 0;
+ ctx->response_timeout.tv_usec = _RESPONSE_TIMEOUT;
+
+ ctx->byte_timeout.tv_sec = 0;
+ ctx->byte_timeout.tv_usec = _BYTE_TIMEOUT;
+
+ ctx->indication_timeout.tv_sec = 0;
+ ctx->indication_timeout.tv_usec = 0;
+}
+
+/* Define the slave number */
+int modbus_set_slave(modbus_t *ctx, int slave)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return ctx->backend->set_slave(ctx, slave);
+}
+
+int modbus_get_slave(modbus_t *ctx)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return ctx->slave;
+}
+
+int modbus_set_error_recovery(modbus_t *ctx, modbus_error_recovery_mode error_recovery)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* The type of modbus_error_recovery_mode is unsigned enum */
+ ctx->error_recovery = (uint8_t) error_recovery;
+ return 0;
+}
+
+// FIXME Doesn't work under Windows RTU
+int modbus_set_socket(modbus_t *ctx, int s)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ ctx->s = s;
+ return 0;
+}
+
+int modbus_get_socket(modbus_t *ctx)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return ctx->s;
+}
+
+/* Get the timeout interval used to wait for a response */
+int modbus_get_response_timeout(modbus_t *ctx, uint32_t *to_sec, uint32_t *to_usec)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ *to_sec = ctx->response_timeout.tv_sec;
+ *to_usec = ctx->response_timeout.tv_usec;
+ return 0;
+}
+
+int modbus_set_response_timeout(modbus_t *ctx, uint32_t to_sec, uint32_t to_usec)
+{
+ if (ctx == NULL || (to_sec == 0 && to_usec == 0) || to_usec > 999999) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ ctx->response_timeout.tv_sec = to_sec;
+ ctx->response_timeout.tv_usec = to_usec;
+ return 0;
+}
+
+/* Get the timeout interval between two consecutive bytes of a message */
+int modbus_get_byte_timeout(modbus_t *ctx, uint32_t *to_sec, uint32_t *to_usec)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ *to_sec = ctx->byte_timeout.tv_sec;
+ *to_usec = ctx->byte_timeout.tv_usec;
+ return 0;
+}
+
+int modbus_set_byte_timeout(modbus_t *ctx, uint32_t to_sec, uint32_t to_usec)
+{
+ /* Byte timeout can be disabled when both values are zero */
+ if (ctx == NULL || to_usec > 999999) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ ctx->byte_timeout.tv_sec = to_sec;
+ ctx->byte_timeout.tv_usec = to_usec;
+ return 0;
+}
+
+/* Get the timeout interval used by the server to wait for an indication from a client
+ */
+int modbus_get_indication_timeout(modbus_t *ctx, uint32_t *to_sec, uint32_t *to_usec)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ *to_sec = ctx->indication_timeout.tv_sec;
+ *to_usec = ctx->indication_timeout.tv_usec;
+ return 0;
+}
+
+int modbus_set_indication_timeout(modbus_t *ctx, uint32_t to_sec, uint32_t to_usec)
+{
+ /* Indication timeout can be disabled when both values are zero */
+ if (ctx == NULL || to_usec > 999999) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ ctx->indication_timeout.tv_sec = to_sec;
+ ctx->indication_timeout.tv_usec = to_usec;
+ return 0;
+}
+
+int modbus_get_header_length(modbus_t *ctx)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return ctx->backend->header_length;
+}
+
+int modbus_enable_quirks(modbus_t *ctx, unsigned int quirks_mask)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* Enable quirks that have a true value at their index in the mask */
+ ctx->quirks |= quirks_mask;
+ return 0;
+}
+
+int modbus_disable_quirks(modbus_t *ctx, unsigned int quirks_mask)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* Disable quirks that have a true value at ther index in the mask */
+ ctx->quirks &= ~quirks_mask;
+ return 0;
+}
+
+int modbus_connect(modbus_t *ctx)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return ctx->backend->connect(ctx);
+}
+
+void modbus_close(modbus_t *ctx)
+{
+ if (ctx == NULL)
+ return;
+
+ ctx->backend->close(ctx);
+}
+
+void modbus_free(modbus_t *ctx)
+{
+ if (ctx == NULL)
+ return;
+
+ ctx->backend->free(ctx);
+}
+
+int modbus_set_debug(modbus_t *ctx, int flag)
+{
+ if (ctx == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ ctx->debug = flag;
+ return 0;
+}
+
+/* Allocates 4 arrays to store bits, input bits, registers and inputs
+ registers. The pointers are stored in modbus_mapping structure.
+
+ The modbus_mapping_new_start_address() function shall return the new allocated
+ structure if successful. Otherwise it shall return NULL and set errno to
+ ENOMEM. */
+modbus_mapping_t *modbus_mapping_new_start_address(unsigned int start_bits,
+ unsigned int nb_bits,
+ unsigned int start_input_bits,
+ unsigned int nb_input_bits,
+ unsigned int start_registers,
+ unsigned int nb_registers,
+ unsigned int start_input_registers,
+ unsigned int nb_input_registers)
+{
+ modbus_mapping_t *mb_mapping;
+
+ mb_mapping = (modbus_mapping_t *) malloc(sizeof(modbus_mapping_t));
+ if (mb_mapping == NULL) {
+ return NULL;
+ }
+
+ /* 0X */
+ mb_mapping->nb_bits = nb_bits;
+ mb_mapping->start_bits = start_bits;
+ if (nb_bits == 0) {
+ mb_mapping->tab_bits = NULL;
+ } else {
+ /* Negative number raises a POSIX error */
+ mb_mapping->tab_bits = (uint8_t *) malloc(nb_bits * sizeof(uint8_t));
+ if (mb_mapping->tab_bits == NULL) {
+ free(mb_mapping);
+ return NULL;
+ }
+ memset(mb_mapping->tab_bits, 0, nb_bits * sizeof(uint8_t));
+ }
+
+ /* 1X */
+ mb_mapping->nb_input_bits = nb_input_bits;
+ mb_mapping->start_input_bits = start_input_bits;
+ if (nb_input_bits == 0) {
+ mb_mapping->tab_input_bits = NULL;
+ } else {
+ mb_mapping->tab_input_bits = (uint8_t *) malloc(nb_input_bits * sizeof(uint8_t));
+ if (mb_mapping->tab_input_bits == NULL) {
+ free(mb_mapping->tab_bits);
+ free(mb_mapping);
+ return NULL;
+ }
+ memset(mb_mapping->tab_input_bits, 0, nb_input_bits * sizeof(uint8_t));
+ }
+
+ /* 4X */
+ mb_mapping->nb_registers = nb_registers;
+ mb_mapping->start_registers = start_registers;
+ if (nb_registers == 0) {
+ mb_mapping->tab_registers = NULL;
+ } else {
+ mb_mapping->tab_registers = (uint16_t *) malloc(nb_registers * sizeof(uint16_t));
+ if (mb_mapping->tab_registers == NULL) {
+ free(mb_mapping->tab_input_bits);
+ free(mb_mapping->tab_bits);
+ free(mb_mapping);
+ return NULL;
+ }
+ memset(mb_mapping->tab_registers, 0, nb_registers * sizeof(uint16_t));
+ }
+
+ /* 3X */
+ mb_mapping->nb_input_registers = nb_input_registers;
+ mb_mapping->start_input_registers = start_input_registers;
+ if (nb_input_registers == 0) {
+ mb_mapping->tab_input_registers = NULL;
+ } else {
+ mb_mapping->tab_input_registers =
+ (uint16_t *) malloc(nb_input_registers * sizeof(uint16_t));
+ if (mb_mapping->tab_input_registers == NULL) {
+ free(mb_mapping->tab_registers);
+ free(mb_mapping->tab_input_bits);
+ free(mb_mapping->tab_bits);
+ free(mb_mapping);
+ return NULL;
+ }
+ memset(mb_mapping->tab_input_registers, 0, nb_input_registers * sizeof(uint16_t));
+ }
+
+ return mb_mapping;
+}
+
+modbus_mapping_t *modbus_mapping_new(int nb_bits,
+ int nb_input_bits,
+ int nb_registers,
+ int nb_input_registers)
+{
+ return modbus_mapping_new_start_address(
+ 0, nb_bits, 0, nb_input_bits, 0, nb_registers, 0, nb_input_registers);
+}
+
+/* Frees the 4 arrays */
+void modbus_mapping_free(modbus_mapping_t *mb_mapping)
+{
+ if (mb_mapping == NULL) {
+ return;
+ }
+
+ free(mb_mapping->tab_input_registers);
+ free(mb_mapping->tab_registers);
+ free(mb_mapping->tab_input_bits);
+ free(mb_mapping->tab_bits);
+ free(mb_mapping);
+}
+
+#ifndef HAVE_STRLCPY
+/*
+ * Function strlcpy was originally developed by
+ * Todd C. Miller to simplify writing secure code.
+ * See ftp://ftp.openbsd.org/pub/OpenBSD/src/lib/libc/string/strlcpy.3
+ * for more information.
+ *
+ * Thank you Ulrich Drepper... not!
+ *
+ * Copy src to string dest of size dest_size. At most dest_size-1 characters
+ * will be copied. Always NUL terminates (unless dest_size == 0). Returns
+ * strlen(src); if retval >= dest_size, truncation occurred.
+ */
+size_t strlcpy(char *dest, const char *src, size_t dest_size)
+{
+ register char *d = dest;
+ register const char *s = src;
+ register size_t n = dest_size;
+
+ /* Copy as many bytes as will fit */
+ if (n != 0 && --n != 0) {
+ do {
+ if ((*d++ = *s++) == 0)
+ break;
+ } while (--n != 0);
+ }
+
+ /* Not enough room in dest, add NUL and traverse rest of src */
+ if (n == 0) {
+ if (dest_size != 0)
+ *d = '\0'; /* NUL-terminate dest */
+ while (*s++)
+ ;
+ }
+
+ return (s - src - 1); /* count does not include NUL */
+}
+#endif
diff --git a/applications/examples/qt5ModbusExample/libmodbus/modbus.h b/applications/examples/qt5ModbusExample/libmodbus/modbus.h
new file mode 100644
index 0000000..fa7ec4a
--- /dev/null
+++ b/applications/examples/qt5ModbusExample/libmodbus/modbus.h
@@ -0,0 +1,338 @@
+/*
+ * Copyright © Stéphane Raimbault
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef MODBUS_H
+#define MODBUS_H
+
+// clang-format off
+/* Add this for macros that defined unix flavor */
+#if (defined(__unix__) || defined(unix)) && !defined(USG)
+# include
+#endif
+
+#ifndef _MSC_VER
+# include