Initial Commit

main
Matthew 12 months ago
parent 2342fb2019
commit 808ac5fbc2

@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.7.34018.315
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "modbus_tools", "modbus_tools\modbus_tools.vcxproj", "{7F9F9A08-25DC-4EF3-BDFD-6E8490445C12}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{7F9F9A08-25DC-4EF3-BDFD-6E8490445C12}.Debug|x64.ActiveCfg = Debug|x64
{7F9F9A08-25DC-4EF3-BDFD-6E8490445C12}.Debug|x64.Build.0 = Debug|x64
{7F9F9A08-25DC-4EF3-BDFD-6E8490445C12}.Debug|x86.ActiveCfg = Debug|Win32
{7F9F9A08-25DC-4EF3-BDFD-6E8490445C12}.Debug|x86.Build.0 = Debug|Win32
{7F9F9A08-25DC-4EF3-BDFD-6E8490445C12}.Release|x64.ActiveCfg = Release|x64
{7F9F9A08-25DC-4EF3-BDFD-6E8490445C12}.Release|x64.Build.0 = Release|x64
{7F9F9A08-25DC-4EF3-BDFD-6E8490445C12}.Release|x86.ActiveCfg = Release|Win32
{7F9F9A08-25DC-4EF3-BDFD-6E8490445C12}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8B564624-5C96-44D9-8ECC-CA659798DF9E}
EndGlobalSection
EndGlobal

@ -0,0 +1,2 @@
1. 第一次打开工程后在 vs的调试选项中打开项目调试属性在高级选项中将unicode字符集改成使用多字节字符集
2. 编译时前将版本改成release 平台改成x86

@ -0,0 +1,98 @@
#include "common.h"
#include "stdio.h"
#include "stdlib.h"
#include "ini.h"
#include "ysp_modbus_master.h"
#include "windows.h"
#define CONFIG_FILE_NAME "modbusConnect.ini"
void xUnsigned_char_hex_out(const char *msg, unsigned char* src, int start, int number)
{
int new_line = 0;
int end = start + number;
if (msg != NULL) {
printf("%s:\n", msg);
}
for (int i = start; i < end; i++) {
printf("%02X ", src[i]);
new_line++;
if ( !(new_line % 8)) {
printf("\n");
}
}
printf("\n");
}
void xRead_reg_hex_out(unsigned short reg_start_addr,const char* msg, unsigned char* src, int start, int number)
{
int new_line = 0;
int end = start + number;
int reg_number = 0;
if (msg != NULL) {
printf("\n%s:\n", msg);
}
printf("-----------------\n");
printf("reg addr | data\n");
//printf("(%d)%.4x ", reg_number, reg_start_addr);
for (int i = start; i < end; i++) {
if ((new_line % 2) == 0) {
printf("\n");
printf("(%d)%.4X |", ++reg_number, reg_start_addr++);
}
new_line++;
printf("%02X ", src[i]);
}
printf("\n-----------------\n\n");
}
int xGet_config_from_file()
{
#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 file fail,all para use default\n");
return 1;
}
//modbus ´Ó»útcp ipaddr
const char* ip_addr = ini_get(ini_ctl, "MODBUS_SLAVE", "SLAVE_IP_ADDR");
xMaster_Set_slave_ip_addr(ip_addr);
//modbus´Ó»útcp port
const char* tcp_port = ini_get(ini_ctl, "MODBUS_SLAVE", "SLAVE_IP_PORT");
xMaster_Set_slave_port(tcp_port);
ini_free(ini_ctl);
return 0;
}

@ -0,0 +1,8 @@
#ifndef _COMMON_H_
#define _COMMON_H_
#pragma once
int xGet_config_from_file();
void xUnsigned_char_hex_out(const char* msg, unsigned char* src, int start, int end);
void xRead_reg_hex_out(unsigned short reg_start_addr, const char* msg, unsigned char* src, int start, int end);
#endif

@ -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,5 @@
[MODBUS_SLAVE]
SLAVE_IP_ADDR=192.168.1.21
SLAVE_IP_PORT=1502

@ -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,301 @@
/*
* 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);
}
uint32_t modbus_get_int(const uint16_t* src)
{
uint32_t i;
i = (((uint32_t)src[1]) << 16) + src[0];
return i;
}

@ -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,331 @@
/*
* 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);
MODBUS_API uint32_t modbus_get_int(const uint16_t * src);
#include "modbus-rtu.h"
#include "modbus-tcp.h"
MODBUS_END_DECLS
#endif /* MODBUS_H */

@ -0,0 +1,23 @@
// modbus_master_test.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include "stdio.h"
#include "stdlib.h"
#include "time.h"
#include "winsock.h"
#include "ini.h"
#include "common.h"
#include "ysp_modbus_master.h"
using namespace std;
#define CONFIG_FILE_NAME "yspConfig.ini"
int main(int argc, char** argv)
{
printf("modbus tools: as modbus master\n");
xGet_config_from_file();
xModbus_master_task_init();//MODBUS thread
while (1);
}

@ -0,0 +1,155 @@
<?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>{7f9f9a08-25dc-4ef3-bdfd-6e8490445c12}</ProjectGuid>
<RootNamespace>modbustools</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>Unicode</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>Unicode</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="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="modbus_tools.cpp" />
<ClCompile Include="ysp_modbus_master.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="common.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="ysp_modbus_master.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

@ -0,0 +1,78 @@
<?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="modbus_tools.cpp">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="common.cpp">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="ini.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="ysp_modbus_master.cpp">
<Filter>源文件</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<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="common.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="ini.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="ysp_modbus_master.h">
<Filter>头文件</Filter>
</ClInclude>
</ItemGroup>
</Project>

@ -0,0 +1,159 @@
#include <iostream>
#include "modbus_lib/modbus-tcp.h"
#include "common.h"
using namespace std;
#define REG_ADDR (header_length+1)
#define REG_NUM (header_length+3)
#define REG_VAL (header_length+3)
#define IP_SLAVE_ADDR "192.168.1.21"
#define TCP_PORT 1502
#define SERVER_ID 0x00000001
static char slave_ip_addr[20] = { 0 };
static unsigned short slave_tcp_port = TCP_PORT;
#define SLAVE_REG_ADDR 0x008E
enum {
TCP,
TCP_PI,
RTU
};
//modbus 从机tcp ipaddr
void xMaster_Set_slave_ip_addr(const char *ip_addr)
{
if (ip_addr) {
memcpy(slave_ip_addr, ip_addr, strlen(ip_addr));
printf("slave_ip_addr=%s\n", slave_ip_addr);
}
else {
memcpy(slave_ip_addr, IP_SLAVE_ADDR, strlen(IP_SLAVE_ADDR));
printf("ip_addr is null,use default\n");
}
}
//modbus从机tcp port
void xMaster_Set_slave_port(const char* tcp_port)
{
if (tcp_port) {
slave_tcp_port = atoi(tcp_port);
printf("tcp_port=%s\n", tcp_port);
printf("slave_tcp_port=%d\n", slave_tcp_port);
}
else {
slave_tcp_port = TCP_PORT;
printf("SLAVE_PORT is null,use default\n");
}
}
DWORD WINAPI xModbus_master_Proc(LPVOID lp) {
char* com = (char*)lp;
CHAR rcv_buf[1024] = { 0 };
int s = -1;
int i = 0;
int rc = 0;
modbus_t* ctx = NULL;
uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH];//接收缓冲区
int header_length;
uint16_t* ptr,*ptr1;
int use_backend=TCP;
uint16_t reg_addr, reg_val, reg_num;
uint16_t tmp = 0;
modbus_mapping_t* mb_mapping;
int nb_points;
uint16_t* tab_rp_registers = NULL;
//Read the starting address of the holding register
unsigned short start_hold_register = 0x0000;//读保持寄存器的起始地址
//Number of registers read from start address
int number_regs = 22;//从起始地址开始读的寄存器数量
nb_points = 500;
tab_rp_registers = (uint16_t*)malloc(nb_points * sizeof(uint16_t));
if (NULL != tab_rp_registers) {
memset(tab_rp_registers, 0, nb_points * sizeof(uint16_t));
}
if (use_backend == TCP) {
ctx = modbus_new_tcp(slave_ip_addr, 1502);
//modbus_set_slave(ctx, SERVER_ID);
}
modbus_set_response_timeout(ctx, 20, 0);
if (modbus_connect(ctx) == -1) {
fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno));
modbus_free(ctx);
return -1;
}
unsigned char* src = (unsigned char*)tab_rp_registers;
rc = modbus_read_registers(ctx, start_hold_register, number_regs, tab_rp_registers);
Sleep(3 * 1000);
//xUnsigned_char_hex_out("recv", src, 0, number_regs*2);
xRead_reg_hex_out(start_hold_register,"master recv", src, 0, number_regs * 2);
//int slave_addr = MODBUS_GET_INT32_FROM_INT16(tab_rp_registers, 0);
//printf("slave_addr=%d\n", slave_addr);
float H2 = modbus_get_float(tab_rp_registers);
printf("H2=%f\n", H2);
float CO = modbus_get_float(tab_rp_registers+2);
printf("CO=%f\n", CO);
float CO2 = modbus_get_float(tab_rp_registers+4);
printf("CO2=%f\n", CO2);
float CH4 = modbus_get_float(tab_rp_registers+6);
printf("CH4=%f\n", CH4);
float C2H2 = modbus_get_float(tab_rp_registers+8);
printf("C2H2=%f\n", C2H2);
float C2H4 = modbus_get_float(tab_rp_registers+10);
printf("C2H4=%f\n", C2H4);
float C2H6 = modbus_get_float(tab_rp_registers+12);
printf("C2H6=%f\n", C2H6);
float total = modbus_get_float(tab_rp_registers + 14);
printf("total=%f\n", total);
float water = modbus_get_float(tab_rp_registers+16);
printf("water=%f\n", water);
//xUnsigned_char_hex_out("recv", src, 36, 36+7);
int H32 = modbus_get_int(tab_rp_registers+18);
printf("H32=%d\n", H32);
int L32 = modbus_get_int(tab_rp_registers+20);
printf("L32=%d\n", L32);
time_t t = H32 << 32 | L32;
printf("t=%ld\n", t);
//time_t t3 = 0x64F9 << 16 | 0x4204;
//printf("t3=%ld\n", t3);
//time_t t4 = 1694056964;
//printf("%02x\n",t4);
while (1);
return 0;
}
int xModbus_master_task_init()
{
// 创建线程,返回句柄
int a = 0;
HANDLE h = CreateThread(NULL, 0, xModbus_master_Proc, NULL, 0, 0);
//WaitForSingleObject(h, INFINITE);
//CloseHandle(h);
return 0;
}

