zyx:2023.09.11 ysp告警及modbus从机代码首次提交
commit
f8e31db8e4
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project>
|
||||||
|
<ProjectOutputs>
|
||||||
|
<ProjectOutput>
|
||||||
|
<FullPath>C:\Users\xydl_lg\Desktop\xying\Ireland\test_pro\iedModbus_mgr\Release\iedModbus_mgr.exe</FullPath>
|
||||||
|
</ProjectOutput>
|
||||||
|
</ProjectOutputs>
|
||||||
|
<ContentFiles />
|
||||||
|
<SatelliteDlls />
|
||||||
|
<NonRecipeFileRefs />
|
||||||
|
</Project>
|
Binary file not shown.
Binary file not shown.
@ -0,0 +1,9 @@
|
|||||||
|
common.cpp
|
||||||
|
C:\Users\xydl_lg\Desktop\xying\Ireland\test_pro\iedModbus_mgr\iedModbus_mgr\common.cpp(137,23): warning C4018: “<”: 有符号/无符号不匹配
|
||||||
|
C:\Users\xydl_lg\Desktop\xying\Ireland\test_pro\iedModbus_mgr\iedModbus_mgr\common.cpp(157,23): warning C4018: “<”: 有符号/无符号不匹配
|
||||||
|
正在创建库 C:\Users\xydl_lg\Desktop\xying\Ireland\test_pro\iedModbus_mgr\Release\iedModbus_mgr.lib 和对象 C:\Users\xydl_lg\Desktop\xying\Ireland\test_pro\iedModbus_mgr\Release\iedModbus_mgr.exp
|
||||||
|
正在生成代码
|
||||||
|
Previous IPDB and IOBJ mismatch, fall back to full compilation.
|
||||||
|
All 346 functions were compiled because no usable IPDB/IOBJ from previous compilation was found.
|
||||||
|
已完成代码的生成
|
||||||
|
iedModbus_mgr.vcxproj -> C:\Users\xydl_lg\Desktop\xying\Ireland\test_pro\iedModbus_mgr\Release\iedModbus_mgr.exe
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,11 @@
|
|||||||
|
C:\Users\xydl_lg\Desktop\xying\Ireland\test_pro\iedModbus_mgr\iedModbus_mgr\common.cpp;C:\Users\xydl_lg\Desktop\xying\Ireland\test_pro\iedModbus_mgr\iedModbus_mgr\Release\common.obj
|
||||||
|
C:\Users\xydl_lg\Desktop\xying\Ireland\test_pro\iedModbus_mgr\iedModbus_mgr\database\database.cpp;C:\Users\xydl_lg\Desktop\xying\Ireland\test_pro\iedModbus_mgr\iedModbus_mgr\Release\database.obj
|
||||||
|
C:\Users\xydl_lg\Desktop\xying\Ireland\test_pro\iedModbus_mgr\iedModbus_mgr\gas_monitor.cpp;C:\Users\xydl_lg\Desktop\xying\Ireland\test_pro\iedModbus_mgr\iedModbus_mgr\Release\gas_monitor.obj
|
||||||
|
C:\Users\xydl_lg\Desktop\xying\Ireland\test_pro\iedModbus_mgr\iedModbus_mgr\iedModbus_mgr.cpp;C:\Users\xydl_lg\Desktop\xying\Ireland\test_pro\iedModbus_mgr\iedModbus_mgr\Release\iedModbus_mgr.obj
|
||||||
|
C:\Users\xydl_lg\Desktop\xying\Ireland\test_pro\iedModbus_mgr\iedModbus_mgr\ini.cpp;C:\Users\xydl_lg\Desktop\xying\Ireland\test_pro\iedModbus_mgr\iedModbus_mgr\Release\ini.obj
|
||||||
|
C:\Users\xydl_lg\Desktop\xying\Ireland\test_pro\iedModbus_mgr\iedModbus_mgr\modbus_lib\modbus-data.cpp;C:\Users\xydl_lg\Desktop\xying\Ireland\test_pro\iedModbus_mgr\iedModbus_mgr\Release\modbus-data.obj
|
||||||
|
C:\Users\xydl_lg\Desktop\xying\Ireland\test_pro\iedModbus_mgr\iedModbus_mgr\modbus_lib\modbus-rtu.cpp;C:\Users\xydl_lg\Desktop\xying\Ireland\test_pro\iedModbus_mgr\iedModbus_mgr\Release\modbus-rtu.obj
|
||||||
|
C:\Users\xydl_lg\Desktop\xying\Ireland\test_pro\iedModbus_mgr\iedModbus_mgr\modbus_lib\modbus-tcp.cpp;C:\Users\xydl_lg\Desktop\xying\Ireland\test_pro\iedModbus_mgr\iedModbus_mgr\Release\modbus-tcp.obj
|
||||||
|
C:\Users\xydl_lg\Desktop\xying\Ireland\test_pro\iedModbus_mgr\iedModbus_mgr\modbus_lib\modbus.cpp;C:\Users\xydl_lg\Desktop\xying\Ireland\test_pro\iedModbus_mgr\iedModbus_mgr\Release\modbus.obj
|
||||||
|
C:\Users\xydl_lg\Desktop\xying\Ireland\test_pro\iedModbus_mgr\iedModbus_mgr\SerialPort.cpp;C:\Users\xydl_lg\Desktop\xying\Ireland\test_pro\iedModbus_mgr\iedModbus_mgr\Release\SerialPort.obj
|
||||||
|
C:\Users\xydl_lg\Desktop\xying\Ireland\test_pro\iedModbus_mgr\iedModbus_mgr\ysp_modbus_slave.cpp;C:\Users\xydl_lg\Desktop\xying\Ireland\test_pro\iedModbus_mgr\iedModbus_mgr\Release\ysp_modbus_slave.obj
|
@ -0,0 +1,2 @@
|
|||||||
|
PlatformToolSet=v143:VCToolArchitecture=Native32Bit:VCToolsVersion=14.37.32822:TargetPlatformVersion=10.0.22621.0:
|
||||||
|
Release|Win32|C:\Users\xydl_lg\Desktop\xying\Ireland\test_pro\iedModbus_mgr\|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,234 @@
|
|||||||
|
#include "common.h"
|
||||||
|
#include "stdio.h"
|
||||||
|
#include "stdlib.h"
|
||||||
|
#include "gas_monitor.h"
|
||||||
|
#include "ini.h"
|
||||||
|
#include "ysp_modbus_slave.h"
|
||||||
|
#include "windows.h"
|
||||||
|
#include <vector>
|
||||||
|
#include "tchar.h" // _sntprintf _T
|
||||||
|
#include <Setupapi.h> //SetupDiGetClassDevs Setup*
|
||||||
|
#include <initguid.h> //GUID
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#pragma comment (lib, "setupapi.lib")
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
//#define CONFIG_FILE_PATH "C:/Users/xydl_lg/Desktop/xying/Ireland/test_ini.ini"
|
||||||
|
//#define CONFIG_FILE_PATH "C:/Users/xydl_lg/Desktop/xying/Ireland/test_pro/iedModbus_mgr/iedModbus_mgr/yspConfig.ini"
|
||||||
|
#define CONFIG_FILE_NAME "yspConfig.ini"
|
||||||
|
|
||||||
|
#define DELAY_MIN_DEFAULT 10 //程序延迟十分钟执行
|
||||||
|
|
||||||
|
#define MIN_SLEEP_TIME(min) min*60*1000
|
||||||
|
#define DELAY_RUN(min) MIN_SLEEP_TIME(min) //监测周期
|
||||||
|
|
||||||
|
|
||||||
|
static int pro_delay_min = DELAY_MIN_DEFAULT;
|
||||||
|
|
||||||
|
//unsigned char 数组转16进制输出
|
||||||
|
void xUnsigned_char_hex_out(const char *msg, unsigned char* src, int start, int end)
|
||||||
|
{
|
||||||
|
if (msg != NULL) {
|
||||||
|
printf("%s:\n", msg);
|
||||||
|
}
|
||||||
|
for (int i = start; i < end + 1; i++) {
|
||||||
|
printf("%02X", src[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
void xDelay_run_process()
|
||||||
|
{
|
||||||
|
#if 0
|
||||||
|
ULONGLONG startTick = 0;
|
||||||
|
startTick = GetTickCount64();
|
||||||
|
|
||||||
|
printf("delay %d min run ...\n", pro_delay_min);
|
||||||
|
Sleep(DELAY_RUN(pro_delay_min));
|
||||||
|
|
||||||
|
_tprintf(_T("sleep : %I64u ms\n"), GetTickCount64() - startTick);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef GUID_DEVINTERFACE_COMPORT
|
||||||
|
DEFINE_GUID(GUID_DEVINTERFACE_COMPORT, 0x86E0D1E0L, 0x8089, 0x11D0, 0x9C, 0xE4, 0x08, 0x00, 0x3E, 0x30, 0x1F, 0x73);
|
||||||
|
#endif
|
||||||
|
struct SerialPortInfo
|
||||||
|
{
|
||||||
|
std::string portName;
|
||||||
|
std::string description;
|
||||||
|
};
|
||||||
|
std::string wstringToString(const std::wstring& wstr)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (wstr.empty())
|
||||||
|
{
|
||||||
|
return std::string();
|
||||||
|
}
|
||||||
|
int size = WideCharToMultiByte(CP_ACP, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL);
|
||||||
|
std::string ret = std::string(size, 0);
|
||||||
|
WideCharToMultiByte(CP_ACP, 0, &wstr[0], (int)wstr.size(), &ret[0], size, NULL, NULL); // CP_UTF8
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
bool enumDetailsSerialPorts(vector<SerialPortInfo>& portInfoList)
|
||||||
|
{
|
||||||
|
bool bRet = false;
|
||||||
|
SerialPortInfo m_serialPortInfo;
|
||||||
|
std::string strFriendlyName;
|
||||||
|
std::string strPortName;
|
||||||
|
HDEVINFO hDevInfo = INVALID_HANDLE_VALUE;
|
||||||
|
// Return only devices that are currently present in a system
|
||||||
|
// The GUID_DEVINTERFACE_COMPORT device interface class is defined for COM ports. GUID
|
||||||
|
// {86E0D1E0-8089-11D0-9CE4-08003E301F73}
|
||||||
|
hDevInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_COMPORT, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
|
||||||
|
if (INVALID_HANDLE_VALUE != hDevInfo)
|
||||||
|
{
|
||||||
|
SP_DEVINFO_DATA devInfoData;
|
||||||
|
// The caller must set DeviceInfoData.cbSize to sizeof(SP_DEVINFO_DATA)
|
||||||
|
devInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
|
||||||
|
for (DWORD i = 0; SetupDiEnumDeviceInfo(hDevInfo, i, &devInfoData); i++)
|
||||||
|
{
|
||||||
|
// get port name
|
||||||
|
TCHAR portName[256] = {0};
|
||||||
|
HKEY hDevKey = SetupDiOpenDevRegKey(hDevInfo, &devInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);
|
||||||
|
if (INVALID_HANDLE_VALUE != hDevKey)
|
||||||
|
{
|
||||||
|
DWORD dwCount = 255; // DEV_NAME_MAX_LEN
|
||||||
|
RegQueryValueEx(hDevKey, _T("PortName"), NULL, NULL, (BYTE*)portName, &dwCount);
|
||||||
|
RegCloseKey(hDevKey);
|
||||||
|
}
|
||||||
|
// get friendly name
|
||||||
|
TCHAR fname[256];
|
||||||
|
SetupDiGetDeviceRegistryProperty(hDevInfo, &devInfoData, SPDRP_FRIENDLYNAME, NULL, (PBYTE)fname,
|
||||||
|
sizeof(fname), NULL);
|
||||||
|
#ifdef UNICODE
|
||||||
|
strPortName = wstringToString(portName);
|
||||||
|
strFriendlyName = wstringToString(fname);
|
||||||
|
#else
|
||||||
|
strPortName = std::string(portName);
|
||||||
|
strFriendlyName = std::string(fname);
|
||||||
|
#endif
|
||||||
|
cout << strFriendlyName << endl;
|
||||||
|
// remove (COMxx)
|
||||||
|
strFriendlyName = strFriendlyName.substr(0, strFriendlyName.find(("(COM")));
|
||||||
|
cout << strFriendlyName << endl;
|
||||||
|
m_serialPortInfo.portName = strPortName;
|
||||||
|
m_serialPortInfo.description = strFriendlyName;
|
||||||
|
portInfoList.push_back(m_serialPortInfo);
|
||||||
|
}
|
||||||
|
if (ERROR_NO_MORE_ITEMS == GetLastError())
|
||||||
|
{
|
||||||
|
bRet = true; // no more item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SetupDiDestroyDeviceInfoList(hDevInfo);
|
||||||
|
return bRet;
|
||||||
|
}
|
||||||
|
|
||||||
|
//枚举找到使用的串口线对应的端口号
|
||||||
|
int xEnum_relay_comName(char* com_name)
|
||||||
|
{
|
||||||
|
const char* serial_description = "USB-SERIAL CH340";//项目中使用的串口芯片描述符
|
||||||
|
vector<SerialPortInfo> spInfo;
|
||||||
|
enumDetailsSerialPorts(spInfo);
|
||||||
|
|
||||||
|
for (int i = 0; i < spInfo.size(); i++)
|
||||||
|
{
|
||||||
|
std::cout << i << " - " << spInfo[i].portName << " | " << spInfo[i].description << std::endl;
|
||||||
|
if (!strncmp(spInfo[i].description.c_str(), serial_description, strlen(serial_description))) {
|
||||||
|
memcpy(com_name, spInfo[i].portName.c_str(), spInfo[i].portName.length());
|
||||||
|
printf("has find uart number is : %s\n", com_name);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int xGet_config_from_configFile()
|
||||||
|
{
|
||||||
|
#if 1
|
||||||
|
char curr_path[MAX_PATH] = { 0 };
|
||||||
|
char configFile_path[MAX_PATH] = { 0 };
|
||||||
|
const char* fmt = "%s/%s";
|
||||||
|
|
||||||
|
GetCurrentDirectory(MAX_PATH, curr_path);
|
||||||
|
for (int i = 0; i < strlen(curr_path); i++) {
|
||||||
|
if(curr_path[i] == 0x5C){ //0x5C \ 反斜杠
|
||||||
|
curr_path[i] = 0x2F; //0x2F / 斜杠
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printf("curr path:%s\n", curr_path);
|
||||||
|
snprintf(configFile_path, sizeof(configFile_path), fmt, curr_path, CONFIG_FILE_NAME);
|
||||||
|
printf("configFile_path:%s\n", configFile_path);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct ini_t* ini_ctl = ini_load(configFile_path);
|
||||||
|
if (ini_ctl == NULL) {
|
||||||
|
printf("open config file fail, path:%s\n", configFile_path);
|
||||||
|
printf("all parameters use default!!!\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//modbus 从机tcp ipaddr
|
||||||
|
const char* ip_addr = ini_get(ini_ctl, "MODBUS", "SLAVE_IP_ADDR");
|
||||||
|
xSet_slave_ip_addr(ip_addr);
|
||||||
|
|
||||||
|
|
||||||
|
//modbus从机tcp port
|
||||||
|
const char* tcp_port = ini_get(ini_ctl, "MODBUS", "SLAVE_PORT");
|
||||||
|
xSet_slave_port(tcp_port);
|
||||||
|
|
||||||
|
//气体监测的周期(分钟)
|
||||||
|
const char* monitor_period = ini_get(ini_ctl, "MONITOR", "GAS_MONITOR_PERIOD");
|
||||||
|
xSet_monitor_period(monitor_period);
|
||||||
|
|
||||||
|
const char* uart_buadrate = ini_get(ini_ctl, "MONITOR", "UART_BUADRATE");
|
||||||
|
xSet_uart_buadrate(uart_buadrate);
|
||||||
|
|
||||||
|
//气体告警阈值
|
||||||
|
const char* H2_threshold = ini_get(ini_ctl, "ALERT", "H2_THRESHOLD");
|
||||||
|
xSet_H2_threshold(H2_threshold);
|
||||||
|
const char* H2_alert = ini_get(ini_ctl, "ALERT", "H2_alert");
|
||||||
|
xSet_H2_alert(H2_alert);
|
||||||
|
|
||||||
|
const char* CO_threshold = ini_get(ini_ctl, "ALERT", "CO_THRESHOLD");
|
||||||
|
xSet_CO_threshold(CO_threshold);
|
||||||
|
const char* CO_alert = ini_get(ini_ctl, "ALERT", "CO_alert");
|
||||||
|
xSet_H2_alert(CO_alert);
|
||||||
|
|
||||||
|
const char* CO2_threshold = ini_get(ini_ctl, "ALERT", "CO2_THRESHOLD");
|
||||||
|
xSet_CO2_threshold(CO2_threshold);
|
||||||
|
const char* CO2_alert = ini_get(ini_ctl, "ALERT", "CO2_alert");
|
||||||
|
xSet_H2_alert(CO2_alert);
|
||||||
|
|
||||||
|
const char* CH4_threshold = ini_get(ini_ctl, "ALERT", "CH4_THRESHOLD");
|
||||||
|
xSet_CH4_period(CH4_threshold);
|
||||||
|
|
||||||
|
const char* C2H2_threshold = ini_get(ini_ctl, "ALERT", "C2H2_THRESHOLD");
|
||||||
|
xSet_C2H2_threshold(C2H2_threshold);
|
||||||
|
|
||||||
|
const char* C2H4_threshold = ini_get(ini_ctl, "ALERT", "C2H4_THRESHOLD");
|
||||||
|
xSet_C2H4_period(C2H4_threshold);
|
||||||
|
|
||||||
|
const char* C2H6_threshold = ini_get(ini_ctl, "ALERT", "C2H6_THRESHOLD");
|
||||||
|
xSet_C2H6_threshold(C2H6_threshold);
|
||||||
|
|
||||||
|
//读取程序延迟执行的时间
|
||||||
|
const char* process_delay_min = ini_get(ini_ctl, "PROCESS", "DELAY_RUN_MIN");
|
||||||
|
if (process_delay_min) {
|
||||||
|
pro_delay_min = atoi(process_delay_min);
|
||||||
|
printf("delay para %s min\n", process_delay_min);
|
||||||
|
printf("delay para: %d min\n", pro_delay_min);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
pro_delay_min = DELAY_MIN_DEFAULT;
|
||||||
|
printf("DELAY_RUN_MIN is null,use default\n");
|
||||||
|
printf("delay para: %d min\n", pro_delay_min);
|
||||||
|
}
|
||||||
|
|
||||||
|
ini_free(ini_ctl);
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
#ifndef _COMMON_H_
|
||||||
|
#define _COMMON_H_
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
|
||||||
|
void xDelay_run_process();
|
||||||
|
int xGet_config_from_configFile();
|
||||||
|
void xUnsigned_char_hex_out(const char* msg, unsigned char* src, int start, int end);
|
||||||
|
int xEnum_relay_comName(char* com_name);
|
||||||
|
#endif
|
@ -0,0 +1,13 @@
|
|||||||
|
#ifndef _DATABASE_H_
|
||||||
|
#define _DATABASE_H_
|
||||||
|
#pragma once
|
||||||
|
#include "../ysp_modbus_slave.h"
|
||||||
|
|
||||||
|
|
||||||
|
void xMutex_init();
|
||||||
|
int fill_ysp_data(ysp_modbus_regs_t* ysp_data);
|
||||||
|
|
||||||
|
#endif // !1
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
|||||||
|
#ifndef _GAS_MONITOR_
|
||||||
|
#define _GAS_MONITOR_
|
||||||
|
|
||||||
|
enum USB_PORT_E{
|
||||||
|
|
||||||
|
USB_PORT_0 = 0,
|
||||||
|
USB_PORT_1 = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define H2_THRESHOLD 120
|
||||||
|
#define CO_THRESHOLD 120
|
||||||
|
#define CO2_THRESHOLD 120
|
||||||
|
#define CH4_THRESHOLD 120
|
||||||
|
#define C2H2_THRESHOLD 120
|
||||||
|
#define C2H4_THRESHOLD 120
|
||||||
|
#define C2H6_THRESHOLD 120
|
||||||
|
|
||||||
|
//¼à²âÖÜÆÚ
|
||||||
|
void xSet_monitor_period(const char* mon_period);
|
||||||
|
void xSet_uart_buadrate(const char* uart_buadrate);
|
||||||
|
//ÆøÌ叿¾¯ãÐÖµ
|
||||||
|
void xSet_H2_threshold(const char* H2_threshold);
|
||||||
|
void xSet_CO_threshold(const char* CO_threshold);
|
||||||
|
void xSet_CO2_threshold(const char* CO2_threshold);
|
||||||
|
void xSet_CH4_period(const char* CH4_threshold);
|
||||||
|
void xSet_C2H2_threshold(const char* C2H2_threshold);
|
||||||
|
void xSet_C2H4_period(const char* C2H4_threshold);
|
||||||
|
void xSet_C2H6_threshold(const char* C2H6_threshold);
|
||||||
|
void xSet_H2_alert(const char* H2_alert);
|
||||||
|
void xSet_CO_alert(const char* CO_alert);
|
||||||
|
void xSet_CO2_alert(const char* CO2_alert);
|
||||||
|
|
||||||
|
int xGas_alert_task_init(char* uart_port);
|
||||||
|
#endif // !_GAS_MONITOR
|
@ -0,0 +1,161 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<ItemGroup Label="ProjectConfigurations">
|
||||||
|
<ProjectConfiguration Include="Debug|Win32">
|
||||||
|
<Configuration>Debug</Configuration>
|
||||||
|
<Platform>Win32</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Release|Win32">
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>Win32</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Debug|x64">
|
||||||
|
<Configuration>Debug</Configuration>
|
||||||
|
<Platform>x64</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Release|x64">
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>x64</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
</ItemGroup>
|
||||||
|
<PropertyGroup Label="Globals">
|
||||||
|
<VCProjectVersion>17.0</VCProjectVersion>
|
||||||
|
<Keyword>Win32Proj</Keyword>
|
||||||
|
<ProjectGuid>{01724c4f-628b-45b7-b0ff-b147d6d9d921}</ProjectGuid>
|
||||||
|
<RootNamespace>iedModbusmgr</RootNamespace>
|
||||||
|
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>true</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v143</PlatformToolset>
|
||||||
|
<CharacterSet>MultiByte</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>false</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v143</PlatformToolset>
|
||||||
|
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||||
|
<CharacterSet>MultiByte</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>true</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v143</PlatformToolset>
|
||||||
|
<CharacterSet>MultiByte</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>false</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v143</PlatformToolset>
|
||||||
|
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||||
|
<CharacterSet>MultiByte</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||||
|
<ImportGroup Label="ExtensionSettings">
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="Shared">
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<PropertyGroup Label="UserMacros" />
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Console</SubSystem>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
|
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Console</SubSystem>
|
||||||
|
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||||
|
<OptimizeReferences>true</OptimizeReferences>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Console</SubSystem>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
|
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Console</SubSystem>
|
||||||
|
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||||
|
<OptimizeReferences>true</OptimizeReferences>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClCompile Include="common.cpp" />
|
||||||
|
<ClCompile Include="database\database.cpp" />
|
||||||
|
<ClCompile Include="gas_monitor.cpp" />
|
||||||
|
<ClCompile Include="iedModbus_mgr.cpp" />
|
||||||
|
<ClCompile Include="ini.cpp" />
|
||||||
|
<ClCompile Include="modbus_lib\modbus-data.cpp" />
|
||||||
|
<ClCompile Include="modbus_lib\modbus-rtu.cpp" />
|
||||||
|
<ClCompile Include="modbus_lib\modbus-tcp.cpp" />
|
||||||
|
<ClCompile Include="modbus_lib\modbus.cpp" />
|
||||||
|
<ClCompile Include="SerialPort.cpp" />
|
||||||
|
<ClCompile Include="ysp_modbus_slave.cpp" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClInclude Include="common.h" />
|
||||||
|
<ClInclude Include="database\database.h" />
|
||||||
|
<ClInclude Include="gas_monitor.h" />
|
||||||
|
<ClInclude Include="ini.h" />
|
||||||
|
<ClInclude Include="modbus_lib\config.h" />
|
||||||
|
<ClInclude Include="modbus_lib\modbus-private.h" />
|
||||||
|
<ClInclude Include="modbus_lib\modbus-rtu-private.h" />
|
||||||
|
<ClInclude Include="modbus_lib\modbus-rtu.h" />
|
||||||
|
<ClInclude Include="modbus_lib\modbus-tcp-private.h" />
|
||||||
|
<ClInclude Include="modbus_lib\modbus-tcp.h" />
|
||||||
|
<ClInclude Include="modbus_lib\modbus-version.h" />
|
||||||
|
<ClInclude Include="modbus_lib\modbus.h" />
|
||||||
|
<ClInclude Include="SerialPort.h" />
|
||||||
|
<ClInclude Include="ysp_modbus_slave.h" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
|
<ImportGroup Label="ExtensionTargets">
|
||||||
|
</ImportGroup>
|
||||||
|
</Project>
|
@ -0,0 +1,96 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<ItemGroup>
|
||||||
|
<Filter Include="源文件">
|
||||||
|
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||||
|
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||||
|
</Filter>
|
||||||
|
<Filter Include="头文件">
|
||||||
|
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||||
|
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||||
|
</Filter>
|
||||||
|
<Filter Include="资源文件">
|
||||||
|
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||||
|
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||||
|
</Filter>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClCompile Include="iedModbus_mgr.cpp">
|
||||||
|
<Filter>源文件</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="modbus_lib\modbus.cpp">
|
||||||
|
<Filter>源文件</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="modbus_lib\modbus-data.cpp">
|
||||||
|
<Filter>源文件</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="modbus_lib\modbus-rtu.cpp">
|
||||||
|
<Filter>源文件</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="modbus_lib\modbus-tcp.cpp">
|
||||||
|
<Filter>源文件</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="database\database.cpp">
|
||||||
|
<Filter>源文件</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="common.cpp">
|
||||||
|
<Filter>源文件</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="gas_monitor.cpp">
|
||||||
|
<Filter>源文件</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="ini.cpp">
|
||||||
|
<Filter>源文件</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="SerialPort.cpp">
|
||||||
|
<Filter>源文件</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="ysp_modbus_slave.cpp">
|
||||||
|
<Filter>源文件</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClInclude Include="common.h">
|
||||||
|
<Filter>头文件</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="gas_monitor.h">
|
||||||
|
<Filter>头文件</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="ini.h">
|
||||||
|
<Filter>头文件</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="SerialPort.h">
|
||||||
|
<Filter>头文件</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="ysp_modbus_slave.h">
|
||||||
|
<Filter>头文件</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="modbus_lib\config.h">
|
||||||
|
<Filter>头文件</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="modbus_lib\modbus.h">
|
||||||
|
<Filter>头文件</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="modbus_lib\modbus-private.h">
|
||||||
|
<Filter>头文件</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="modbus_lib\modbus-rtu.h">
|
||||||
|
<Filter>头文件</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="modbus_lib\modbus-rtu-private.h">
|
||||||
|
<Filter>头文件</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="modbus_lib\modbus-tcp.h">
|
||||||
|
<Filter>头文件</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="modbus_lib\modbus-tcp-private.h">
|
||||||
|
<Filter>头文件</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="modbus_lib\modbus-version.h">
|
||||||
|
<Filter>头文件</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="database\database.h">
|
||||||
|
<Filter>头文件</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup />
|
||||||
|
</Project>
|
@ -0,0 +1,274 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2016 rxi
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
|
#include "ini.h"
|
||||||
|
|
||||||
|
struct ini_t {
|
||||||
|
char *data;
|
||||||
|
char *end;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/* Case insensitive string compare */
|
||||||
|
static int strcmpci(const char *a, const char *b) {
|
||||||
|
for (;;) {
|
||||||
|
int d = tolower(*a) - tolower(*b);
|
||||||
|
if (d != 0 || !*a) {
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
a++, b++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns the next string in the split data */
|
||||||
|
static char* next(ini_t *ini, char *p) {
|
||||||
|
p += strlen(p);
|
||||||
|
while (p < ini->end && *p == '\0') {
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void trim_back(ini_t *ini, char *p) {
|
||||||
|
while (p >= ini->data && (*p == ' ' || *p == '\t' || *p == '\r')) {
|
||||||
|
*p-- = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static char* discard_line(ini_t *ini, char *p) {
|
||||||
|
while (p < ini->end && *p != '\n') {
|
||||||
|
*p++ = '\0';
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static char *unescape_quoted_value(ini_t *ini, char *p) {
|
||||||
|
/* Use `q` as write-head and `p` as read-head, `p` is always ahead of `q`
|
||||||
|
* as escape sequences are always larger than their resultant data */
|
||||||
|
char *q = p;
|
||||||
|
p++;
|
||||||
|
while (p < ini->end && *p != '"' && *p != '\r' && *p != '\n') {
|
||||||
|
if (*p == '\\') {
|
||||||
|
/* Handle escaped char */
|
||||||
|
p++;
|
||||||
|
switch (*p) {
|
||||||
|
default : *q = *p; break;
|
||||||
|
case 'r' : *q = '\r'; break;
|
||||||
|
case 'n' : *q = '\n'; break;
|
||||||
|
case 't' : *q = '\t'; break;
|
||||||
|
case '\r' :
|
||||||
|
case '\n' :
|
||||||
|
case '\0' : goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
/* Handle normal char */
|
||||||
|
*q = *p;
|
||||||
|
}
|
||||||
|
q++, p++;
|
||||||
|
}
|
||||||
|
end:
|
||||||
|
return q;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Splits data in place into strings containing section-headers, keys and
|
||||||
|
* values using one or more '\0' as a delimiter. Unescapes quoted values */
|
||||||
|
static void split_data(ini_t *ini) {
|
||||||
|
char *value_start, *line_start;
|
||||||
|
char *p = ini->data;
|
||||||
|
|
||||||
|
while (p < ini->end) {
|
||||||
|
switch (*p) {
|
||||||
|
case '\r':
|
||||||
|
case '\n':
|
||||||
|
case '\t':
|
||||||
|
case ' ':
|
||||||
|
*p = '\0';
|
||||||
|
/* Fall through */
|
||||||
|
|
||||||
|
case '\0':
|
||||||
|
p++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '[':
|
||||||
|
p += strcspn(p, "]\n");
|
||||||
|
*p = '\0';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ';':
|
||||||
|
p = discard_line(ini, p);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
line_start = p;
|
||||||
|
p += strcspn(p, "=\n");
|
||||||
|
|
||||||
|
/* Is line missing a '='? */
|
||||||
|
if (*p != '=') {
|
||||||
|
p = discard_line(ini, line_start);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
trim_back(ini, p - 1);
|
||||||
|
|
||||||
|
/* Replace '=' and whitespace after it with '\0' */
|
||||||
|
do {
|
||||||
|
*p++ = '\0';
|
||||||
|
} while (*p == ' ' || *p == '\r' || *p == '\t');
|
||||||
|
|
||||||
|
/* Is a value after '=' missing? */
|
||||||
|
if (*p == '\n' || *p == '\0') {
|
||||||
|
p = discard_line(ini, line_start);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*p == '"') {
|
||||||
|
/* Handle quoted string value */
|
||||||
|
value_start = p;
|
||||||
|
p = unescape_quoted_value(ini, p);
|
||||||
|
|
||||||
|
/* Was the string empty? */
|
||||||
|
if (p == value_start) {
|
||||||
|
p = discard_line(ini, line_start);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Discard the rest of the line after the string value */
|
||||||
|
p = discard_line(ini, p);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
/* Handle normal value */
|
||||||
|
p += strcspn(p, "\n");
|
||||||
|
trim_back(ini, p - 1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ini_t* ini_load(const char *filename) {
|
||||||
|
ini_t *ini = NULL;
|
||||||
|
FILE *fp = NULL;
|
||||||
|
int n, sz;
|
||||||
|
|
||||||
|
/* Init ini struct */
|
||||||
|
ini = (ini_t*)malloc(sizeof(*ini));
|
||||||
|
if (!ini) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
memset(ini, 0, sizeof(*ini));
|
||||||
|
|
||||||
|
/* Open file */
|
||||||
|
fopen_s(&fp,filename, "rb");
|
||||||
|
if (!fp) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get file size */
|
||||||
|
fseek(fp, 0, SEEK_END);
|
||||||
|
sz = ftell(fp);
|
||||||
|
rewind(fp);
|
||||||
|
|
||||||
|
/* Load file content into memory, null terminate, init end var */
|
||||||
|
ini->data = (char*)malloc(sz + 1);
|
||||||
|
ini->data[sz] = '\0';
|
||||||
|
ini->end = ini->data + sz;
|
||||||
|
n = fread(ini->data, 1, sz, fp);
|
||||||
|
if (n != sz) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prepare data */
|
||||||
|
split_data(ini);
|
||||||
|
|
||||||
|
/* Clean up and return */
|
||||||
|
fclose(fp);
|
||||||
|
return ini;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
if (fp) fclose(fp);
|
||||||
|
if (ini) ini_free(ini);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ini_free(ini_t *ini) {
|
||||||
|
free(ini->data);
|
||||||
|
free(ini);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const char* ini_get(ini_t *ini, const char *section, const char *key) {
|
||||||
|
char *current_section = (char*)"";
|
||||||
|
char *val;
|
||||||
|
char *p = ini->data;
|
||||||
|
|
||||||
|
if (*p == '\0') {
|
||||||
|
p = next(ini, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (p < ini->end) {
|
||||||
|
if (*p == '[') {
|
||||||
|
/* Handle section */
|
||||||
|
current_section = p + 1;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
/* Handle key */
|
||||||
|
val = next(ini, p);
|
||||||
|
if (!section || !strcmpci(section, current_section)) {
|
||||||
|
if (!strcmpci(p, key)) {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
p = next(ini, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int ini_sget(
|
||||||
|
ini_t *ini, const char *section, const char *key,
|
||||||
|
const char *scanfmt, void *dst
|
||||||
|
) {
|
||||||
|
const char *val = ini_get(ini, section, key);
|
||||||
|
if (!val) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (scanfmt) {
|
||||||
|
sscanf_s(val, scanfmt, dst);
|
||||||
|
} else {
|
||||||
|
*((const char**) dst) = val;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2016 rxi
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the MIT license. See `ini.c` for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef INI_H
|
||||||
|
#define INI_H
|
||||||
|
|
||||||
|
#define INI_VERSION "0.1.1"
|
||||||
|
|
||||||
|
typedef struct ini_t ini_t;
|
||||||
|
|
||||||
|
ini_t* ini_load(const char *filename);
|
||||||
|
void ini_free(ini_t *ini);
|
||||||
|
const char* ini_get(ini_t *ini, const char *section, const char *key);
|
||||||
|
int ini_sget(ini_t *ini, const char *section, const char *key, const char *scanfmt, void *dst);
|
||||||
|
|
||||||
|
#endif
|
@ -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 <arpa/inet.h> 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 <dlfcn.h> header file. */
|
||||||
|
/* #undef HAVE_DLFCN_H */
|
||||||
|
|
||||||
|
/* Define to 1 if you have the <errno.h> header file. */
|
||||||
|
#define HAVE_ERRNO_H 1
|
||||||
|
|
||||||
|
/* Define to 1 if you have the <fcntl.h> 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 <inttypes.h> header file. */
|
||||||
|
#define HAVE_INTTYPES_H 1
|
||||||
|
|
||||||
|
/* Define to 1 if you have the <limits.h> header file. */
|
||||||
|
#define HAVE_LIMITS_H 1
|
||||||
|
|
||||||
|
/* Define to 1 if you have the <linux/serial.h> header file. */
|
||||||
|
/* #undef HAVE_LINUX_SERIAL_H */
|
||||||
|
|
||||||
|
/* Define to 1 if you have the <memory.h> 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 <netdb.h> header file. */
|
||||||
|
/* #undef HAVE_NETDB_H */
|
||||||
|
|
||||||
|
/* Define to 1 if you have the <netinet/in.h> header file. */
|
||||||
|
/* #undef HAVE_NETINET_IN_H */
|
||||||
|
|
||||||
|
/* Define to 1 if you have the <netinet/tcp.h> 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 <stdint.h> header file. */
|
||||||
|
#define HAVE_STDINT_H 1
|
||||||
|
|
||||||
|
/* Define to 1 if you have the <stdlib.h> 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 <strings.h> header file. */
|
||||||
|
/* #undef HAVE_STRINGS_H */
|
||||||
|
|
||||||
|
/* Define to 1 if you have the <string.h> 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 <sys/ioctl.h> header file. */
|
||||||
|
/* #undef HAVE_SYS_IOCTL_H */
|
||||||
|
|
||||||
|
/* Define to 1 if you have the <sys/socket.h> header file. */
|
||||||
|
/* #undef HAVE_SYS_SOCKET_H */
|
||||||
|
|
||||||
|
/* Define to 1 if you have the <sys/stat.h> header file. */
|
||||||
|
#define HAVE_SYS_STAT_H 1
|
||||||
|
|
||||||
|
/* Define to 1 if you have the <sys/time.h> header file. */
|
||||||
|
/* #undef HAVE_SYS_TIME_H */
|
||||||
|
|
||||||
|
/* Define to 1 if you have the <sys/types.h> header file. */
|
||||||
|
#define HAVE_SYS_TYPES_H 1
|
||||||
|
|
||||||
|
/* Define to 1 if you have the <termios.h> header file. */
|
||||||
|
/* #undef HAVE_TERMIOS_H */
|
||||||
|
|
||||||
|
/* Define to 1 if you have the <time.h> header file. */
|
||||||
|
#define HAVE_TIME_H 1
|
||||||
|
|
||||||
|
/* Define to 1 if you have the <unistd.h> 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 <vfork.h> header file. */
|
||||||
|
/* #undef HAVE_VFORK_H */
|
||||||
|
|
||||||
|
/* Define to 1 if you have the <winsock2.h> 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.1"
|
||||||
|
|
||||||
|
/* 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.1"
|
||||||
|
|
||||||
|
/* Define to 1 if you have the ANSI C header files. */
|
||||||
|
#define STDC_HEADERS 1
|
||||||
|
|
||||||
|
/* Define to 1 if you can safely include both <sys/time.h> and <time.h>. */
|
||||||
|
/* #undef TIME_WITH_SYS_TIME */
|
||||||
|
|
||||||
|
/* Version number of package */
|
||||||
|
#define VERSION "3.1.1"
|
||||||
|
|
||||||
|
/* Define to empty if `const' does not conform to ANSI C. */
|
||||||
|
/* #undef const */
|
||||||
|
|
||||||
|
/* Define to `int' if <sys/types.h> does not define. */
|
||||||
|
/* #undef pid_t */
|
||||||
|
|
||||||
|
/* Define to `unsigned int' if <sys/types.h> does not define. */
|
||||||
|
/* #undef size_t */
|
||||||
|
|
||||||
|
/* Define as `fork' if `vfork' does not work. */
|
||||||
|
#define vfork fork
|
@ -0,0 +1,294 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © Stéphane Raimbault <stephane.raimbault@gmail.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
#ifndef _MSC_VER
|
||||||
|
# include <stdint.h>
|
||||||
|
#else
|
||||||
|
# include "stdint.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
# include <winsock2.h>
|
||||||
|
#else
|
||||||
|
# include <arpa/inet.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include "modbus.h"
|
||||||
|
|
||||||
|
#if defined(HAVE_BYTESWAP_H)
|
||||||
|
# include <byteswap.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
# include <libkern/OSByteOrder.h>
|
||||||
|
# define bswap_16 OSSwapInt16
|
||||||
|
# define bswap_32 OSSwapInt32
|
||||||
|
# define bswap_64 OSSwapInt64
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__GNUC__)
|
||||||
|
# define GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__ * 10)
|
||||||
|
# if GCC_VERSION >= 430
|
||||||
|
// Since GCC >= 4.30, GCC provides __builtin_bswapXX() alternatives so we switch to them
|
||||||
|
# undef bswap_32
|
||||||
|
# define bswap_32 __builtin_bswap32
|
||||||
|
# endif
|
||||||
|
# if GCC_VERSION >= 480
|
||||||
|
# undef bswap_16
|
||||||
|
# define bswap_16 __builtin_bswap16
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(_MSC_VER) && (_MSC_VER >= 1400)
|
||||||
|
# define bswap_32 _byteswap_ulong
|
||||||
|
# define bswap_16 _byteswap_ushort
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !defined(bswap_16)
|
||||||
|
# warning "Fallback on C functions for bswap_16"
|
||||||
|
static inline uint16_t bswap_16(uint16_t x)
|
||||||
|
{
|
||||||
|
return (x >> 8) | (x << 8);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !defined(bswap_32)
|
||||||
|
# warning "Fallback on C functions for bswap_32"
|
||||||
|
static inline uint32_t bswap_32(uint32_t x)
|
||||||
|
{
|
||||||
|
return (bswap_16(x & 0xffff) << 16) | (bswap_16(x >> 16));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
a = (src[0] >> 8) & 0xFF;
|
||||||
|
b = (src[0] >> 0) & 0xFF;
|
||||||
|
c = (src[1] >> 8) & 0xFF;
|
||||||
|
d = (src[1] >> 0) & 0xFF;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
a = (src[0] >> 8) & 0xFF;
|
||||||
|
b = (src[0] >> 0) & 0xFF;
|
||||||
|
c = (src[1] >> 8) & 0xFF;
|
||||||
|
d = (src[1] >> 0) & 0xFF;
|
||||||
|
|
||||||
|
i = (d << 24) | (c << 16) | (b << 8) | (a << 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;
|
||||||
|
|
||||||
|
a = (src[0] >> 8) & 0xFF;
|
||||||
|
b = (src[0] >> 0) & 0xFF;
|
||||||
|
c = (src[1] >> 8) & 0xFF;
|
||||||
|
d = (src[1] >> 0) & 0xFF;
|
||||||
|
|
||||||
|
i = (b << 24) | (a << 16) | (d << 8) | (c << 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;
|
||||||
|
|
||||||
|
a = (src[0] >> 8) & 0xFF;
|
||||||
|
b = (src[0] >> 0) & 0xFF;
|
||||||
|
c = (src[1] >> 8) & 0xFF;
|
||||||
|
d = (src[1] >> 0) & 0xFF;
|
||||||
|
|
||||||
|
i = (c << 24) | (d << 16) | (a << 8) | (b << 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)
|
||||||
|
{
|
||||||
|
float f;
|
||||||
|
uint32_t i;
|
||||||
|
|
||||||
|
i = (((uint32_t) src[1]) << 16) + src[0];
|
||||||
|
memcpy(&f, &i, sizeof(float));
|
||||||
|
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set a float to 4 bytes for Modbus w/o any conversion (ABCD) */
|
||||||
|
void modbus_set_float_abcd(float f, uint16_t *dest)
|
||||||
|
{
|
||||||
|
uint32_t i;
|
||||||
|
uint8_t *out = (uint8_t *) dest;
|
||||||
|
uint8_t a, b, c, d;
|
||||||
|
|
||||||
|
memcpy(&i, &f, sizeof(uint32_t));
|
||||||
|
a = (i >> 24) & 0xFF;
|
||||||
|
b = (i >> 16) & 0xFF;
|
||||||
|
c = (i >> 8) & 0xFF;
|
||||||
|
d = (i >> 0) & 0xFF;
|
||||||
|
|
||||||
|
out[0] = a;
|
||||||
|
out[1] = b;
|
||||||
|
out[2] = c;
|
||||||
|
out[3] = 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)
|
||||||
|
{
|
||||||
|
uint32_t i;
|
||||||
|
uint8_t *out = (uint8_t *) dest;
|
||||||
|
uint8_t a, b, c, d;
|
||||||
|
|
||||||
|
memcpy(&i, &f, sizeof(uint32_t));
|
||||||
|
a = (i >> 24) & 0xFF;
|
||||||
|
b = (i >> 16) & 0xFF;
|
||||||
|
c = (i >> 8) & 0xFF;
|
||||||
|
d = (i >> 0) & 0xFF;
|
||||||
|
|
||||||
|
out[0] = d;
|
||||||
|
out[1] = c;
|
||||||
|
out[2] = b;
|
||||||
|
out[3] = a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set a float to 4 bytes for Modbus with byte swap conversion (BADC) */
|
||||||
|
void modbus_set_float_badc(float f, uint16_t *dest)
|
||||||
|
{
|
||||||
|
uint32_t i;
|
||||||
|
uint8_t *out = (uint8_t *) dest;
|
||||||
|
uint8_t a, b, c, d;
|
||||||
|
|
||||||
|
memcpy(&i, &f, sizeof(uint32_t));
|
||||||
|
a = (i >> 24) & 0xFF;
|
||||||
|
b = (i >> 16) & 0xFF;
|
||||||
|
c = (i >> 8) & 0xFF;
|
||||||
|
d = (i >> 0) & 0xFF;
|
||||||
|
|
||||||
|
out[0] = b;
|
||||||
|
out[1] = a;
|
||||||
|
out[2] = d;
|
||||||
|
out[3] = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set a float to 4 bytes for Modbus with word swap conversion (CDAB) */
|
||||||
|
void modbus_set_float_cdab(float f, uint16_t *dest)
|
||||||
|
{
|
||||||
|
uint32_t i;
|
||||||
|
uint8_t *out = (uint8_t *) dest;
|
||||||
|
uint8_t a, b, c, d;
|
||||||
|
|
||||||
|
memcpy(&i, &f, sizeof(uint32_t));
|
||||||
|
a = (i >> 24) & 0xFF;
|
||||||
|
b = (i >> 16) & 0xFF;
|
||||||
|
c = (i >> 8) & 0xFF;
|
||||||
|
d = (i >> 0) & 0xFF;
|
||||||
|
|
||||||
|
out[0] = c;
|
||||||
|
out[1] = d;
|
||||||
|
out[2] = a;
|
||||||
|
out[3] = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* DEPRECATED - Set a float to 4 bytes in a sort of Modbus format! */
|
||||||
|
void modbus_set_float(float f, uint16_t *dest)
|
||||||
|
{
|
||||||
|
uint32_t i;
|
||||||
|
|
||||||
|
memcpy(&i, &f, sizeof(uint32_t));
|
||||||
|
dest[0] = (uint16_t) i;
|
||||||
|
dest[1] = (uint16_t) (i >> 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © Stéphane Raimbault <stephane.raimbault@gmail.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MODBUS_PRIVATE_H
|
||||||
|
#define MODBUS_PRIVATE_H
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
#ifndef _MSC_VER
|
||||||
|
# include <stdint.h>
|
||||||
|
# include <sys/time.h>
|
||||||
|
#else
|
||||||
|
# include "stdint.h"
|
||||||
|
# include <time.h>
|
||||||
|
typedef int ssize_t;
|
||||||
|
#endif
|
||||||
|
// clang-format on
|
||||||
|
#include "config.h"
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#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 (*prepare_response_tid)(const uint8_t *req, int *req_length);
|
||||||
|
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 */
|
@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © Stéphane Raimbault <stephane.raimbault@gmail.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MODBUS_RTU_PRIVATE_H
|
||||||
|
#define MODBUS_RTU_PRIVATE_H
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef _MSC_VER
|
||||||
|
#include <stdint.h>
|
||||||
|
#else
|
||||||
|
#include "stdint.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
#include <windows.h>
|
||||||
|
#else
|
||||||
|
#include <termios.h>
|
||||||
|
#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 */
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © Stéphane Raimbault <stephane.raimbault@gmail.com>
|
||||||
|
*
|
||||||
|
* 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 */
|
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © Stéphane Raimbault <stephane.raimbault@gmail.com>
|
||||||
|
*
|
||||||
|
* 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 */
|
@ -0,0 +1,978 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © Stéphane Raimbault <stephane.raimbault@gmail.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
#if defined(_WIN32)
|
||||||
|
#pragma comment(lib,"ws2_32.lib")
|
||||||
|
# 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 <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#ifndef _MSC_VER
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
#include <signal.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
/* Already set in modbus-tcp.h but it seems order matters in VS2005 */
|
||||||
|
# include <winsock2.h>
|
||||||
|
# include <ws2tcpip.h>
|
||||||
|
# define SHUT_RDWR 2
|
||||||
|
# define close closesocket
|
||||||
|
# define strdup _strdup
|
||||||
|
#else
|
||||||
|
# include <sys/socket.h>
|
||||||
|
# include <sys/ioctl.h>
|
||||||
|
|
||||||
|
#if defined(__OpenBSD__) || (defined(__FreeBSD__) && __FreeBSD__ < 5)
|
||||||
|
# define OS_BSD
|
||||||
|
# include <netinet/in_systm.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
# include <netinet/in.h>
|
||||||
|
# include <netinet/ip.h>
|
||||||
|
# include <netinet/tcp.h>
|
||||||
|
# include <arpa/inet.h>
|
||||||
|
# include <netdb.h>
|
||||||
|
#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 = (modbus_tcp_t*)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_prepare_response_tid(const uint8_t *req, int *req_length)
|
||||||
|
{
|
||||||
|
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, (const char *) &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, (const void *) &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) {
|
||||||
|
/* Timeout or fail */
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The connection is established if SO_ERROR and optval are set to 0 */
|
||||||
|
rc = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (char *) &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 = (modbus_tcp_t*)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 = (modbus_tcp_pi_t*)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) {
|
||||||
|
fprintf(stderr, "Error returned by getaddrinfo: %s\n", gai_strerror(rc));
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
int 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) {
|
||||||
|
rc_sum += rc;
|
||||||
|
}
|
||||||
|
} 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 = (modbus_tcp_t*)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, (char *) &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 = (modbus_tcp_pi_t*)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) {
|
||||||
|
fprintf(stderr, "Error returned by getaddrinfo: %s\n", gai_strerror(rc));
|
||||||
|
}
|
||||||
|
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, (char *) &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 = (modbus_tcp_pi_t*)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_prepare_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_prepare_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © Stéphane Raimbault <stephane.raimbault@gmail.com>
|
||||||
|
*
|
||||||
|
* 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 <errno.h> */
|
||||||
|
#include <winsock2.h>
|
||||||
|
#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 */
|
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © Stéphane Raimbault <stephane.raimbault@gmail.com>
|
||||||
|
*
|
||||||
|
* 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 (1)
|
||||||
|
|
||||||
|
/* The full version, like 1.2.3 */
|
||||||
|
#define LIBMODBUS_VERSION 3.1.1
|
||||||
|
|
||||||
|
/* The full version, in string form (suited for string concatenation)
|
||||||
|
*/
|
||||||
|
#define LIBMODBUS_VERSION_STRING "3.1.1"
|
||||||
|
|
||||||
|
/* 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 */
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,330 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © Stéphane Raimbault <stephane.raimbault@gmail.com>
|
||||||
|
*
|
||||||
|
* 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 <sys/param.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef _MSC_VER
|
||||||
|
# include <stdint.h>
|
||||||
|
#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
|
||||||
|
|
||||||
|
#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;
|
||||||
|
|
||||||
|
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_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 */
|
@ -0,0 +1,17 @@
|
|||||||
|
[MODBUS]
|
||||||
|
SLAVE_IP_ADDR=192.168.1.21
|
||||||
|
SLAVE_PORT=1502
|
||||||
|
[ALERT]
|
||||||
|
H2_THRESHOLD = 120
|
||||||
|
CO_THRESHOLD=120
|
||||||
|
CO2_THRESHOLD=120
|
||||||
|
CH4_THRESHOLD=120
|
||||||
|
C2H2_THRESHOLD=120.85
|
||||||
|
C2H4_THRESHOLD=120
|
||||||
|
C2H6_THRESHOLD=120
|
||||||
|
H2_alert=1
|
||||||
|
CO_alert=1
|
||||||
|
CO2_alert=0
|
||||||
|
[MONITOR]
|
||||||
|
GAS_MONITOR_PERIOD = 40
|
||||||
|
|
Loading…
Reference in New Issue