@ -0,0 +1,20 @@
#pragma once
#ifndef _YSP_MODBUS_MASTER_H
#define _YSP_MODBUS_MASTER_H
/*
008EH 008FH slave address
*/
//modbus 从机tcp ipaddr
void xMaster_Set_slave_ip_addr(const char* ip_addr);
//modbus从机tcp port
void xMaster_Set_slave_port(const char* tcp_port);
/*
+ // 寄存器地址连续
*/
int xModbus_master_task_init();
#endif

@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.7.34018.315
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "iedModbus_mgr", "iedModbus_mgr\iedModbus_mgr.vcxproj", "{01724C4F-628B-45B7-B0FF-B147D6D9D921}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{01724C4F-628B-45B7-B0FF-B147D6D9D921}.Debug|x64.ActiveCfg = Debug|x64
{01724C4F-628B-45B7-B0FF-B147D6D9D921}.Debug|x64.Build.0 = Debug|x64
{01724C4F-628B-45B7-B0FF-B147D6D9D921}.Debug|x86.ActiveCfg = Debug|Win32
{01724C4F-628B-45B7-B0FF-B147D6D9D921}.Debug|x86.Build.0 = Debug|Win32
{01724C4F-628B-45B7-B0FF-B147D6D9D921}.Release|x64.ActiveCfg = Release|x64
{01724C4F-628B-45B7-B0FF-B147D6D9D921}.Release|x64.Build.0 = Release|x64
{01724C4F-628B-45B7-B0FF-B147D6D9D921}.Release|x86.ActiveCfg = Release|Win32
{01724C4F-628B-45B7-B0FF-B147D6D9D921}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9114805C-437F-473A-8A0A-AA3A35B1EDEE}
EndGlobalSection
EndGlobal

@ -0,0 +1,2 @@
1. 第一次打开工程后在 vs的调试选项中打开项目调试属性在高级选项中将unicode字符集改成使用多字节字符集
2. 编译时前将版本改成release 平台改成x86

@ -0,0 +1,317 @@
//#include <windows.h>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <WinSock2.h>
#include "SerialPort.h"
#include <fileapi.h>
#include <winnt.h>
//获取继电器板子对应的串口名称
bool xGet_relay_com(char* com_name)
{
return true;
}
SerialPort::SerialPort()
{
}
SerialPort::~SerialPort()
{
}
bool SerialPort::open(const char* portname, int baudrate, char parity, char databit, char stopbit, FlowControl fc, char synchronizeflag)
{
this->synchronizeflag = synchronizeflag;
HANDLE hCom = NULL;
if (this->synchronizeflag)
{
std::cout << "uart W/R type: synchronizeflag\n" << synchronizeflag << std::endl;
//同步方式
hCom = CreateFile(portname,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
0,
NULL);
}
else
{
std::cout << "uart W/R type: asynchronousflag\n" << synchronizeflag << std::endl;
//异步方式
hCom = CreateFile(portname,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL);
}
if (hCom == (HANDLE)-1)
{
cout << "open uart file fail" << endl;
return false;
}
if (!SetupComm(hCom, 1024, 1024))
{
return false;
}
DCB dcb;
if (!GetCommState(hCom, &dcb))
{
return false;
}
memset(&dcb, 0, sizeof(dcb));
dcb.DCBlength = sizeof(dcb);
dcb.BaudRate = baudrate;
dcb.ByteSize = databit;
switch (parity)
{
case 0:
dcb.Parity = NOPARITY;
break;
case 1:
dcb.Parity = ODDPARITY;
break;
case 2:
dcb.Parity = EVENPARITY;
break;
case 3:
dcb.Parity = MARKPARITY;
break;
}
switch (stopbit)
{
case 1:
dcb.StopBits = ONESTOPBIT;
break;
case 2:
dcb.StopBits = TWOSTOPBITS;
break;
case 3:
dcb.StopBits = ONE5STOPBITS;
break;
}
dcb.fDsrSensitivity = FALSE;
dcb.fTXContinueOnXoff = FALSE;
dcb.fRtsControl = RTS_CONTROL_DISABLE;
dcb.fDtrControl = DTR_CONTROL_ENABLE;
switch (fc)
{
case NoFlowControl:
{
dcb.fOutxCtsFlow = FALSE;
dcb.fOutxDsrFlow = FALSE;
dcb.fOutX = FALSE;
dcb.fInX = FALSE;
break;
}
case CtsRtsFlowControl:
{
dcb.fOutxCtsFlow = TRUE;
dcb.fOutxDsrFlow = FALSE;
dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
dcb.fOutX = FALSE;
dcb.fInX = FALSE;
break;
}
case CtsDtrFlowControl:
{
dcb.fOutxCtsFlow = TRUE;
dcb.fOutxDsrFlow = FALSE;
dcb.fDtrControl = DTR_CONTROL_HANDSHAKE;
dcb.fOutX = FALSE;
dcb.fInX = FALSE;
break;
}
case DsrRtsFlowControl:
{
dcb.fOutxCtsFlow = FALSE;
dcb.fOutxDsrFlow = TRUE;
dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
dcb.fOutX = FALSE;
dcb.fInX = FALSE;
break;
}
case DsrDtrFlowControl:
{
dcb.fOutxCtsFlow = FALSE;
dcb.fOutxDsrFlow = TRUE;
dcb.fDtrControl = DTR_CONTROL_HANDSHAKE;
dcb.fOutX = FALSE;
dcb.fInX = FALSE;
break;
}
case XonXoffFlowControl:
{
dcb.fOutxCtsFlow = FALSE;
dcb.fOutxDsrFlow = FALSE;
dcb.fOutX = TRUE;
dcb.fInX = TRUE;
dcb.XonChar = 0x11;
dcb.XoffChar = 0x13;
dcb.XoffLim = 100;
dcb.XonLim = 100;
break;
}
}//end switch
if (!SetCommState(hCom, &dcb))
{
return false;
}
COMMTIMEOUTS TimeOuts;
#if 1
TimeOuts.ReadIntervalTimeout = 1000;
TimeOuts.ReadTotalTimeoutMultiplier = 500;
TimeOuts.ReadTotalTimeoutConstant = 500;
TimeOuts.WriteTotalTimeoutMultiplier = 500;
TimeOuts.WriteTotalTimeoutConstant = 2000;
#else
TimeOuts.ReadIntervalTimeout = 0;
TimeOuts.ReadTotalTimeoutMultiplier = 0;
TimeOuts.ReadTotalTimeoutConstant = 5000;
TimeOuts.WriteTotalTimeoutMultiplier = 0;
TimeOuts.WriteTotalTimeoutConstant = 0;
#endif
SetCommTimeouts(hCom, &TimeOuts);//超时时间设置
PurgeComm(hCom, PURGE_TXCLEAR | PURGE_RXCLEAR);
memcpy(pHandle, &hCom, sizeof(hCom));
return true;
}
void SerialPort::close()
{
HANDLE hCom = *(HANDLE*)pHandle;
CloseHandle(hCom);
}
int SerialPort::send(const void *buf,int len)
{
HANDLE hCom = *(HANDLE*)pHandle;
if (this->synchronizeflag)
{
// 同步方式
DWORD dwBytesWrite = len;
BOOL bWriteStat = WriteFile(hCom,
buf,
dwBytesWrite,
&dwBytesWrite,
NULL);
if (!bWriteStat)
{
return 0;
}
return dwBytesWrite;
}
else
{
DWORD dwBytesWrite = len;
DWORD dwErrorFlags;
COMSTAT comStat;
OVERLAPPED m_osWrite;
//创建一个用于OVERLAPPED的事件处理不会真正用到但系统要求这么做
memset(&m_osWrite, 0, sizeof(m_osWrite));
m_osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, "WriteEvent");
ClearCommError(hCom, &dwErrorFlags, &comStat);
BOOL bWriteStat = WriteFile(hCom,
buf,
dwBytesWrite,
&dwBytesWrite,
&m_osWrite);
if (!bWriteStat)
{
if (GetLastError() == ERROR_IO_PENDING)
{
WaitForSingleObject(m_osWrite.hEvent, 1000);
}
else
{
ClearCommError(hCom, &dwErrorFlags, &comStat);
CloseHandle(m_osWrite.hEvent);
return 0;
}
}
return dwBytesWrite;
}
}
int SerialPort::receive(void *buf,int maxlen)
{
HANDLE hCom = *(HANDLE*)pHandle;
if (this->synchronizeflag)
{
//同步方式
DWORD wCount = maxlen;
BOOL bReadStat = ReadFile(hCom,
buf,
wCount,
&wCount,
NULL);
if (!bReadStat)
{
return 0;
}
return wCount;
}
else
{
DWORD wCount = maxlen;
DWORD dwErrorFlags;
COMSTAT comStat;
OVERLAPPED m_osRead;
memset(&m_osRead, 0, sizeof(m_osRead));
m_osRead.hEvent = CreateEvent(NULL, TRUE, FALSE, "ReadEvent");
ClearCommError(hCom, &dwErrorFlags, &comStat);
if (!comStat.cbInQue)return 0;
BOOL bReadStat = ReadFile(hCom,
buf,
wCount,
&wCount,
&m_osRead);
if (!bReadStat)
{
if (GetLastError() == ERROR_IO_PENDING)
{
GetOverlappedResult(hCom, &m_osRead, &wCount, TRUE);
}
else
{
ClearCommError(hCom, &dwErrorFlags, &comStat);
CloseHandle(m_osRead.hEvent);
return 0;
}
}
return wCount;
}
}

@ -0,0 +1,47 @@
#ifndef _SERIALPORT_H
#define _SERIALPORT_H
#include <iostream>
using namespace std;
enum FlowControl
{
NoFlowControl,
CtsRtsFlowControl,
CtsDtrFlowControl,
DsrRtsFlowControl,
DsrDtrFlowControl,
XonXoffFlowControl
};
using namespace std;
class SerialPort
{
public:
SerialPort();
~SerialPort();
// 打开串口,成功返回true失败返回false
// portname(串口名): 在Windows下是"COM1""COM2"等在Linux下是"/dev/ttyS1"等
// baudrate(波特率): 9600、19200、38400、43000、56000、57600、115200
// parity(校验位): 0为无校验1为奇校验2为偶校验3为标记校验仅适用于windows)
// databit(数据位): 4-8(windows),5-8(linux)通常为8位
// stopbit(停止位): 1为1位停止位2为2位停止位,3为1.5位停止位
//synchronizeflag(同步、异步仅适用windows): 0 async, 1 sync
bool open(const char* portname, int baudrate, char parity, char databit, char stopbit, FlowControl fc, char synchronizeflag = 1);
//发送数据或写数据成功返回发送数据长度失败返回0
int send(const void *buf,int len);
//接受数据或读数据成功返回读取实际数据的长度失败返回0
int receive(void *buf,int maxlen);
//关闭串口
void close();
private:
int pHandle[16];
char synchronizeflag;
};
bool xGet_relay_com(char* com_name);
#endif

@ -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,368 @@
// sql_server_test.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include<stdio.h>
#include<string.h>
#include<windows.h>
#include<sql.h>
#include<sqlext.h>
#include<sqltypes.h>
#include <ctime>
#include "../ysp_modbus_slave.h"
HANDLE hMutex_database; //定义互斥对象
//入参time_fmt 格式为: 2023-09-01 12:32:43
//出参: 距离1970的对应的格林秒数
//mktime(t)受系统时区设置的影响 如果是东八区会减掉入参8个小时 如果是0时区则不减直接转
// 注意因为mktime的特性 所以入参 time_fmt必须是跟系统时区设置相关得到的格式化时间
void GetTimeFromFormat(const char* time_fmt, int* time_h32, int* time_l32)
{
struct tm tm_s = { 0 };
int year = 0, month = 0, day = 0, hour = 0, min = 0, sec = 0;
year = atoi(time_fmt);
month = atoi(time_fmt + 4);
day = atoi(time_fmt + 7);
hour = atoi(time_fmt + 11);
min = atoi(time_fmt + 14);
sec = atoi(time_fmt + 17);
//09 atoi 是 -9
if (month < 0)
month = 0 - month;
if (day < 0)
day = 0 - day;
if (hour < 0)
hour = 0 - hour;
if (min < 0)
min = 0 - min;
if (sec < 0)
sec = 0 - sec;
tm_s.tm_year = year - 1900;
tm_s.tm_mon = month - 1;
tm_s.tm_mday = day;
tm_s.tm_hour = hour;
tm_s.tm_min = min;
tm_s.tm_sec = sec;
//mktime受系统时区设置的影响 如果是东八区会减掉入参8个小时 如果是0时区则不减直接转
//即mktime得到是当前对应格林秒数(距离1970)
time_t t1 = mktime(&tm_s);
printf("data timestamp (str):%s\n", time_fmt);
printf("data Distance Greenwich Mean Time (s):%ld\n", t1);
*time_h32 = t1 >> 32 & 0x00000000FFFFFFFF;
*time_l32 = t1 & 0x00000000FFFFFFFF;
}
//#include <sqlext.h>
/*数据库中的实际表结构*/
typedef struct DataGas_table {
int ID;//类型为自增所以无法软件insert
int DeviceID;
char datetime[20];//格式: 1990-07-04 23:04:12
float H2;
float CO;
float CO2;
float CH4;
float C2H2;
float C2H4;
float C2H6;
float Total;
char RawData[2];//该软件中用不到实际表中为varbinary(max)类型
char Result[2];//实际为varchar
char ProcessedData[2];//该软件中用不到实际表中为varbinary(max)类型
}DataGas_tab_t;
#define DATAGAS_INIT(data) \
do { \
DataGas_tab_t data; \
memset(&data, 0, sizeof(data)); \
}while (0)
#define DATAGAS_INIT_DEFAULT(data) \
do { \
\
memset(&data, 67, sizeof(data)); \
memset(&data.datetime, 0, sizeof(data.datetime)); \
memcpy(data.datetime, "2023-09-04 17:24:44", sizeof("2023-09-04 17:24:44")); \
data.RawData[0] = 2;\
data.ProcessedData[0] = 2;\
data.RawData[1] = 0;\
data.Result[1] = 0;\
data.ProcessedData[1] = 0;\
}while (0)
SQLRETURN ret;
SQLHENV henv;
SQLHDBC hdbc;
SQLHSTMT hstmt;
//DeviceID, ReadDate, H2, CO, CO2, CH4, C2H2, C2H4, C2H6, Total, RawData, Result, ProcessedData
int DisplayError(SQLCHAR* e1, int e2, const char* msg, int len)
{
std::cout << "msg:" << msg << std::endl;
return 0;
}
int Sql_DataGas_insert(DataGas_tab_t data)
{
SQLCHAR SqlState[6], SQLStmt[100], Msg[SQL_MAX_MESSAGE_LENGTH];
SQLINTEGER NativeError;
SQLSMALLINT i, MsgLen;
SQLRETURN rc1, rc2;
char sql_cmd[1024] = { 0 };
const char* fmt = "insert into OCD_DB.dbo.DataGas (DeviceID, ReadDate, H2, CO, CO2, CH4, C2H2, C2H4, C2H6, Total, RawData, Result, ProcessedData) values(%d,\'%s\',%f,%f,%f,%f,%f,%f,%f,%f,%d,\'%s\',%d)";
snprintf(sql_cmd, sizeof(sql_cmd), fmt, data.DeviceID, data.datetime, data.H2, data.CO, data.CO2, data.CH4, data.C2H2, data.C2H4, data.C2H6, data.Total, 5, data.Result, 6);
std::cout << "data.datetime " << data.datetime << std::endl;
std::cout << "data.H2 " << data.H2 << std::endl;
std::cout << "data.CO " << data.CO << std::endl;
std::cout << "data.sql_cmd " << sql_cmd << std::endl;
ret = SQLExecDirect(hstmt, (SQLCHAR*)sql_cmd, SQL_NTS);
if (ret != SQL_SUCCESS) {
std::cout << "insert fail" << std::endl;
}
if ((ret == SQL_SUCCESS_WITH_INFO) || (ret == SQL_ERROR)) {
SQLLEN numRecs = 0;
SQLGetDiagField(SQL_HANDLE_STMT, hstmt, 0, SQL_DIAG_NUMBER, &numRecs, 0, 0);
// Get the status records.
i = 1;
while (i <= numRecs && (rc2 = SQLGetDiagRec(SQL_HANDLE_STMT, hstmt, i, SqlState, &NativeError,
Msg, sizeof(Msg), &MsgLen)) != SQL_NO_DATA) {
DisplayError(SqlState, NativeError, (const char*)Msg, MsgLen);
i++;
}
}
//if(data.DeviceID)
return 0;
}
int Connect() {
#if 0
static char init = 0;
if (init) {
printf("database connected\n");
return 0;
}
#endif
ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
ret = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, SQL_IS_INTEGER);
ret = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
ret = SQLConnect(hdbc, (SQLCHAR*)"ysp", SQL_NTS, (SQLCHAR*)"sa", SQL_NTS, (SQLCHAR*)"123456", SQL_NTS);
if (!(ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO)) {
printf("open database fail!\n");
return 1;
}
//init = 1;
printf("connect database\n");
return 0;
}
void free() {
SQLDisconnect(hdbc);
SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
SQLFreeHandle(SQL_HANDLE_ENV, henv);
}
void Insert_data_for_test()
{
#if 1
DataGas_tab_t data;
DATAGAS_INIT_DEFAULT(data);
data.H2 = 99;
Sql_DataGas_insert(data);
#endif
}
int get_MicroWater_from_database(ysp_modbus_regs_t* ysp_data)
{
int res = 0;
if (Connect()) {
free();
return 1;
}
std::cout << "read microwater data " << std::endl;
ret = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
if (!(ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO)) {
std::cout << "SQLAllocHandle fail " << std::endl;
free();
return 1;
}
SQLPrepare(hstmt, (SQLCHAR*)("select * from OCD_DB.dbo.DataH2O where ID = (select MAX(ID) from OCD_DB.dbo.DataH2O )"), SQL_NTS);
ret = SQLExecute(hstmt);
if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) {
if ((ret = SQLFetch(hstmt)) == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) {
//SQLCHAR str9[20], str10[20], str11[20], str12[20], str13[20];
SQLLEN len_str3, len_str6;
SQLCHAR time_str[20];
SQLCHAR PPM[20] = { 0 };
std::cout << "get micro water data" << std::endl;
#if 0
SQLGetData(hstmt, 1, SQL_C_CHAR, str1, 20, &len_str1);//ID
SQLGetData(hstmt, 2, SQL_C_CHAR, str2, 20, &len_str2);
#endif
SQLGetData(hstmt, 3, SQL_C_CHAR, time_str, 20, &len_str3);//datetime
SQLGetData(hstmt, 6, SQL_C_CHAR, PPM, 20, &len_str6);//PPM
printf("datetime:%s\n", time_str);
printf("PPM:%s\n", PPM);
ysp_data->Water= atof((const char*)PPM);
printf("ysp_data->Water:%f\n", ysp_data->Water);
//ysp_data->W_timeH32, ysp_data->W_timeL32 这两个字段没有,后面如果有需要加进去
//GetTimeFromFormat((const char*)time_str, &ysp_data->W_timeH32, &ysp_data->W_timeL32);
}
else {
printf("DataH2O table is empty\n");
}
}
else {
res = 1;//fail
std::cout << "SQLExecute faild" << std::endl;
}
free();
return res;
}
int get_gas_from_database(ysp_modbus_regs_t* ysp_data) {
int res = 0;
if (Connect()) {
free();
return 1;
}
std::cout << "read data " << std::endl;
ret = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
if (!(ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO)) {
std::cout << "SQLAllocHandle fail " << std::endl;
free();
return 1;
}
//Insert_data_for_test();
//SQLPrepare(hstmt, (SQLCHAR*)("select * from OCD_DB.dbo.DataGas"), SQL_NTS);
//SQLPrepare(hstmt, (SQLCHAR*)("select * from OCD_DB.dbo.DataGas where ID = 45 "), SQL_NTS);
SQLPrepare(hstmt, (SQLCHAR*)("select * from OCD_DB.dbo.DataGas where ID = (select MAX(ID) from OCD_DB.dbo.DataGas )"), SQL_NTS);
//SQLPrepare(hstmt, (SQLCHAR*)("SELECT TOP 1 * FROM OCD_DB.dbo.DataGas ORDER BY ID DESC"), SQL_NTS);
//SQLPrepare(hstmt, (SQLCHAR*)("SELECT* FROM(SELECT*, ROW_NUMBER() OVER(ORDER BY ID DESC) AS RowNum FROM OCD_DB.dbo.DataGas) AS SubQuery WHERE RowNum = 2"), SQL_NTS);
ret = SQLExecute(hstmt);
if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) {
if ((ret = SQLFetch(hstmt)) == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) {
//SQLCHAR str9[20], str10[20], str11[20], str12[20], str13[20];
SQLLEN len_str1, len_str2, len_str3, len_str4;
SQLCHAR time_str[20];
SQLCHAR H2[20] = { 0 }, CO[20] = { 0 }, CO2[20] = { 0 }, CH4[20] = { 0 }, C2H2[20] = { 0 }, C2H4[20] = { 0 }, C2H6[20] = { 0 }, Total[20] = { 0 };
std::cout << "get data" << std::endl;
#if 0
SQLGetData(hstmt, 1, SQL_C_CHAR, str1, 20, &len_str1);
SQLGetData(hstmt, 2, SQL_C_CHAR, str2, 20, &len_str2);
#endif
SQLGetData(hstmt, 3, SQL_C_CHAR, time_str, 20, &len_str3);
SQLGetData(hstmt, 4, SQL_C_CHAR, H2, 20, &len_str4);
SQLGetData(hstmt, 5, SQL_C_CHAR, CO, 20, &len_str4);
SQLGetData(hstmt, 6, SQL_C_CHAR, CO2, 20, &len_str4);
SQLGetData(hstmt, 7, SQL_C_CHAR, CH4, 20, &len_str4);
SQLGetData(hstmt, 8, SQL_C_CHAR, C2H2, 20, &len_str4);
SQLGetData(hstmt, 9, SQL_C_CHAR, C2H4, 20, &len_str4);
SQLGetData(hstmt, 10, SQL_C_CHAR, C2H6, 20, &len_str4);
SQLGetData(hstmt, 11, SQL_C_CHAR, Total, 20, &len_str4);
//printf("%s\t%s\t%s\t%s\n", str1, str2, str3, str4);
printf("gas time:%s\n", time_str);
printf("H2:%s\nCO:%s\nCO2:%s\nCH4:%s\n", H2, CO, CO2, CH4);
printf("C2H2:%s\nC2H4:%s\nC2H6:%s\nTotal:%s\n", C2H2, C2H4, C2H6, Total);
ysp_data->H2 = atof((const char*)H2);
printf("----translate\n");
ysp_data->CO = atof((const char*)CO);
ysp_data->CO2 = atof((const char*)CO2);
ysp_data->CH4 = atof((const char*)CH4);
ysp_data->C2H2 = atof((const char*)C2H2);
ysp_data->C2H4 = atof((const char*)C2H4);
ysp_data->C2H6 = atof((const char*)C2H6);
ysp_data->Total = atof((const char*)Total);
#if 0 //测试字节数据
char temp1[4] = { 0 };
memcpy(temp1, &ysp_data->C2H2, 4);
printf("test: %02x%02x% 02x%02x\n", temp1[0], temp1[1], temp1[2], temp1[3]);
#endif
printf("H2:%f\nCO:%f\nCO2:%f\nCH4:%f\n", ysp_data->H2, ysp_data->CO, ysp_data->CO2, ysp_data->CH4);
printf("C2H2:%f\nC2H4:%f\nC2H6:%f\nTotal:%f\n", ysp_data->C2H2, ysp_data->C2H4, ysp_data->C2H6, ysp_data->Total);
GetTimeFromFormat((const char*)time_str, &ysp_data->time_H32, &ysp_data->time_L32);
}
else {
printf("DataGas table is empty\n");
}
}
else {
res = 1;//fail
std::cout << "SQLExecute faild" << std::endl;
}
free();
return res;
}
int fill_ysp_data(ysp_modbus_regs_t * ysp_data)
{
int ret = 0;
WaitForSingleObject(hMutex_database, INFINITE);
#if 0 //用于测试
int i = 0;
uint16_t* ptr;
ptr = (uint16_t*)ysp_data;
for (i = 0; i < sizeof(ysp_modbus_regs_t) / 2; i++) {
ptr[i] = 0x0000 + i;
}
ysp_data->H2 =67.87;
ysp_data->CO = 102;
ysp_data->CO2 =65;
ysp_data->CH4 = 67.87;
ysp_data->C2H2 = 67.87;
ysp_data->C2H4 = 67.87;
ysp_data->C2H6 = 67.87;
ysp_data->Total = 67.87;
ysp_data->Water = 67.87;
ysp_data->time_H32 = 0x11223344;
ysp_data->time_L32 = 0x55667788;
#else
printf("read database\n");
ret = get_gas_from_database(ysp_data);//从数据库读取气体数据
Sleep(50);
ret = get_MicroWater_from_database(ysp_data);//从数据库读取微水数据
//ysp_data->H2 = 67.87;//for test
#endif
ReleaseMutex(hMutex_database);
return ret;
}
void xMutex_init()
{
hMutex_database = CreateMutex(NULL, FALSE, NULL);
}

@ -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,606 @@
#include "windows.h"
#include "gas_monitor.h"
#include "SerialPort.h"
#include "database/database.h"
#include <iomanip>
#include "ysp_modbus_slave.h"
#include "common.h"
#include "tchar.h"
/*
gas_monitor.c :
usbCH340microMCU
usb
*/
using namespace std;
#if 1 //发布时用这组
#define MIN_SLEEP(min) min*60*1000
#define PERIOD_MONITOR(min) MIN_SLEEP(min) //监测周期
#define DEFAULT_PERIOD 40
#else //这一组用于开发测试
#define MIN_SLEEP(min) min*1*1000
#define PERIOD_MONITOR(min) MIN_SLEEP(min) //监测周期
#define DEFAULT_PERIOD 5 //默认监测周期秒
#endif
#define OVER_THRESHOLD 1 //超过阈值
#define NOT_OVER_THRESHOLD 0
#define DATA_HAS_CHANGE 1//数据发生变化
#define NORMAL_CLOSE 1 //继电器常闭
#define NORMAL_OPEN 0 //继电器常开
#define BUADRATE_DEFAULT 9600
static unsigned int monitor_period = DEFAULT_PERIOD; //监测周期 mintue
static unsigned int buadrate = BUADRATE_DEFAULT;
float h2_threshold = H2_THRESHOLD; // 各个气体的默认告警阈值
float co_threshold = CO_THRESHOLD;
float co2_threshold = CO2_THRESHOLD;
float ch4_threshold = CH4_THRESHOLD;
float c2h2_threshold = C2H2_THRESHOLD;
float c2h4_threshold = C2H4_THRESHOLD;
float c2h6_threshold = C2H6_THRESHOLD;
static char H2_alert_flag = 0;
static char CO_alert_flag = 0;
static char CO2_alert_flag = 0;
#if 0 //这段代码暂时留着备份 可能后面确实用不到了再删
//H2
cout << "\n H2 h2_threshold = " << h2_threshold << endl;
cout << " ysp_data.H2 curr value = " << ysp_data.H2 << endl;
if (ExceedThreshold(ysp_data.H2, h2_threshold)) {
//超过阈值uart输出控制信号
cout << "H2 over" << endl;
if (last_signal_H2 != OVER_THRESHOLD) {
cout << "H2 relay normal close" << endl;
last_signal_H2 = OVER_THRESHOLD;
uart_signal_arr[H2][0] = DATA_HAS_CHANGE;//第一个byte表示那个数据需要输出信号
uart_signal_arr[H2][1] = NORMAL_CLOSE;//第二个byte表示该数据输出什么信号
}
}
else {
cout << "H2 not over" << endl;
//在范围内uart输出控制信号
if (last_signal_H2 != NOT_OVER_THRESHOLD) {
cout << "H2 relay normal open" << endl;
last_signal_H2 = NOT_OVER_THRESHOLD;
uart_signal_arr[H2][0] = DATA_HAS_CHANGE;//第一个byte表示那个数据需要输出信号
uart_signal_arr[H2][1] = NORMAL_OPEN;//第二个byte表示该数据输出什么信号
}
}
#endif
enum data_code_e {
H2 = 0,
CO = 1,
CO2 = 2,
CH4 = 3,
C2H2 = 4,
C2H4 = 5,
C2H6 = 6,
Total = 7,
MAX_ALERT //最大告警数
};
/*
uart_signal_arr[MAX_ALERT][2];
:
:
*/
unsigned char uart_signal_arr[MAX_ALERT][2];
//常闭
//顺序和data_code_e一定要一致
static unsigned char uart_close_cmd_arr[MAX_ALERT][3] = {
{0x00,0xF1,0xFF},
{0x00,0xF2,0xFF},
{0x00,0xF3,0xFF},
{0x00,0xF4,0xFF},
{0x00,0xF5,0xFF},
{0x00,0xF6,0xFF},
{0x00,0xF7,0xFF},
{0x00,0xF8,0xFF},
};
//常开
//顺序和data_code_e一定要一致
static unsigned char uart_open_cmd_arr[MAX_ALERT][3] = {
{0x00,0x01,0xFF},
{0x00,0x02,0xFF},
{0x00,0x03,0xFF},
{0x00,0x04,0xFF},
{0x00,0x05,0xFF},
{0x00,0x06,0xFF},
{0x00,0x07,0xFF},
{0x00,0x08,0xFF},
};
const double eps = 1e-6;
#define Equ(a,b) ((abs((a) - (b))) < (eps))
#define More(a,b) (((a) - (b)) > (eps))
#define Less(a,b) (((a) - (b)) < -(eps))
#define MoreEqu(a,b) (((a) - (b)) > (-eps))
#define LessEqu(a,b) (((a) - (b)) < (eps))
//浮点数比较,超过阈值则返回1
int ExceedThreshold(float data, float threshold)
{
if (MoreEqu(data, threshold)) {
return 1;
}
return 0;
}
//监测周期
void xSet_monitor_period(const char* mon_period)
{
if (mon_period) {
monitor_period = atoi(mon_period);
printf("monitor_period=%s\n", mon_period);
printf("monitor_period=%d\n", monitor_period);
}
else {
monitor_period = DEFAULT_PERIOD;
printf("GAS_MONITOR_PERIOD is null,use default\n");
}
}
void xSet_uart_buadrate(const char* uart_buadrate)
{
if (uart_buadrate) {
buadrate = atoi(uart_buadrate);
printf("uart_buadrate=%s\n", uart_buadrate);
printf("buadrate=%d\n", buadrate);
}
else {
buadrate = BUADRATE_DEFAULT;
printf("UART_BUADRATE is null,use default\n");
}
}
//气体告警阈值
void xSet_H2_threshold(const char* H2_threshold)
{
if (H2_threshold) {
h2_threshold = atof(H2_threshold);
printf("H2_threshold=%s\n", H2_threshold);
printf("h2_threshold=%f\n", h2_threshold);
}
else {
h2_threshold = H2_THRESHOLD;
printf("H2_THRESHOLD is null,use default\n");
}
}
void xSet_CO_threshold(const char* CO_threshold)
{
if (CO_threshold) {
co_threshold = atof(CO_threshold);
printf("CO_threshold=%s\n", CO_threshold);
printf("co_threshold=%f\n", co_threshold);
}
else {
co_threshold = CO_THRESHOLD;
printf("CO_THRESHOLD is null,use default\n");
}
}
void xSet_CO2_threshold(const char* CO2_threshold)
{
if (CO2_threshold) {
co2_threshold = atof(CO2_threshold);
printf("H2_threshold=%s\n", CO2_threshold);
printf("co2_threshold=%f\n", co2_threshold);
}
else {
co2_threshold = CO2_THRESHOLD;
printf("CO2_THRESHOLD is null,use default\n");
}
}
void xSet_CH4_period(const char* CH4_threshold)
{
if (CH4_threshold) {
ch4_threshold = atof(CH4_threshold);
printf("CH4_threshold=%s\n", CH4_threshold);
printf("ch4_threshold=%f\n", ch4_threshold);
}
else {
ch4_threshold = CH4_THRESHOLD;
printf("CH4_THRESHOLD is null,use default\n");
}
}
void xSet_C2H2_threshold(const char* C2H2_threshold)
{
if (C2H2_threshold) {
c2h2_threshold = atof(C2H2_threshold);
printf("C2H2_threshold=%s\n", C2H2_threshold);
printf("c2h2_threshold=%f\n", c2h2_threshold);
}
else {
c2h2_threshold = C2H2_THRESHOLD;
printf("C2H2_THRESHOLD is null,use default\n");
}
}
void xSet_C2H4_period(const char* C2H4_threshold)
{
if (C2H4_threshold) {
c2h4_threshold = atof(C2H4_threshold);
printf("C2H4_threshold=%s\n", C2H4_threshold);
printf("c2h4_threshold=%f\n", c2h4_threshold);
}
else {
c2h4_threshold = C2H4_THRESHOLD;
printf("C2H4_THRESHOLD is null,use default\n");
}
}
void xSet_C2H6_threshold(const char* C2H6_threshold)
{
if (C2H6_threshold) {
c2h6_threshold = atof(C2H6_threshold);
printf("C2H6_threshold=%s\n", C2H6_threshold);
printf("c2h6_threshold=%f\n", c2h6_threshold);
}
else {
c2h6_threshold = C2H6_THRESHOLD;
printf("C2H6_THRESHOLD is null,use default\n");
}
}
void xSet_H2_alert(const char* H2_alert)
{
if (H2_alert) {
H2_alert_flag = atoi(H2_alert);
printf("H2_alert=%s\n", H2_alert);
printf("H2_alert_flag=%d\n", H2_alert_flag);
}
else {
H2_alert_flag = 0;//不告警
printf("H2_alert is null,use default\n");
}
}
void xSet_CO_alert(const char* CO_alert)
{
if (CO_alert) {
CO_alert_flag = atoi(CO_alert);
printf("CO_alert=%s\n", CO_alert);
printf("CO_alert_flag=%d\n", CO_alert_flag);
}
else {
CO_alert_flag = 0;//不告警
printf("CO_alert is null,use default\n");
}
}
void xSet_CO2_alert(const char* CO2_alert)
{
if (CO2_alert) {
CO2_alert_flag = atoi(CO2_alert);
printf("CO2_alert=%s\n", CO2_alert);
printf("CO2_alert_flag=%d\n", CO2_alert_flag);
}
else {
CO2_alert_flag = 0;//不告警
printf("CO2_alert is null,use default\n");
}
}
//软件启动时继电器的默认状态 常开
void xAll_relay_normal_open()
{
Sleep(1000 * 20);
unsigned char rcv_buf[1024] = { 0 };
INT res = false;
SerialPort usb_serial0;
char com_name[50] = { 0 };
res = xEnum_relay_comName(com_name);//获取继电器对应的串口名称
if (res != true) {
printf("xAll_relay_normal_open, not find CH340set relay default state fail\n");
return;
}
else {
printf("find CH340 com: %s\n", com_name);
}
res = usb_serial0.open(com_name, buadrate, 0, 8, 1, NoFlowControl, 1);
if (res != true) {
cout << "open usb uart failed,continue" << endl;//考虑加错误处理
return;
}
for (int i = 0; i < MAX_ALERT; i++) {
memset(rcv_buf, 0, sizeof(rcv_buf));
cout << "i ==" << i << endl;
xUnsigned_char_hex_out("uart send", uart_open_cmd_arr[i], 0, 2);
res = usb_serial0.send(uart_open_cmd_arr[i], 3);
cout << "send byte = " << res << endl;
//接受单片机回复
cout << "try recv..." << endl;
res = usb_serial0.receive(rcv_buf, 5);
cout << "recv byte = " << res << endl;
if (0 != res) {
cout << "recv msg from uart " << com_name << endl;
xUnsigned_char_hex_out("uart recv", rcv_buf, 0, 5);
if (0xEF == (unsigned char)rcv_buf[3]) {//如果成功,返回命令 + EF EF是第四个字节
cout << "uart send succ " << com_name << endl;
}
}
else {
cout << "not recv uart msg" << endl;
}
Sleep(10);//每个信号数据包加点间隔
}
cout << "close uart" << endl;
usb_serial0.close();
}
void alert_data_compare(ysp_modbus_regs_t ysp_data)
{
memset(uart_signal_arr, 0, sizeof(uart_signal_arr));
cout << "\n======================" << endl;
if (H2_alert_flag) {
//H2
cout << "\n H2 h2_threshold = " << h2_threshold << endl;
cout << " ysp_data.H2 curr value = " << ysp_data.H2 << endl;
if (ExceedThreshold(ysp_data.H2, h2_threshold)) {
//超过阈值uart输出控制信号
cout << "H2 over" << endl;
cout << "H2 relay normal close" << endl;
uart_signal_arr[H2][0] = DATA_HAS_CHANGE;//第一个byte表示那个数据需要输出信号
uart_signal_arr[H2][1] = NORMAL_CLOSE;//第二个byte表示该数据输出什么信号
}
else {
cout << "H2 not over" << endl;
cout << "H2 relay normal open" << endl;
uart_signal_arr[H2][0] = DATA_HAS_CHANGE;//第一个byte表示那个数据需要输出信号
uart_signal_arr[H2][1] = NORMAL_OPEN;//第二个byte表示该数据输出什么信号
}
}
if (CO_alert_flag) {
//CO
cout << "\n CO co_threshold = " << co_threshold << endl;
cout << " ysp_data.CO curr value = " << ysp_data.CO << endl;
if (ExceedThreshold(ysp_data.CO, co_threshold)) {
//超过阈值uart输出控制信号
cout << "CO over" << endl;
cout << "CO relay normal close" << endl;
uart_signal_arr[CO][0] = DATA_HAS_CHANGE;//第一个byte表示那个数据需要输出信号
uart_signal_arr[CO][1] = NORMAL_CLOSE;
}
else {
//在范围内uart输出控制信号
cout << "CO ont over" << endl;
cout << "CO relay normal open" << endl;
uart_signal_arr[CO][0] = DATA_HAS_CHANGE;//第一个byte表示那个数据需要输出信号
uart_signal_arr[CO][1] = NORMAL_OPEN;//第二个byte表示该数据输出什么信号
}
}
if (CO2_alert_flag) {
//CO2
cout << "\n CO2 co2_threshold = " << co2_threshold << endl;
cout << " ysp_data.CO2 curr value = " << ysp_data.CO2 << endl;
if (ExceedThreshold(ysp_data.CO2, co2_threshold)) {
//超过阈值uart输出控制信号
cout << "CO2 over" << endl;
cout << "CO2 relay normal close" << endl;
uart_signal_arr[CO2][0] = DATA_HAS_CHANGE;//第一个byte表示那个数据需要输出信号
uart_signal_arr[CO2][1] = NORMAL_CLOSE;//常闭
}
else {
cout << "CO2 not over" << endl;
cout << "CO2 relay normal open" << endl;
uart_signal_arr[CO2][0] = DATA_HAS_CHANGE;//第一个byte表示那个数据需要输出信号
uart_signal_arr[CO2][1] = NORMAL_OPEN;//常开
}
}
cout << "\n======================" << endl;
}
DWORD WINAPI Gas_threadProc(LPVOID lp) {
char* com = (char*)lp;
unsigned char rcv_buf[1024] = {0};
INT res = false;
SerialPort usb_serial0;
char uart_cmd[1024] = {0};
static char last_signal_CO2 = NOT_OVER_THRESHOLD;//默认是0,表示低于阈值
static char last_signal_H2 = NOT_OVER_THRESHOLD;//默认是0
static char last_signal_CO = NOT_OVER_THRESHOLD;//默认是0
ULONGLONG startTick = 0;
char com_name[50] = { 0 };
//继电器默认状态为
xAll_relay_normal_open();
ysp_modbus_regs_t ysp_data = {0};
//周期从数据库读气体数据,并判断和阈值比较如果超过,则控制继电器输出开关量
while (1) {
startTick = GetTickCount64();
cout << "loop monitor period " << (monitor_period) << " mintues" << endl;
Sleep(PERIOD_MONITOR(monitor_period));//周期大小分钟
_tprintf(_T("sleep : %I64u ms\n"), GetTickCount64() - startTick);
xGet_config_from_configFile();//just for test
cout << "grap gas data from database" << endl << endl;
memset(uart_signal_arr, 0, sizeof(uart_signal_arr));
if (fill_ysp_data(&ysp_data)) {
continue;
}
alert_data_compare(ysp_data);
#if 0
cout << "\n======================" << endl;
//H2
cout << "\n H2 h2_threshold = " << h2_threshold << endl;
cout << " ysp_data.H2 curr value = " << ysp_data.H2 << endl;
if (ExceedThreshold(ysp_data.H2, h2_threshold)) {
//超过阈值uart输出控制信号
cout << "H2 over" << endl;
cout << "H2 relay normal close" << endl;
uart_signal_arr[H2][0] = DATA_HAS_CHANGE;//第一个byte表示那个数据需要输出信号
uart_signal_arr[H2][1] = NORMAL_CLOSE;//第二个byte表示该数据输出什么信号
}
else {
cout << "H2 not over" << endl;
cout << "H2 relay normal open" << endl;
uart_signal_arr[H2][0] = DATA_HAS_CHANGE;//第一个byte表示那个数据需要输出信号
uart_signal_arr[H2][1] = NORMAL_OPEN;//第二个byte表示该数据输出什么信号
}
//CO
cout << "\n CO co_threshold = " << co_threshold << endl;
cout << " ysp_data.CO curr value = " << ysp_data.CO << endl;
if (ExceedThreshold(ysp_data.CO, co_threshold)) {
//超过阈值uart输出控制信号
cout << "CO over" << endl;
cout << "CO relay normal close" << endl;
uart_signal_arr[CO][0] = DATA_HAS_CHANGE;//第一个byte表示那个数据需要输出信号
uart_signal_arr[CO][1] = NORMAL_CLOSE;
}
else {
//在范围内uart输出控制信号
cout << "CO ont over" << endl;
cout << "CO relay normal open" << endl;
uart_signal_arr[CO][0] = DATA_HAS_CHANGE;//第一个byte表示那个数据需要输出信号
uart_signal_arr[CO][1] = NORMAL_OPEN;//第二个byte表示该数据输出什么信号
}
//CO2
cout << "\n CO2 co2_threshold = " << co2_threshold << endl;
cout << " ysp_data.CO2 curr value = " << ysp_data.CO2 << endl;
if (ExceedThreshold(ysp_data.CO2, co2_threshold)) {
//超过阈值uart输出控制信号
cout << "CO2 over" << endl;
cout << "CO2 relay normal close" << endl;
uart_signal_arr[CO2][0] = DATA_HAS_CHANGE;//第一个byte表示那个数据需要输出信号
uart_signal_arr[CO2][1] = NORMAL_CLOSE;//常闭
}
else {
cout << "CO2 not over" << endl;
cout << "CO2 relay normal open" << endl;
uart_signal_arr[CO2][0] = DATA_HAS_CHANGE;//第一个byte表示那个数据需要输出信号
uart_signal_arr[CO2][1] = NORMAL_OPEN;//常开
}
cout <<"\n======================" << endl;
#endif
#if 0
int signal_sum = 0;
for (int i = 0; i < MAX_ALERT; i++) {
signal_sum += uart_signal_arr[i][0];
}
if (0 == signal_sum) {
cout << "no change,continue" << endl;
continue;//说明数据范围没有变化
}
cout << "some gas over" << endl;
//有数据范围发生变化打开uart
cout << "com number " << com << endl;
#else
cout << "try synchronize all signal to relay"<< endl;
#endif
#if 1
memset(com_name, 0, sizeof(com_name));
res = xEnum_relay_comName(com_name);//获取继电器对应的串口名称
if (res != true) {
printf("not find CH340 synchronize signal fail,continue monitor\n");
continue;
}
else {
printf("find CH340 com: %s\n", com_name);
}
#endif
res = usb_serial0.open(com_name, buadrate, 0, 8, 1, NoFlowControl, 1);
if (res != true) {
cout << "open usb uart failed,continue" << endl;//考虑加错误处理
continue;
}
for (int i = 0; i < MAX_ALERT; i++) {
for (int j = 0; j < 2; j++) {
memset(rcv_buf, 0, sizeof(rcv_buf));
if (DATA_HAS_CHANGE == uart_signal_arr[i][0]) {
cout << "i ==" << i << endl;
if (NORMAL_CLOSE == uart_signal_arr[i][1]) {
xUnsigned_char_hex_out("uart send",uart_close_cmd_arr[i], 0, 2);
//发送指令
res = usb_serial0.send(uart_close_cmd_arr[i], 3);
cout << "send byte = " << res << endl;
}
else if (NORMAL_OPEN == uart_signal_arr[i][1]) {
xUnsigned_char_hex_out("uart send", uart_open_cmd_arr[i],0,2);
res = usb_serial0.send(uart_open_cmd_arr[i], 3);
cout << "send byte = " << res << endl;
}
//接受单片机回复
cout << "try recv..."<< endl;
res = usb_serial0.receive(rcv_buf, 5);
cout << "recv byte = " << res << endl;
if (0 != res) {
cout << "recv msg from uart "<< com_name << endl;
xUnsigned_char_hex_out("uart recv", rcv_buf, 0, 5);
if (0xEF == (unsigned char)rcv_buf[3]) {//如果成功,返回命令 + EF EF是第四个字节
cout << "uart send succ " << com_name << endl;
break;//succ
}
}
else {
cout << "not recv uart msg" << endl;
}
//try again
}
}
Sleep(50);//每个信号数据包加点间隔
}
cout << "close uart" << endl;
usb_serial0.close();
}
#if 0
while (1) {
for (int i = 0; i < 6; i++) {
uart_cmd[0] = 0x00;
uart_cmd[1] = 0xF1 + i;
uart_cmd[2] = 0xff;
usb_serial0.send(uart_cmd, 3);
cout << "sendt i "<< i << endl;
usb_serial0.receive(rcv_buf, 100);
cout << "recv msg from uart 0" << endl;
cout << setbase(16) << rcv_buf[0] << endl;
Sleep(2000);
}
for (int i = 0; i < 6; i++) {
uart_cmd[0] = 0x00;
uart_cmd[1] = 0x01 + i;
uart_cmd[2] = 0xff;
usb_serial0.send(uart_cmd, 3);
usb_serial0.receive(rcv_buf, 100);
cout << "recv msg from uart 0" << endl;
cout << setbase(16) << rcv_buf[0] << endl;
Sleep(2000);
}
}
#endif
return 0;
}
int xGas_alert_task_init(char * uart_port)
{
// 创建线程,返回句柄
int a = 0;
HANDLE h = CreateThread(NULL, 0, Gas_threadProc, uart_port, 0, 0);
printf("start Gas monitor thread\n");
//WaitForSingleObject(h, INFINITE);
//CloseHandle(h);
return 0;
}

@ -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,54 @@

#if defined(_WIN32)
#pragma comment(lib,"Kernel32.lib")
#endif
#include <iostream>
#include <Windows.h>
#include "gas_monitor.h"
#include "ysp_modbus_slave.h"
#include "database/database.h"
#include "common.h"
//同时只有进程运行
BOOL xProcess_unique()
{
HANDLE hMutex = CreateMutex(nullptr, FALSE, "ysp_monitor");
if (GetLastError() == ERROR_ALREADY_EXISTS) // 已经有了一个实例
{
printf("just one process run,exit");
exit(0);
}
// 其他代码
return TRUE;
}
int main(int argc, char** argv)
{
char com_test[20] = { 0 };
xProcess_unique();
std::cout << "RUN YSP MONITOR APP!\n";
#if 1 //com_test 用来开发测试 发布时不起作用
if (argc > 1) {
memcpy(com_test, argv[1], strlen(argv[1]));
std::cout << "uart test:" << argv[1] << std::endl;
}
else {
memcpy(com_test, "COM4", strlen("COM4"));
std::cout << "default uart test:" << com_test << std::endl;
}
#endif
//xGet_config_from_configFile 放在任务之前执行里面有读取延迟执行时间
xGet_config_from_configFile();
xDelay_run_process();
xMutex_init();
xModbus_task_init();//MODBUS thread
xGas_alert_task_init(com_test);//GAS monitor thread
while (1);
}

@ -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.17763.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>v141</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v141</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,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

@ -0,0 +1,281 @@
#include <iostream>
#include "modbus_lib/modbus-tcp.h"
#include "ysp_modbus_slave.h"
#include "database/database.h"
using namespace std;
#define REG_ADDR (header_length+1)
#define REG_NUM (header_length+3)
#define REG_VAL (header_length+3)
#define IP_LOCAL_ADDR "192.168.1.21"
#define TCP_PORT 1502
#define MODBUS_UART "COM3" //爱尔兰项目用不到 uart modbus
#define SERVER_ID 0x00000001 //默认从机地址
#define SLAVE_REG_ADDR 0x008E //从机modbus 地址的寄存器地址
char slave_ip_addr[20] = { 0 }; //modbus slave tcp ip地址
unsigned short slave_tcp_port = TCP_PORT; //modbus slave tcp port端口号
ysp_modbus_regs_t ysp_modbus_data;
slave_add_t salve_addr = {0};
enum {
TCP,
TCP_PI,
RTU
};
int xSlave_info_init()
{
salve_addr.slave_addr = SERVER_ID;
memset(&ysp_modbus_data, 0, sizeof(ysp_modbus_data));
memcpy(slave_ip_addr, IP_LOCAL_ADDR, strlen(IP_LOCAL_ADDR));
return 0;
}
//modbus 从机tcp ipaddr
void xSet_slave_ip_addr(const char* ip_addr)
{
if (ip_addr) {
memcpy(slave_ip_addr, ip_addr, strlen(ip_addr));
printf("slave_ip_addr=%s\n", slave_ip_addr);
}
else {
memcpy(slave_ip_addr, IP_LOCAL_ADDR, strlen(IP_LOCAL_ADDR));
printf("SLAVE_IP_ADDR is null,use default\n");
}
}
//modbus从机tcp port
void xSet_slave_port(const char* tcp_port)
{
if (tcp_port) {
slave_tcp_port = atoi(tcp_port);
printf("tcp_port=%s\n", tcp_port);
printf("slave_tcp_port=%d\n", slave_tcp_port);
}
else {
slave_tcp_port = TCP_PORT;
printf("SLAVE_PORT is null,use default\n");
}
}
DWORD WINAPI xModbus_msg_ThreadProc(LPVOID lp) {
char* com = (char*)lp;
CHAR rcv_buf[1024] = { 0 };
int s = -1;
int i = 0;
int rc = 0;
modbus_t* ctx = NULL;
uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH];//接收缓冲区
int header_length;
uint16_t* ptr,*ptr1;
int use_backend=TCP;
uint16_t reg_addr, reg_val, reg_num;
uint16_t tmp = 0;
modbus_mapping_t* mb_mapping;
/*
master
accept 退accept
*/
if (use_backend == TCP) {
ctx = modbus_new_tcp(slave_ip_addr, slave_tcp_port);
//modbus_set_slave(ctx, SERVER_ID);
}
else if (use_backend == TCP_PI) {
ctx = modbus_new_tcp_pi(slave_ip_addr, "1502");
}
else {
ctx = modbus_new_rtu(MODBUS_UART, 115200, 'N', 8, 1);
modbus_set_slave(ctx, SERVER_ID);
}
//modbus_set_indication_timeout(ctx, 4, 0);
header_length = modbus_get_header_length(ctx);
modbus_set_debug(ctx, TRUE);
printf("modbus set debug true\n");
mb_mapping = modbus_mapping_new(0, 0, sizeof(ysp_modbus_regs_t) / 2 + sizeof(slave_add_t) / 2, 0);
printf("total regs nums = %d\n", sizeof(ysp_modbus_regs_t) / 2 + sizeof(slave_add_t) / 2);
if (mb_mapping == NULL) {
fprintf(stderr, "Failed to allocate the mapping: %s\n", modbus_strerror(errno));
modbus_free(ctx);
return -1;
}
if (use_backend == TCP) {
std::cout << "modbus start listen\n" << std::endl;
s = modbus_tcp_listen(ctx, 1);
if (-1 == s) {
std::cout << "modbus tcp listen fial,check ip or port,exit thread\n" << std::endl;
return -1;
}
}
else if (use_backend == TCP_PI) {
s = modbus_tcp_pi_listen(ctx, 1);
}
else {
#if 0
rc = modbus_connect(ctx);
if (rc == -1) {
fprintf(stderr, "Unable to connect %s\n", modbus_strerror(errno));
modbus_free(ctx);
return -1;
}
#endif
}
for (;;) {
if (use_backend == TCP) {
std::cout << std::endl << "modbus accept wait connect (hold)\n" << std::endl;
modbus_tcp_accept(ctx, &s);
std::cout << "modbus accept new connect\n" << std::endl;
}
else if (use_backend == TCP_PI) {
modbus_tcp_pi_accept(ctx, &s);
}
else {
rc = modbus_connect(ctx);
if (rc == -1) {
fprintf(stderr, "Unable to connect %s\n", modbus_strerror(errno));
modbus_free(ctx);
return -1;
}
}
std::cout << "modbus receive loop\n" << std::endl;
for (;;) {//循环接受消息
do {
rc = modbus_receive(ctx, query);
/* Filtered queries return 0 */
} while (rc == 0);//地址错误会返回0,CRC错误会返回-1和errno=EMBBADCRC,正常会返回接收到的字节数
/* The connection is not closed on errors which require on reply such as
bad CRC in RTU. */
if (rc == -1 && errno != EMBBADCRC) {
/* Quit */
std::cout << "error once connect over 1" << std::endl;
break;
}
if (rc == -1)
{
if (errno == EMBBADCRC)
{
std::cout << "bad crc in query" << std::endl;
continue;
}
else
{
std::cout << "parse modbus frame error: %s\n" << std::endl;
std::cout << "error once connect over 2" << std::endl;
break;
}
}
std::cout << "modbus new msg\n" << std::endl;
switch (query[header_length])
{
case MODBUS_FC_READ_COILS://0x01 Read Coils in mb_mapping->tab_bitsIO输出状态
printf("unsurported function 2\n");
//否定应答,暂不做数据映射
modbus_reply_exception(ctx, query, MODBUS_EXCEPTION_ILLEGAL_FUNCTION);
break;
case MODBUS_FC_READ_DISCRETE_INPUTS://0x02 Read Discrete Inputs in mb_mapping->tab_input_bitsIO输入状态
printf("unsurported function 2\n");
//否定应答,暂不做数据映射
modbus_reply_exception(ctx, query, MODBUS_EXCEPTION_ILLEGAL_FUNCTION);
continue;
break;
case MODBUS_FC_READ_HOLDING_REGISTERS://0x03 Read Holding Registers in mb_mapping->tab_registers:REG输出
//读寄存器
std::cout << "modbus function 0x03\n" << std::endl;
if (MODBUS_GET_INT16_FROM_INT8(query, REG_ADDR) < (sizeof(ysp_modbus_regs_t) / 2 + sizeof(slave_add_t) / 2))
{
printf("reg vaild\n");
//读取实时数据
if ((MODBUS_GET_INT16_FROM_INT8(query, REG_ADDR) + MODBUS_GET_INT16_FROM_INT8(query, REG_NUM)) > (sizeof(ysp_modbus_regs_t) / 2)+ (sizeof(slave_add_t) / 2))
{
printf("reply to this special register address by an exception\n");
//否定应答
modbus_reply_exception(ctx, query, MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS);
continue;
}
printf("update data\n");
//气体数据
if (fill_ysp_data(&ysp_modbus_data)) {
printf("get data from database fail, erase regs\n");
//memset(&ysp_modbus_data, 0, sizeof(&ysp_modbus_data)); 不擦除,回复上次结果
}
printf("time h32:%d\n", ysp_modbus_data.time_H32);
printf("time l32:%d\n", ysp_modbus_data.time_L32);
ptr = (uint16_t*)&ysp_modbus_data;
for (i = 0; i < (sizeof(ysp_modbus_regs_t) / 2); i++)
{
mb_mapping->tab_registers[i] = ptr[i];
}
ptr1 = (uint16_t*)&salve_addr;
mb_mapping->tab_registers[i] = ptr1[1];//i越小越先传输
mb_mapping->tab_registers[i+1] = ptr1[0];//ptr1[0] 里存的是低地址数据
//mb_mapping->tab_registers[i] =0x3344;
//mb_mapping->tab_registers[i+1] = 0x5566;
}
break;
case MODBUS_FC_READ_INPUT_REGISTERS://0x04 Read Input Registers in mb_mapping->tab_input_registers:REG输入
//可用于读取测量值等不可上位机修改的量,暂不做数据映射
break;
case MODBUS_FC_WRITE_SINGLE_COIL://0x05 Write Single Coil:slave_addr+cmd+reg_addr+[FF 00 or 00 00]+crc corresponding to mb_mapping->tab_bits
//写单个继电器
break;
case MODBUS_FC_WRITE_SINGLE_REGISTER://0x06 Write Single Register:slave_addr+cmd+reg_addr+reg_val+crc corresponding to mb_mapping->tab_registers
//写单个寄存器,暂不做数据映射
printf("function code 0x06\n");
reg_addr = MODBUS_GET_INT16_FROM_INT8(query, REG_ADDR);
reg_val = MODBUS_GET_INT16_FROM_INT8(query, REG_VAL);
printf( "reg_addr=%d reg_val=0x%04x\n", reg_addr, reg_val);
if (reg_addr == SLAVE_REG_ADDR)
{
printf("modify slave address succ\n");
salve_addr.slave_addr = reg_val;
}
break;
case MODBUS_FC_WRITE_MULTIPLE_COILS://0x0F Write Multiple Coils:slave_addr+cmd+reg_addr+coil_num+bit_arr+crc corresponding to mb_mapping->tab_bits
//写多个继电器,暂不做数据映射
break;
case MODBUS_FC_WRITE_MULTIPLE_REGISTERS://0x10 Write Multiple Registers:slave_addr+cmd+reg_addr+reg_num+reg_val_arr+crc corresponding to mb_mapping->tab_registers
//写多个寄存器
break;
default://
break;
}
//正常应答
rc = modbus_reply(ctx, query, rc, mb_mapping);
if (rc == -1) {
printf("reply modbus frame error:%s\n", modbus_strerror(errno));
break;
}
}
}
return 0;
}
int xModbus_task_init()
{
// 创建线程,返回句柄
int a = 0;
xSlave_info_init();
Sleep(10);
HANDLE h = CreateThread(NULL, 0, xModbus_msg_ThreadProc, NULL, 0, 0);
printf("start Modbus thread\n");
//WaitForSingleObject(h, INFINITE);
//CloseHandle(h);
return 0;
}

@ -0,0 +1,62 @@
#pragma once
#ifndef _YSP_MODBUS_SLAVE_H
#define _YSP_MODBUS_SLAVE_H
//一个寄存器占两个字节,这样映射的话就是一个数据占两个寄存器
typedef struct ysp_modbus_regsiter {
float H2;
float CO;
float CO2;
float CH4;
float C2H2;
float C2H4;
float C2H6;
float Total; //总烃
float Water; //微水
int time_H32; //1970以来的秒 共8byte 低32位 先传输
int time_L32; //高32位
int pad_reg[60];//用于扩展
}ysp_modbus_regs_t;
/*
0000H_0001H: H2 float
0002H_0003H: COfloat
0004H_0005H: CO2float
0006H_0007H: CH4float
0008H_0009H: C2H2float
000AH_000BH: C2H4float
000CH_000DH: C2H6float
000EH_000FH: Totalfloat
00010H_0011H: Waterfloat //到这里有9*2=18个寄存器 (0到17寄存器)
00012H_0013H: 4(18-19)
00014H_0015H4//时间占8个字节(20-21寄存器)
00020H 0049H:
*/
typedef struct slave_address {
int slave_addr;
}slave_add_t;
//modbus 从机tcp ipaddr
void xSet_slave_ip_addr(const char* ip_addr);
//modbus从机tcp port
void xSet_slave_port(const char* tcp_port);
/*
008EH 008FH slave address
*/
/*
+ // 寄存器地址连续
*/
int xModbus_task_init();
#endif
Loading…
Cancel
Save