PR-765 Start the modbus master using the same service based approach
This commit is contained in:
parent
5c7ead0873
commit
5fe3358462
|
@ -2,6 +2,7 @@
|
|||
guidelines = 80, 120
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
|
||||
[*.sh]
|
||||
end_of_line = lf
|
||||
|
|
|
@ -227,7 +227,7 @@ if(OPLC_MAIN_PROGRAM)
|
|||
message("Compile main program enabled")
|
||||
|
||||
set(OPLC_USER_DIR ${PROJECT_SOURCE_DIR}/etc/src)
|
||||
file(GLOB oplc_SRC runtime/core/*.cpp runtime/core/dnp3s/*.cpp runtime/core/modbusslave/*.cpp runtime/core/service/*.cpp runtime/vendor/inih-r46/*.c)
|
||||
file(GLOB oplc_SRC runtime/core/*.cpp runtime/core/dnp3s/*.cpp runtime/core/modbusslave/*.cpp runtime/core/modbusmaster/*.cpp runtime/core/service/*.cpp runtime/vendor/inih-r46/*.c)
|
||||
|
||||
include_directories(${OPLC_USER_DIR})
|
||||
include_directories(runtime/core)
|
||||
|
|
|
@ -69,3 +69,12 @@ _Devices can be passed to the `docker` daemon using the `-v` flag (e.g. `-v /dev
|
|||
```bash
|
||||
docker run -it --rm --privileged -p 8080:8080 openplc:v3
|
||||
```
|
||||
|
||||
## Running Standalone
|
||||
|
||||
The normal approach for running OpenPLC is though the web interface. However,
|
||||
it is possible to run OpenPLC without the web interface using configuration
|
||||
information supplied in a configuration file.
|
||||
|
||||
See the file `config.ini.example` in this repository for information about
|
||||
how to run standalone.
|
||||
|
|
|
@ -0,0 +1,180 @@
|
|||
; This configuration file enables configuring OpenPLC capabilities
|
||||
; when the OpenPLC runtime starts. These capabilities can be configured
|
||||
; without using the web front end and enable running the OpenPLC runtime
|
||||
; standalone.
|
||||
;
|
||||
; This file must be located in the "etc" folder and must be named
|
||||
; "config.ini".
|
||||
|
||||
; ---------------------------------------------------------
|
||||
; ---------------------------------------------------------
|
||||
[logging]
|
||||
|
||||
; Level defines the minimum level for a message to appear
|
||||
; as a log message. Messages are a lower level are not
|
||||
; output.
|
||||
;
|
||||
; Level may be one of the following values, ordered from
|
||||
; lowest to highest:
|
||||
; trace, debug, info, warn, error
|
||||
level = info
|
||||
|
||||
; ---------------------------------------------------------
|
||||
; ---------------------------------------------------------
|
||||
[interactive]
|
||||
; The interactive server is what connects the web front end to the runtime
|
||||
; You need this enabled if you want to be able to enable/disable services
|
||||
; at runtime, view logs, etc.
|
||||
;
|
||||
; If you set this value to false and use the OpenPLC web interface to
|
||||
; start the runtime, the web interface will not be able to communicate
|
||||
; with the runtime.
|
||||
enabled = true
|
||||
|
||||
; ---------------------------------------------------------
|
||||
; ---------------------------------------------------------
|
||||
[modbusslave]
|
||||
; Modbus slave enables reading and writing located variables through the
|
||||
; modbus interface. This starts a Modbus server (also know as a slave)
|
||||
; running in the OpenPLC runtime.
|
||||
enabled = false
|
||||
|
||||
; TCP Settings
|
||||
; ------------
|
||||
port = 502
|
||||
address = 127.0.0.1
|
||||
|
||||
; How we bind located variables to modbus registers.
|
||||
; This may be one of:
|
||||
; sized - indicates to use the size of a located variable to determine
|
||||
; the register type
|
||||
binding = sized
|
||||
|
||||
; ---------------------------------------------------------
|
||||
; ---------------------------------------------------------
|
||||
[modbusmaster]
|
||||
; Modbus master enables reading and writing located variables through the
|
||||
; modbus interface. This starts capabilties to poll one or more Modbus
|
||||
; servers and exchange data with the located variables.
|
||||
|
||||
enabled = true
|
||||
|
||||
; We support multiple modbus masters. Each master should specify
|
||||
; a complete set of configuration information within this section.
|
||||
; Different masters are identified by a postfix which includes the
|
||||
; index of the master. Indices start at 0 and go up from there.
|
||||
|
||||
name.0 = 1
|
||||
protocol.0 = tcp
|
||||
slave_id.0 = 1
|
||||
ip_address.0 = 127.0.0.1
|
||||
ip_port.0 = 1000
|
||||
; rtu_baud_rate.0 =
|
||||
; rtu_parity.0
|
||||
; rtu_data_bit.0
|
||||
; rtu_stop_bit.0
|
||||
discrete_inputs_start.0 = 0
|
||||
discrete_inputs_size.0 = 1
|
||||
;coils_start.0
|
||||
;coils_size.0
|
||||
;input_registers_start.0
|
||||
;input_registers_size.0
|
||||
;holding_registers_read_start.0
|
||||
;holding_registers_read_size.0
|
||||
;holding_registers_start.0
|
||||
;holding_registers_size.0
|
||||
|
||||
; ---------------------------------------------------------
|
||||
; ---------------------------------------------------------
|
||||
[pstorage]
|
||||
; The pstorage service reads and writes persistent storage. It enables
|
||||
; the runtime to restore important variables to the previous value
|
||||
; if the runtime is restarated.
|
||||
enabled = false
|
||||
|
||||
; How long should we wait between write cycle. The persistent storage
|
||||
; checks at this rate for changes and only persists to disk if a
|
||||
; value has changed within the poll period
|
||||
poll_interval = 10
|
||||
|
||||
; ---------------------------------------------------------
|
||||
; ---------------------------------------------------------
|
||||
[dnp3s]
|
||||
; The dnp3s enables a DNP3 outstation.
|
||||
enabled = false
|
||||
|
||||
; Location Bindings
|
||||
; -----------------
|
||||
|
||||
; Bind OpenPLC bit-sized output 0.0 to DNP3 binary input at index 0
|
||||
; Note that the second hierarchical index must be 0
|
||||
bind_location = name:%QX0.0,group:1,index:0,
|
||||
|
||||
; Bind OpenPLC word-sized output 2 to DNP3 analog input at index 1
|
||||
; bind_location = name:%QW2,group:30,index:1,
|
||||
|
||||
; Bind OpenPLC word-sized output 2 to DNP3 analog output status at index 1
|
||||
; bind_location = name:%QW2,group:40,index:1,
|
||||
|
||||
; Bind OpenPLC long word-sized output 2 to DNP3 analog input at index 10
|
||||
; bind_location = name:%QL2,group:30,index:10,
|
||||
|
||||
; Bind OpenPLC bit-sized input 0.0 to DNP3 analog input at index 10
|
||||
bind_location = name:%IX0.0,group:12,index:0,
|
||||
bind_location = name:%IX1.0,group:12,index:1,
|
||||
|
||||
; Bind OpenPLC word-sized input 2 to DNP3 analog command at index 0
|
||||
; bind_location = name:%IW2,group:41,index:0,
|
||||
|
||||
; TCP Settings
|
||||
; ------------
|
||||
address = 127.0.0.1
|
||||
port = 20000
|
||||
|
||||
; Link Settings
|
||||
; -------------
|
||||
|
||||
; The remote and local address
|
||||
local_address = 10
|
||||
remote_address = 1
|
||||
|
||||
; Keep alive timeout. A value in seconds, or the keyword MAX
|
||||
; keep_alive_timeout = MAX
|
||||
|
||||
|
||||
; Parameters
|
||||
; ----------
|
||||
|
||||
; Enable unsolicited reporting if master allows it
|
||||
enable_unsolicited = true
|
||||
|
||||
; How long (seconds) the outstation will allow a operate
|
||||
; to follow a select
|
||||
; select_timeout = 10
|
||||
|
||||
; max control commands for a single APDU
|
||||
; max_controls_per_request = 16
|
||||
|
||||
; maximum fragment size the outstation will recieve
|
||||
; default is the max value
|
||||
; max_rx_frag_size = 2048
|
||||
|
||||
; maximum fragment size the outstation will send if
|
||||
; it needs to fragment. Default is the max falue
|
||||
; max_tx_frag_size = 2048
|
||||
|
||||
; size of the event buffer
|
||||
; event_buffer_size = 10
|
||||
|
||||
; Timeout for solicited confirms (milliseconds)
|
||||
; sol_confirm_timeout = 5000
|
||||
|
||||
; Timeout for unsolicited confirms (milliseconds)
|
||||
; unsol_conrfirm_timeout = 5000
|
||||
|
||||
; Timeout for unsolicited retries (milliseconds)
|
||||
; unsol_retry_timeout = 5000
|
||||
|
||||
; The rate as which data is exchanged between DNP3 and the runtime
|
||||
; (milliseconds)
|
||||
; poll_interval = 250
|
|
@ -26,7 +26,7 @@ endif()
|
|||
include_directories(lib)
|
||||
|
||||
# The primary source is everything in this directory
|
||||
file(GLOB oplc_SRC *.cpp dnp3s/*.cpp service/*.cpp modbusslave/*.cpp)
|
||||
file(GLOB oplc_SRC *.cpp dnp3s/*.cpp service/*.cpp modbusslave/*.cpp modbusmaster/*.cpp)
|
||||
|
||||
message("In runtime")
|
||||
message(${oplc_SRC})
|
||||
|
|
|
@ -130,7 +130,6 @@ void bootstrap()
|
|||
// a standard part the platform where we have implemented capabilities
|
||||
// for specific hardware targes.
|
||||
initializeHardware();
|
||||
initializeMB();
|
||||
// User provided logic that runs on initialization.
|
||||
initCustomLayer();
|
||||
updateBuffersIn();
|
||||
|
@ -143,7 +142,7 @@ void bootstrap()
|
|||
// SERVICE INITIALIZATION
|
||||
//======================================================
|
||||
|
||||
// Initializes any services that is known and wants to participate
|
||||
// Initializes any services that are known and want to participate
|
||||
// in bootstrapping.
|
||||
services_init();
|
||||
|
||||
|
|
|
@ -51,6 +51,38 @@ inline bool ini_matches(const char* section_expected,
|
|||
&& strcmp(value_expected, value) == 0;
|
||||
}
|
||||
|
||||
/// Compare a INI declaration name that has a postfix with an index,
|
||||
/// for example "example.1". This returns 0 if the provided name matches
|
||||
/// the expected value and there is a postfix. Returns the postfix
|
||||
/// number to the caller.
|
||||
///
|
||||
/// The intention of this function is to create a standard mechanism to
|
||||
/// declare configuration items that are indexed. The index is normally
|
||||
/// used to lookup in an array for the slot where to populate the value.
|
||||
///
|
||||
/// @param name The name to test. This is the value read from the INI file.
|
||||
/// @param expected The name to test against, without the separating period or
|
||||
/// a marker for the index.
|
||||
/// @param index If the return value of this function is 0, then this will
|
||||
/// contain the index that was read as the postfix.
|
||||
/// @return 0 if there is a match, otherwise non-zero.
|
||||
inline int cmpnameid(const char* name, const char* expected,
|
||||
std::uint8_t* index) {
|
||||
size_t expected_len = strlen(expected);
|
||||
int ret = strncmp(name, expected, expected_len);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t name_len = strlen(name);
|
||||
if (name_len > expected_len + 1 && name[expected_len] == '.') {
|
||||
*index = atoi(name + (expected_len + 1));
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// Implementation for fgets based on istream.
|
||||
/// @param str pointer to an array of chars where the string read is copied.
|
||||
/// @param num Maximum number of characters to be copied into str.
|
||||
|
|
|
@ -140,8 +140,6 @@ int processEnipMessage(unsigned char *buffer, int buffer_size, void* user_data);
|
|||
uint16_t processPCCCMessage(unsigned char *buffer, int buffer_size);
|
||||
|
||||
// modbus_master.cpp
|
||||
void initializeMB();
|
||||
void *querySlaveDevices(void *arg);
|
||||
void updateBuffersIn_MB();
|
||||
void updateBuffersOut_MB();
|
||||
|
||||
|
|
|
@ -1,647 +0,0 @@
|
|||
// Copyright 2015 Thiago Alves
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http ://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissionsand
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
// This file is responsible for parse and discovery of slave devices by parsing
|
||||
// the mbconfig.cfg file. This code also updates OpenPLC internal buffers with
|
||||
// the data queried from the slave devices.
|
||||
// Thiago Alves, Jul 2018
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
#include <modbus.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include "ladder.h"
|
||||
|
||||
/** \addtogroup openplc_runtime
|
||||
* @{
|
||||
*/
|
||||
|
||||
#define MB_TCP 1
|
||||
#define MB_RTU 2
|
||||
#define MAX_MB_IO 400
|
||||
|
||||
using namespace std;
|
||||
|
||||
uint8_t bool_input_buf[MAX_MB_IO];
|
||||
uint8_t bool_output_buf[MAX_MB_IO];
|
||||
uint16_t int_input_buf[MAX_MB_IO];
|
||||
uint16_t int_output_buf[MAX_MB_IO];
|
||||
|
||||
pthread_mutex_t ioLock;
|
||||
|
||||
struct MB_address
|
||||
{
|
||||
uint16_t start_address;
|
||||
uint16_t num_regs;
|
||||
};
|
||||
|
||||
struct MB_device
|
||||
{
|
||||
modbus_t *mb_ctx;
|
||||
char dev_name[100];
|
||||
uint8_t protocol;
|
||||
char dev_address[100];
|
||||
uint16_t ip_port;
|
||||
int rtu_baud;
|
||||
char rtu_parity;
|
||||
int rtu_data_bit;
|
||||
int rtu_stop_bit;
|
||||
uint8_t dev_id;
|
||||
bool isConnected;
|
||||
|
||||
struct MB_address discrete_inputs;
|
||||
struct MB_address coils;
|
||||
struct MB_address input_registers;
|
||||
struct MB_address holding_read_registers;
|
||||
struct MB_address holding_registers;
|
||||
};
|
||||
|
||||
struct MB_device *mb_devices;
|
||||
uint8_t num_devices;
|
||||
uint16_t polling_period = 100;
|
||||
uint16_t timeout = 1000;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// \brief Finds the data between the separators on the line provided
|
||||
/// \param *line
|
||||
/// \param *buf
|
||||
/// \param separator1
|
||||
/// \param separator2
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
void getData(char *line, char *buf, char separator1, char separator2)
|
||||
{
|
||||
int i=0, j=0;
|
||||
buf[j] = '\0';
|
||||
|
||||
while (line[i] != separator1 && line[i] != '\0')
|
||||
{
|
||||
i++;
|
||||
}
|
||||
i++;
|
||||
|
||||
while (line[i] != separator2 && line[i] != '\0')
|
||||
{
|
||||
buf[j] = line[i];
|
||||
i++;
|
||||
j++;
|
||||
buf[j] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// \brief Get the number of the Modbus device
|
||||
/// \param *line
|
||||
/// \return int with no
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
int getDeviceNumber(char *line)
|
||||
{
|
||||
char temp[5];
|
||||
int i = 0, j = 6;
|
||||
|
||||
while (line[j] != '.')
|
||||
{
|
||||
temp[i] = line[j];
|
||||
i++;
|
||||
j++;
|
||||
temp[i] = '\0';
|
||||
}
|
||||
|
||||
return(atoi(temp));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// \brief get the type of function or parameter for the Modbus device
|
||||
/// \param *line
|
||||
/// \param *parameter
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
void getFunction(char *line, char *parameter)
|
||||
{
|
||||
int i = 0, j = 0;
|
||||
|
||||
while (line[j] != '.')
|
||||
{
|
||||
j++;
|
||||
}
|
||||
j++;
|
||||
|
||||
while (line[j] != ' ' && line[j] != '=' && line[j] != '(')
|
||||
{
|
||||
parameter[i] = line[j];
|
||||
i++;
|
||||
j++;
|
||||
parameter[i] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// \brief Parses the mbconfig.cfg file
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
void parseConfig()
|
||||
{
|
||||
string line;
|
||||
char line_str[1024];
|
||||
ifstream cfgfile("mbconfig.cfg");
|
||||
|
||||
if (cfgfile.is_open())
|
||||
{
|
||||
while (getline(cfgfile, line))
|
||||
{
|
||||
strncpy(line_str, line.c_str(), 1024);
|
||||
if (line_str[0] != '#' && strlen(line_str) > 1)
|
||||
{
|
||||
if (!strncmp(line_str, "Num_Devices", 11))
|
||||
{
|
||||
char temp_buffer[5];
|
||||
getData(line_str, temp_buffer, '"', '"');
|
||||
num_devices = atoi(temp_buffer);
|
||||
mb_devices = (struct MB_device *)malloc(num_devices*sizeof(struct MB_device));
|
||||
}
|
||||
else if (!strncmp(line_str, "Polling_Period", 14))
|
||||
{
|
||||
char temp_buffer[10];
|
||||
getData(line_str, temp_buffer, '"', '"');
|
||||
polling_period = atoi(temp_buffer);
|
||||
}
|
||||
else if (!strncmp(line_str, "Timeout", 7))
|
||||
{
|
||||
char temp_buffer[10];
|
||||
getData(line_str, temp_buffer, '"', '"');
|
||||
timeout = atoi(temp_buffer);
|
||||
}
|
||||
|
||||
else if (!strncmp(line_str, "device", 6))
|
||||
{
|
||||
int deviceNumber = getDeviceNumber(line_str);
|
||||
char functionType[100];
|
||||
getFunction(line_str, functionType);
|
||||
|
||||
if (!strncmp(functionType, "name", 4))
|
||||
{
|
||||
getData(line_str, mb_devices[deviceNumber].dev_name, '"', '"');
|
||||
}
|
||||
else if (!strncmp(functionType, "protocol", 8))
|
||||
{
|
||||
char temp_buffer[5];
|
||||
getData(line_str, temp_buffer, '"', '"');
|
||||
|
||||
if (!strncmp(temp_buffer, "TCP", 3))
|
||||
mb_devices[deviceNumber].protocol = MB_TCP;
|
||||
else if (!strncmp(temp_buffer, "RTU", 3))
|
||||
mb_devices[deviceNumber].protocol = MB_RTU;
|
||||
}
|
||||
else if (!strncmp(functionType, "slave_id", 8))
|
||||
{
|
||||
char temp_buffer[5];
|
||||
getData(line_str, temp_buffer, '"', '"');
|
||||
mb_devices[deviceNumber].dev_id = atoi(temp_buffer);
|
||||
}
|
||||
else if (!strncmp(functionType, "address", 7))
|
||||
{
|
||||
getData(line_str, mb_devices[deviceNumber].dev_address, '"', '"');
|
||||
}
|
||||
else if (!strncmp(functionType, "IP_Port", 7))
|
||||
{
|
||||
char temp_buffer[6];
|
||||
getData(line_str, temp_buffer, '"', '"');
|
||||
mb_devices[deviceNumber].ip_port = atoi(temp_buffer);
|
||||
}
|
||||
else if (!strncmp(functionType, "RTU_Baud_Rate", 13))
|
||||
{
|
||||
char temp_buffer[10];
|
||||
getData(line_str, temp_buffer, '"', '"');
|
||||
mb_devices[deviceNumber].rtu_baud = atoi(temp_buffer);
|
||||
}
|
||||
else if (!strncmp(functionType, "RTU_Parity", 10))
|
||||
{
|
||||
char temp_buffer[3];
|
||||
getData(line_str, temp_buffer, '"', '"');
|
||||
mb_devices[deviceNumber].rtu_parity = temp_buffer[0];
|
||||
}
|
||||
else if (!strncmp(functionType, "RTU_Data_Bits", 13))
|
||||
{
|
||||
char temp_buffer[6];
|
||||
getData(line_str, temp_buffer, '"', '"');
|
||||
mb_devices[deviceNumber].rtu_data_bit = atoi(temp_buffer);
|
||||
}
|
||||
else if (!strncmp(functionType, "RTU_Stop_Bits", 13))
|
||||
{
|
||||
char temp_buffer[20];
|
||||
getData(line_str, temp_buffer, '"', '"');
|
||||
mb_devices[deviceNumber].rtu_stop_bit = atoi(temp_buffer);
|
||||
}
|
||||
else if (!strncmp(functionType, "Discrete_Inputs_Start", 21))
|
||||
{
|
||||
char temp_buffer[10];
|
||||
getData(line_str, temp_buffer, '"', '"');
|
||||
mb_devices[deviceNumber].discrete_inputs.start_address = atoi(temp_buffer);
|
||||
}
|
||||
else if (!strncmp(functionType, "Discrete_Inputs_Size", 20))
|
||||
{
|
||||
char temp_buffer[10];
|
||||
getData(line_str, temp_buffer, '"', '"');
|
||||
mb_devices[deviceNumber].discrete_inputs.num_regs = atoi(temp_buffer);
|
||||
}
|
||||
else if (!strncmp(functionType, "Coils_Start", 11))
|
||||
{
|
||||
char temp_buffer[10];
|
||||
getData(line_str, temp_buffer, '"', '"');
|
||||
mb_devices[deviceNumber].coils.start_address = atoi(temp_buffer);
|
||||
}
|
||||
else if (!strncmp(functionType, "Coils_Size", 10))
|
||||
{
|
||||
char temp_buffer[10];
|
||||
getData(line_str, temp_buffer, '"', '"');
|
||||
mb_devices[deviceNumber].coils.num_regs = atoi(temp_buffer);
|
||||
}
|
||||
else if (!strncmp(functionType, "Input_Registers_Start", 21))
|
||||
{
|
||||
char temp_buffer[10];
|
||||
getData(line_str, temp_buffer, '"', '"');
|
||||
mb_devices[deviceNumber].input_registers.start_address = atoi(temp_buffer);
|
||||
}
|
||||
else if (!strncmp(functionType, "Input_Registers_Size", 20))
|
||||
{
|
||||
char temp_buffer[10];
|
||||
getData(line_str, temp_buffer, '"', '"');
|
||||
mb_devices[deviceNumber].input_registers.num_regs = atoi(temp_buffer);
|
||||
}
|
||||
else if (!strncmp(functionType, "Holding_Registers_Read_Start", 28))
|
||||
{
|
||||
char temp_buffer[10];
|
||||
getData(line_str, temp_buffer, '"', '"');
|
||||
mb_devices[deviceNumber].holding_read_registers.start_address = atoi(temp_buffer);
|
||||
}
|
||||
else if (!strncmp(functionType, "Holding_Registers_Read_Size", 27))
|
||||
{
|
||||
char temp_buffer[10];
|
||||
getData(line_str, temp_buffer, '"', '"');
|
||||
mb_devices[deviceNumber].holding_read_registers.num_regs = atoi(temp_buffer);
|
||||
}
|
||||
else if (!strncmp(functionType, "Holding_Registers_Start", 23))
|
||||
{
|
||||
char temp_buffer[10];
|
||||
getData(line_str, temp_buffer, '"', '"');
|
||||
mb_devices[deviceNumber].holding_registers.start_address = atoi(temp_buffer);
|
||||
}
|
||||
else if (!strncmp(functionType, "Holding_Registers_Size", 22))
|
||||
{
|
||||
char temp_buffer[10];
|
||||
getData(line_str, temp_buffer, '"', '"');
|
||||
mb_devices[deviceNumber].holding_registers.num_regs = atoi(temp_buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
spdlog::info("Skipping configuration of Slave Devices (mbconfig.cfg file not found)");
|
||||
}
|
||||
|
||||
//Parser Debug
|
||||
///*
|
||||
for (int i = 0; i < num_devices; i++)
|
||||
{
|
||||
printf("Device %d\n", i);
|
||||
printf("Name: %s\n", mb_devices[i].dev_name);
|
||||
printf("Protocol: %d\n", mb_devices[i].protocol);
|
||||
printf("Address: %s\n", mb_devices[i].dev_address);
|
||||
printf("IP Port: %d\n", mb_devices[i].ip_port);
|
||||
printf("Baud rate: %d\n", mb_devices[i].rtu_baud);
|
||||
printf("Parity: %c\n", mb_devices[i].rtu_parity);
|
||||
printf("Data Bits: %d\n", mb_devices[i].rtu_data_bit);
|
||||
printf("Stop Bits: %d\n", mb_devices[i].rtu_stop_bit);
|
||||
printf("DI Start: %d\n", mb_devices[i].discrete_inputs.start_address);
|
||||
printf("DI Size: %d\n", mb_devices[i].discrete_inputs.num_regs);
|
||||
printf("Coils Start: %d\n", mb_devices[i].coils.start_address);
|
||||
printf("Coils Size: %d\n", mb_devices[i].coils.num_regs);
|
||||
printf("IR Start: %d\n", mb_devices[i].input_registers.start_address);
|
||||
printf("IR Size: %d\n", mb_devices[i].input_registers.num_regs);
|
||||
printf("HR Start: %d\n", mb_devices[i].holding_registers.start_address);
|
||||
printf("HR Size: %d\n", mb_devices[i].holding_registers.num_regs);
|
||||
printf("\n\n");
|
||||
}
|
||||
//*/
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// \brief Thread to poll each slave device
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
void *querySlaveDevices(void *arg)
|
||||
{
|
||||
while (run_openplc)
|
||||
{
|
||||
uint16_t bool_input_index = 0;
|
||||
uint16_t bool_output_index = 0;
|
||||
uint16_t int_input_index = 0;
|
||||
uint16_t int_output_index = 0;
|
||||
|
||||
for (int i = 0; i < num_devices; i++)
|
||||
{
|
||||
//Verify if device is connected
|
||||
if (!mb_devices[i].isConnected)
|
||||
{
|
||||
spdlog::info("Device {} is disconnected. Attempting to reconnect...", mb_devices[i].dev_name);
|
||||
if (modbus_connect(mb_devices[i].mb_ctx) == -1)
|
||||
{
|
||||
spdlog::error("Connection failed on MB device {}: {}", mb_devices[i].dev_name, modbus_strerror(errno));
|
||||
|
||||
if (special_functions[2] != NULL) *special_functions[2]++;
|
||||
|
||||
// Because this device is not connected, we skip those input registers
|
||||
bool_input_index += (mb_devices[i].discrete_inputs.num_regs);
|
||||
int_input_index += (mb_devices[i].input_registers.num_regs);
|
||||
int_input_index += (mb_devices[i].holding_read_registers.num_regs);
|
||||
bool_output_index += (mb_devices[i].coils.num_regs);
|
||||
int_output_index += (mb_devices[i].holding_registers.num_regs);
|
||||
}
|
||||
else
|
||||
{
|
||||
spdlog::info("Connected to MB device {}", mb_devices[i].dev_name);
|
||||
mb_devices[i].isConnected = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (mb_devices[i].isConnected)
|
||||
{
|
||||
struct timespec ts;
|
||||
ts.tv_sec = 0;
|
||||
ts.tv_nsec = (1000*1000*1000*28)/mb_devices[i].rtu_baud;
|
||||
|
||||
//Read discrete inputs
|
||||
if (mb_devices[i].discrete_inputs.num_regs != 0)
|
||||
{
|
||||
uint8_t *tempBuff;
|
||||
tempBuff = (uint8_t *)malloc(mb_devices[i].discrete_inputs.num_regs);
|
||||
nanosleep(&ts, NULL);
|
||||
int return_val = modbus_read_input_bits(mb_devices[i].mb_ctx, mb_devices[i].discrete_inputs.start_address,
|
||||
mb_devices[i].discrete_inputs.num_regs, tempBuff);
|
||||
if (return_val == -1)
|
||||
{
|
||||
if (mb_devices[i].protocol != MB_RTU)
|
||||
{
|
||||
modbus_close(mb_devices[i].mb_ctx);
|
||||
mb_devices[i].isConnected = false;
|
||||
}
|
||||
|
||||
spdlog::info("Modbus Read Discrete Input Registers failed on MB device {}: {}", mb_devices[i].dev_name, modbus_strerror(errno));
|
||||
bool_input_index += (mb_devices[i].discrete_inputs.num_regs);
|
||||
if (special_functions[2] != NULL) *special_functions[2]++;
|
||||
}
|
||||
else
|
||||
{
|
||||
pthread_mutex_lock(&ioLock);
|
||||
for (int j = 0; j < return_val; j++)
|
||||
{
|
||||
bool_input_buf[bool_input_index] = tempBuff[j];
|
||||
bool_input_index++;
|
||||
}
|
||||
pthread_mutex_unlock(&ioLock);
|
||||
}
|
||||
|
||||
free(tempBuff);
|
||||
}
|
||||
|
||||
//Write coils
|
||||
if (mb_devices[i].coils.num_regs != 0)
|
||||
{
|
||||
uint8_t *tempBuff;
|
||||
tempBuff = (uint8_t *)malloc(mb_devices[i].coils.num_regs);
|
||||
|
||||
pthread_mutex_lock(&ioLock);
|
||||
for (int j = 0; j < mb_devices[i].coils.num_regs; j++)
|
||||
{
|
||||
tempBuff[j] = bool_output_buf[bool_output_index];
|
||||
bool_output_index++;
|
||||
}
|
||||
pthread_mutex_unlock(&ioLock);
|
||||
|
||||
nanosleep(&ts, NULL);
|
||||
int return_val = modbus_write_bits(mb_devices[i].mb_ctx, mb_devices[i].coils.start_address, mb_devices[i].coils.num_regs, tempBuff);
|
||||
if (return_val == -1)
|
||||
{
|
||||
if (mb_devices[i].protocol != MB_RTU)
|
||||
{
|
||||
modbus_close(mb_devices[i].mb_ctx);
|
||||
mb_devices[i].isConnected = false;
|
||||
}
|
||||
|
||||
spdlog::error("Modbus Write Coils failed on MB device {}: {}", mb_devices[i].dev_name, modbus_strerror(errno));
|
||||
if (special_functions[2] != NULL) *special_functions[2]++;
|
||||
}
|
||||
|
||||
free(tempBuff);
|
||||
}
|
||||
|
||||
//Read input registers
|
||||
if (mb_devices[i].input_registers.num_regs != 0)
|
||||
{
|
||||
uint16_t *tempBuff;
|
||||
tempBuff = (uint16_t *)malloc(2*mb_devices[i].input_registers.num_regs);
|
||||
nanosleep(&ts, NULL);
|
||||
int return_val = modbus_read_input_registers( mb_devices[i].mb_ctx, mb_devices[i].input_registers.start_address,
|
||||
mb_devices[i].input_registers.num_regs, tempBuff);
|
||||
if (return_val == -1)
|
||||
{
|
||||
if (mb_devices[i].protocol != MB_RTU)
|
||||
{
|
||||
modbus_close(mb_devices[i].mb_ctx);
|
||||
mb_devices[i].isConnected = false;
|
||||
}
|
||||
|
||||
spdlog::error("Modbus Read Input Registers failed on MB device {}: {}", mb_devices[i].dev_name, modbus_strerror(errno));
|
||||
int_input_index += (mb_devices[i].input_registers.num_regs);
|
||||
if (special_functions[2] != NULL) *special_functions[2]++;
|
||||
}
|
||||
else
|
||||
{
|
||||
pthread_mutex_lock(&ioLock);
|
||||
for (int j = 0; j < return_val; j++)
|
||||
{
|
||||
int_input_buf[int_input_index] = tempBuff[j];
|
||||
int_input_index++;
|
||||
}
|
||||
pthread_mutex_unlock(&ioLock);
|
||||
}
|
||||
|
||||
free(tempBuff);
|
||||
}
|
||||
|
||||
//Read holding registers
|
||||
if (mb_devices[i].holding_read_registers.num_regs != 0)
|
||||
{
|
||||
uint16_t *tempBuff;
|
||||
tempBuff = (uint16_t *)malloc(2*mb_devices[i].holding_read_registers.num_regs);
|
||||
nanosleep(&ts, NULL);
|
||||
int return_val = modbus_read_registers(mb_devices[i].mb_ctx, mb_devices[i].holding_read_registers.start_address,
|
||||
mb_devices[i].holding_read_registers.num_regs, tempBuff);
|
||||
if (return_val == -1)
|
||||
{
|
||||
if (mb_devices[i].protocol != MB_RTU)
|
||||
{
|
||||
modbus_close(mb_devices[i].mb_ctx);
|
||||
mb_devices[i].isConnected = false;
|
||||
}
|
||||
spdlog::error("Modbus Read Holding Registers failed on MB device {}: {}", mb_devices[i].dev_name, modbus_strerror(errno));
|
||||
int_input_index += (mb_devices[i].holding_read_registers.num_regs);
|
||||
if (special_functions[2] != NULL) *special_functions[2]++;
|
||||
}
|
||||
else
|
||||
{
|
||||
pthread_mutex_lock(&ioLock);
|
||||
for (int j = 0; j < return_val; j++)
|
||||
{
|
||||
int_input_buf[int_input_index] = tempBuff[j];
|
||||
int_input_index++;
|
||||
}
|
||||
pthread_mutex_unlock(&ioLock);
|
||||
}
|
||||
|
||||
free(tempBuff);
|
||||
}
|
||||
|
||||
//Write holding registers
|
||||
if (mb_devices[i].holding_registers.num_regs != 0)
|
||||
{
|
||||
uint16_t *tempBuff;
|
||||
tempBuff = (uint16_t *)malloc(2*mb_devices[i].holding_registers.num_regs);
|
||||
|
||||
pthread_mutex_lock(&ioLock);
|
||||
for (int j = 0; j < mb_devices[i].holding_registers.num_regs; j++)
|
||||
{
|
||||
tempBuff[j] = int_output_buf[int_output_index];
|
||||
int_output_index++;
|
||||
}
|
||||
pthread_mutex_unlock(&ioLock);
|
||||
|
||||
nanosleep(&ts, NULL);
|
||||
int return_val = modbus_write_registers(mb_devices[i].mb_ctx, mb_devices[i].holding_registers.start_address,
|
||||
mb_devices[i].holding_registers.num_regs, tempBuff);
|
||||
if (return_val == -1)
|
||||
{
|
||||
if (mb_devices[i].protocol != MB_RTU)
|
||||
{
|
||||
modbus_close(mb_devices[i].mb_ctx);
|
||||
mb_devices[i].isConnected = false;
|
||||
}
|
||||
|
||||
spdlog::error("Modbus Write Holding Registers failed on MB device {}: {}", mb_devices[i].dev_name, modbus_strerror(errno));
|
||||
if (special_functions[2] != NULL) *special_functions[2]++;
|
||||
}
|
||||
|
||||
free(tempBuff);
|
||||
}
|
||||
}
|
||||
}
|
||||
this_thread::sleep_for(chrono::milliseconds(polling_period));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// \brief This function is called by the main OpenPLC routine when it is
|
||||
/// initializing. Modbus master initialization procedures are here.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
void initializeMB()
|
||||
{
|
||||
parseConfig();
|
||||
|
||||
for (int i = 0; i < num_devices; i++)
|
||||
{
|
||||
if (mb_devices[i].protocol == MB_TCP)
|
||||
{
|
||||
mb_devices[i].mb_ctx = modbus_new_tcp(mb_devices[i].dev_address, mb_devices[i].ip_port);
|
||||
}
|
||||
else if (mb_devices[i].protocol == MB_RTU)
|
||||
{
|
||||
mb_devices[i].mb_ctx = modbus_new_rtu( mb_devices[i].dev_address, mb_devices[i].rtu_baud,
|
||||
mb_devices[i].rtu_parity, mb_devices[i].rtu_data_bit,
|
||||
mb_devices[i].rtu_stop_bit);
|
||||
}
|
||||
|
||||
//slave id
|
||||
modbus_set_slave(mb_devices[i].mb_ctx, mb_devices[i].dev_id);
|
||||
|
||||
//timeout
|
||||
uint32_t to_sec = timeout / 1000;
|
||||
uint32_t to_usec = (timeout % 1000) * 1000;
|
||||
modbus_set_response_timeout(mb_devices[i].mb_ctx, to_sec, to_usec);
|
||||
}
|
||||
|
||||
//Initialize comm error counter
|
||||
if (special_functions[2] != NULL) *special_functions[2] = 0;
|
||||
|
||||
if (num_devices > 0)
|
||||
{
|
||||
pthread_t thread;
|
||||
int ret = pthread_create(&thread, NULL, querySlaveDevices, NULL);
|
||||
if (ret==0)
|
||||
{
|
||||
pthread_detach(thread);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// \brief This function is called by the OpenPLC in a loop. Here the internal
|
||||
/// buffers must be updated to reflect the actual Input state.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
void updateBuffersIn_MB()
|
||||
{
|
||||
pthread_mutex_lock(&ioLock);
|
||||
|
||||
for (int i = 0; i < MAX_MB_IO; i++)
|
||||
{
|
||||
if (bool_input[100+(i/8)][i%8] != NULL) *bool_input[100+(i/8)][i%8] = bool_input_buf[i];
|
||||
if (int_input[100+i] != NULL) *int_input[100+i] = int_input_buf[i];
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&ioLock);
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// \brief This function is called by the OpenPLC in a loop. Here the internal buffers
|
||||
/// must be updated to reflect the actual Output state.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
void updateBuffersOut_MB()
|
||||
{
|
||||
pthread_mutex_lock(&ioLock);
|
||||
|
||||
for (int i = 0; i < MAX_MB_IO; i++)
|
||||
{
|
||||
if (bool_output[100+(i/8)][i%8] != NULL) bool_output_buf[i] = *bool_output[100+(i/8)][i%8];
|
||||
if (int_output[100+i] != NULL) int_output_buf[i] = *int_output[100+i];
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&ioLock);
|
||||
}
|
||||
|
||||
/** @}*/
|
|
@ -0,0 +1,489 @@
|
|||
// Copyright 2015 Thiago Alves
|
||||
// Copyright 2019 Smarter Grid Solutions
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http ://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissionsand
|
||||
// limitations under the License.
|
||||
|
||||
#include <ini.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <modbus.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ladder.h"
|
||||
#include "master.h"
|
||||
#include "../glue.h"
|
||||
#include "../ini_util.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
/** \addtogroup openplc_runtime
|
||||
* @{
|
||||
*/
|
||||
|
||||
#define MAX_MB_IO 400
|
||||
|
||||
uint8_t bool_input_buf[MAX_MB_IO];
|
||||
uint8_t bool_output_buf[MAX_MB_IO];
|
||||
uint16_t int_input_buf[MAX_MB_IO];
|
||||
uint16_t int_output_buf[MAX_MB_IO];
|
||||
|
||||
pthread_mutex_t ioLock;
|
||||
|
||||
/// \brief This function is called by the OpenPLC in a loop. Here the internal
|
||||
/// buffers must be updated to reflect the actual Input state.
|
||||
void updateBuffersIn_MB()
|
||||
{
|
||||
pthread_mutex_lock(&ioLock);
|
||||
|
||||
for (int i = 0; i < MAX_MB_IO; i++)
|
||||
{
|
||||
if (bool_input[100+(i/8)][i%8] != NULL) *bool_input[100+(i/8)][i%8] = bool_input_buf[i];
|
||||
if (int_input[100+i] != NULL) *int_input[100+i] = int_input_buf[i];
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&ioLock);
|
||||
}
|
||||
|
||||
|
||||
/// \brief This function is called by the OpenPLC in a loop. Here the internal buffers
|
||||
/// must be updated to reflect the actual Output state.
|
||||
void updateBuffersOut_MB()
|
||||
{
|
||||
pthread_mutex_lock(&ioLock);
|
||||
|
||||
for (int i = 0; i < MAX_MB_IO; i++)
|
||||
{
|
||||
if (bool_output[100+(i/8)][i%8] != NULL) bool_output_buf[i] = *bool_output[100+(i/8)][i%8];
|
||||
if (int_output[100+i] != NULL) int_output_buf[i] = *int_output[100+i];
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&ioLock);
|
||||
}
|
||||
|
||||
enum MasterProtocol {
|
||||
ProtocolInvalid,
|
||||
ProtocolTcp,
|
||||
ProtocolRtu,
|
||||
};
|
||||
|
||||
const uint8_t MASTER_ITEM_SIZE(100);
|
||||
|
||||
struct ModbusAddress
|
||||
{
|
||||
uint16_t start_address;
|
||||
uint16_t num_regs;
|
||||
};
|
||||
|
||||
/// Defines the configuration information for a particular modbus
|
||||
/// master.
|
||||
struct Master {
|
||||
/// A name, mostly for the purpose of logging.
|
||||
char name[MASTER_ITEM_SIZE];
|
||||
/// Which protocol do we use for communication.
|
||||
MasterProtocol protocol;
|
||||
/// The ID of the slave.
|
||||
uint8_t slave_id;
|
||||
/// The IP address (if using TCP).
|
||||
char ip_address[MASTER_ITEM_SIZE];
|
||||
/// The port (if using TCP).
|
||||
uint16_t ip_port;
|
||||
uint16_t rtu_baud_rate;
|
||||
uint8_t rtu_parity;
|
||||
uint16_t rtu_data_bit;
|
||||
uint16_t rtu_stop_bit;
|
||||
modbus_t* mb_ctx;
|
||||
uint16_t timeout;
|
||||
bool is_connected;
|
||||
|
||||
struct ModbusAddress discrete_inputs;
|
||||
struct ModbusAddress coils;
|
||||
struct ModbusAddress input_registers;
|
||||
struct ModbusAddress holding_read_registers;
|
||||
struct ModbusAddress holding_registers;
|
||||
|
||||
Master() :
|
||||
name{'\0'},
|
||||
protocol(ProtocolInvalid),
|
||||
ip_address{'\0'},
|
||||
mb_ctx(nullptr),
|
||||
is_connected(false)
|
||||
{}
|
||||
|
||||
void create() {
|
||||
if (protocol == ProtocolTcp) {
|
||||
mb_ctx = modbus_new_tcp(ip_address, ip_port);
|
||||
} else if (protocol == ProtocolRtu) {
|
||||
mb_ctx = modbus_new_rtu(ip_address, rtu_baud_rate, rtu_parity, rtu_data_bit, rtu_stop_bit);
|
||||
}
|
||||
|
||||
modbus_set_slave(mb_ctx, slave_id);
|
||||
|
||||
uint32_t to_sec = timeout / 1000;
|
||||
uint32_t to_usec = (timeout % 1000) * 1000;
|
||||
modbus_set_response_timeout(mb_ctx, to_sec, to_usec);
|
||||
}
|
||||
};
|
||||
|
||||
struct ModbusMasterConfig {
|
||||
|
||||
chrono::milliseconds polling_period;
|
||||
vector<Master>* masters;
|
||||
|
||||
Master* config_item(uint8_t index) {
|
||||
size_t required_size = max(masters->size(), static_cast<size_t>(index + 1));
|
||||
if (masters->size() < required_size) {
|
||||
masters->resize(index + 1);
|
||||
}
|
||||
return &masters->at(index);
|
||||
}
|
||||
};
|
||||
|
||||
int modbus_master_cfg_handler(void* user_data, const char* section,
|
||||
const char* name, const char* value) {
|
||||
if (strcmp("modbusmaster", section) != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto config = reinterpret_cast<ModbusMasterConfig*>(user_data);
|
||||
|
||||
uint8_t index;
|
||||
if (oplc::cmpnameid(name, "name", &index) == 0) {
|
||||
strncpy(config->config_item(index)->name, value, MASTER_ITEM_SIZE);
|
||||
config->config_item(index)->name[MASTER_ITEM_SIZE - 1] = '\0';
|
||||
} else if (oplc::cmpnameid(name, "protocol", &index) == 0) {
|
||||
if (strcmp(value, "tcp") == 0) {
|
||||
config->config_item(index)->protocol = ProtocolTcp;
|
||||
} else if (strcmp(value, "rtu") == 0) {
|
||||
config->config_item(index)->protocol = ProtocolRtu;
|
||||
}
|
||||
} else if (oplc::cmpnameid(name, "slave_id", &index) == 0) {
|
||||
config->config_item(index)->slave_id = atoi(value);
|
||||
} else if (oplc::cmpnameid(name, "ip_address", &index) == 0) {
|
||||
strncpy(config->config_item(index)->ip_address, value, MASTER_ITEM_SIZE);
|
||||
config->config_item(index)->ip_address[MASTER_ITEM_SIZE - 1] = '\0';
|
||||
} else if (oplc::cmpnameid(name, "ip_port", &index) == 0) {
|
||||
config->config_item(index)->ip_port = atoi(value);
|
||||
} else if (oplc::cmpnameid(name, "rtu_baud_rate", &index) == 0) {
|
||||
config->config_item(index)->rtu_baud_rate = atoi(value);
|
||||
} else if (oplc::cmpnameid(name, "rtu_parity", &index) == 0) {
|
||||
config->config_item(index)->rtu_parity = atoi(value);
|
||||
} else if (oplc::cmpnameid(name, "rtu_data_bit", &index) == 0) {
|
||||
config->config_item(index)->rtu_data_bit = atoi(value);
|
||||
} else if (oplc::cmpnameid(name, "rtu_stop_bit", &index) == 0) {
|
||||
config->config_item(index)->rtu_stop_bit = atoi(value);
|
||||
} else if (oplc::cmpnameid(name, "discrete_inputs_start", &index) == 0) {
|
||||
config->config_item(index)->discrete_inputs.start_address = atoi(value);
|
||||
} else if (oplc::cmpnameid(name, "discrete_inputs_size", &index) == 0) {
|
||||
config->config_item(index)->discrete_inputs.num_regs = atoi(value);
|
||||
} else if (oplc::cmpnameid(name, "coils_start", &index) == 0) {
|
||||
config->config_item(index)->coils.start_address = atoi(value);
|
||||
} else if (oplc::cmpnameid(name, "coils_size", &index) == 0) {
|
||||
config->config_item(index)->coils.num_regs = atoi(value);
|
||||
} else if (oplc::cmpnameid(name, "input_registers_start", &index) == 0) {
|
||||
config->config_item(index)->input_registers.start_address = atoi(value);
|
||||
} else if (oplc::cmpnameid(name, "input_registers_size", &index) == 0) {
|
||||
config->config_item(index)->input_registers.num_regs = atoi(value);
|
||||
} else if (oplc::cmpnameid(name, "holding_registers_read_start", &index) == 0) {
|
||||
config->config_item(index)->holding_read_registers.start_address = atoi(value);
|
||||
} else if (oplc::cmpnameid(name, "holding_registers_read_size", &index) == 0) {
|
||||
config->config_item(index)->holding_read_registers.num_regs = atoi(value);
|
||||
} else if (oplc::cmpnameid(name, "holding_registers_start", &index) == 0) {
|
||||
config->config_item(index)->holding_registers.start_address = atoi(value);
|
||||
} else if (oplc::cmpnameid(name, "holding_registers_size", &index) == 0) {
|
||||
config->config_item(index)->holding_registers.num_regs = atoi(value);
|
||||
} else if (strcmp(name, "enabled") == 0) {
|
||||
// Nothing to do here - we already know this is enabled
|
||||
} else {
|
||||
spdlog::warn("Unknown configuration item {}", name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct MasterArgs {
|
||||
volatile bool* run;
|
||||
chrono::milliseconds polling_period;
|
||||
vector<Master>* masters;
|
||||
};
|
||||
|
||||
void* modbus_master_poll_slaves(void* args) {
|
||||
auto master_args = reinterpret_cast<MasterArgs*>(args);
|
||||
|
||||
while (*master_args->run) {
|
||||
uint16_t bool_input_index = 0;
|
||||
uint16_t bool_output_index = 0;
|
||||
uint16_t int_input_index = 0;
|
||||
uint16_t int_output_index = 0;
|
||||
|
||||
for (size_t i = 0; i < master_args->masters->size(); i++) {
|
||||
Master& master = master_args->masters->at(i);
|
||||
//Verify if device is connected
|
||||
if (!master.is_connected) {
|
||||
spdlog::info("Device {} is disconnected. Attempting to reconnect...", master.name);
|
||||
if (modbus_connect(master.mb_ctx) == -1)
|
||||
{
|
||||
spdlog::error("Connection failed on MB device {}: {}", master.name, modbus_strerror(errno));
|
||||
|
||||
if (special_functions[2] != NULL) *special_functions[2]++;
|
||||
|
||||
// Because this device is not connected, we skip those input registers
|
||||
bool_input_index += (master.discrete_inputs.num_regs);
|
||||
int_input_index += (master.input_registers.num_regs);
|
||||
int_input_index += (master.holding_read_registers.num_regs);
|
||||
bool_output_index += (master.coils.num_regs);
|
||||
int_output_index += (master.holding_registers.num_regs);
|
||||
}
|
||||
else
|
||||
{
|
||||
spdlog::info("Connected to MB device {}", master.name);
|
||||
master.is_connected = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (master.is_connected)
|
||||
{
|
||||
struct timespec ts;
|
||||
ts.tv_sec = 0;
|
||||
ts.tv_nsec = (1000*1000*1000*28)/master.rtu_baud_rate;
|
||||
|
||||
//Read discrete inputs
|
||||
if (master.discrete_inputs.num_regs != 0)
|
||||
{
|
||||
uint8_t *tempBuff;
|
||||
tempBuff = (uint8_t *)malloc(master.discrete_inputs.num_regs);
|
||||
nanosleep(&ts, NULL);
|
||||
int return_val = modbus_read_input_bits(master.mb_ctx, master.discrete_inputs.start_address,
|
||||
master.discrete_inputs.num_regs, tempBuff);
|
||||
if (return_val == -1)
|
||||
{
|
||||
if (master.protocol == ProtocolTcp)
|
||||
{
|
||||
modbus_close(master.mb_ctx);
|
||||
master.is_connected = false;
|
||||
}
|
||||
|
||||
spdlog::info("Modbus Read Discrete Input Registers failed on MB device {}: {}", master.name, modbus_strerror(errno));
|
||||
bool_input_index += (master.discrete_inputs.num_regs);
|
||||
if (special_functions[2] != NULL) *special_functions[2]++;
|
||||
}
|
||||
else
|
||||
{
|
||||
pthread_mutex_lock(&ioLock);
|
||||
for (int j = 0; j < return_val; j++)
|
||||
{
|
||||
bool_input_buf[bool_input_index] = tempBuff[j];
|
||||
bool_input_index++;
|
||||
}
|
||||
pthread_mutex_unlock(&ioLock);
|
||||
}
|
||||
|
||||
free(tempBuff);
|
||||
}
|
||||
|
||||
//Write coils
|
||||
if (master.coils.num_regs != 0)
|
||||
{
|
||||
uint8_t *tempBuff;
|
||||
tempBuff = (uint8_t *)malloc(master.coils.num_regs);
|
||||
|
||||
pthread_mutex_lock(&ioLock);
|
||||
for (int j = 0; j < master.coils.num_regs; j++)
|
||||
{
|
||||
tempBuff[j] = bool_output_buf[bool_output_index];
|
||||
bool_output_index++;
|
||||
}
|
||||
pthread_mutex_unlock(&ioLock);
|
||||
|
||||
nanosleep(&ts, NULL);
|
||||
int return_val = modbus_write_bits(master.mb_ctx, master.coils.start_address, master.coils.num_regs, tempBuff);
|
||||
if (return_val == -1)
|
||||
{
|
||||
if (master.protocol == ProtocolTcp)
|
||||
{
|
||||
modbus_close(master.mb_ctx);
|
||||
master.is_connected = false;
|
||||
}
|
||||
|
||||
spdlog::error("Modbus Write Coils failed on MB device {}: {}", master.name, modbus_strerror(errno));
|
||||
if (special_functions[2] != NULL) *special_functions[2]++;
|
||||
}
|
||||
|
||||
free(tempBuff);
|
||||
}
|
||||
|
||||
//Read input registers
|
||||
if (master.input_registers.num_regs != 0)
|
||||
{
|
||||
uint16_t *tempBuff;
|
||||
tempBuff = (uint16_t *)malloc(2*master.input_registers.num_regs);
|
||||
nanosleep(&ts, NULL);
|
||||
int return_val = modbus_read_input_registers(master.mb_ctx, master.input_registers.start_address,
|
||||
master.input_registers.num_regs, tempBuff);
|
||||
if (return_val == -1)
|
||||
{
|
||||
if (master.protocol == ProtocolTcp)
|
||||
{
|
||||
modbus_close(master.mb_ctx);
|
||||
master.is_connected = false;
|
||||
}
|
||||
|
||||
spdlog::error("Modbus Read Input Registers failed on MB device {}: {}", master.name, modbus_strerror(errno));
|
||||
int_input_index += (master.input_registers.num_regs);
|
||||
if (special_functions[2] != NULL) *special_functions[2]++;
|
||||
}
|
||||
else
|
||||
{
|
||||
pthread_mutex_lock(&ioLock);
|
||||
for (int j = 0; j < return_val; j++)
|
||||
{
|
||||
int_input_buf[int_input_index] = tempBuff[j];
|
||||
int_input_index++;
|
||||
}
|
||||
pthread_mutex_unlock(&ioLock);
|
||||
}
|
||||
|
||||
free(tempBuff);
|
||||
}
|
||||
|
||||
//Read holding registers
|
||||
if (master.holding_read_registers.num_regs != 0)
|
||||
{
|
||||
uint16_t *tempBuff;
|
||||
tempBuff = (uint16_t *)malloc(2*master.holding_read_registers.num_regs);
|
||||
nanosleep(&ts, NULL);
|
||||
int return_val = modbus_read_registers(master.mb_ctx, master.holding_read_registers.start_address,
|
||||
master.holding_read_registers.num_regs, tempBuff);
|
||||
if (return_val == -1)
|
||||
{
|
||||
if (master.protocol == ProtocolTcp)
|
||||
{
|
||||
modbus_close(master.mb_ctx);
|
||||
master.is_connected = false;
|
||||
}
|
||||
spdlog::error("Modbus Read Holding Registers failed on MB device {}: {}", master.name, modbus_strerror(errno));
|
||||
int_input_index += (master.holding_read_registers.num_regs);
|
||||
if (special_functions[2] != NULL) *special_functions[2]++;
|
||||
}
|
||||
else
|
||||
{
|
||||
pthread_mutex_lock(&ioLock);
|
||||
for (int j = 0; j < return_val; j++)
|
||||
{
|
||||
int_input_buf[int_input_index] = tempBuff[j];
|
||||
int_input_index++;
|
||||
}
|
||||
pthread_mutex_unlock(&ioLock);
|
||||
}
|
||||
|
||||
free(tempBuff);
|
||||
}
|
||||
|
||||
//Write holding registers
|
||||
if (master.holding_registers.num_regs != 0)
|
||||
{
|
||||
uint16_t *tempBuff;
|
||||
tempBuff = (uint16_t *)malloc(2*master.holding_registers.num_regs);
|
||||
|
||||
pthread_mutex_lock(&ioLock);
|
||||
for (int j = 0; j < master.holding_registers.num_regs; j++)
|
||||
{
|
||||
tempBuff[j] = int_output_buf[int_output_index];
|
||||
int_output_index++;
|
||||
}
|
||||
pthread_mutex_unlock(&ioLock);
|
||||
|
||||
nanosleep(&ts, NULL);
|
||||
int return_val = modbus_write_registers(master.mb_ctx, master.holding_registers.start_address,
|
||||
master.holding_registers.num_regs, tempBuff);
|
||||
if (return_val == -1)
|
||||
{
|
||||
if (master.protocol == ProtocolTcp)
|
||||
{
|
||||
modbus_close(master.mb_ctx);
|
||||
master.is_connected = false;
|
||||
}
|
||||
|
||||
spdlog::error("Modbus Write Holding Registers failed on MB device {}: {}", master.name, modbus_strerror(errno));
|
||||
if (special_functions[2] != NULL) *special_functions[2]++;
|
||||
}
|
||||
|
||||
free(tempBuff);
|
||||
}
|
||||
}
|
||||
}
|
||||
this_thread::sleep_for(master_args->polling_period);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void modbus_master_run(oplc::config_stream& cfg_stream,
|
||||
const char* cfg_overrides,
|
||||
const GlueVariablesBinding& bindings,
|
||||
volatile bool& run) {
|
||||
// Read the configuration information for the masters
|
||||
vector<Master> master_defs;
|
||||
ModbusMasterConfig config {
|
||||
.polling_period = chrono::milliseconds(100),
|
||||
.masters = &master_defs,
|
||||
};
|
||||
ini_parse_stream(oplc::istream_fgets, cfg_stream.get(),
|
||||
modbus_master_cfg_handler, &config);
|
||||
cfg_stream.reset(nullptr);
|
||||
|
||||
// Create the context for each master
|
||||
for (size_t index = 0; index < master_defs.size(); ++index) {
|
||||
master_defs[index].create();
|
||||
}
|
||||
|
||||
//Initialize comm error counter
|
||||
if (special_functions[2] != NULL) {
|
||||
*special_functions[2] = 0;
|
||||
}
|
||||
|
||||
// Start a unified polling thread for all masters
|
||||
auto master_args = new MasterArgs {
|
||||
.run = &run,
|
||||
.polling_period = chrono::milliseconds(config.polling_period),
|
||||
.masters = &master_defs };
|
||||
pthread_t thread;
|
||||
int ret = pthread_create(&thread, nullptr, modbus_master_poll_slaves, master_args);
|
||||
if (ret == 0) {
|
||||
pthread_detach(thread);
|
||||
} else {
|
||||
delete master_args;
|
||||
}
|
||||
|
||||
while (run) {
|
||||
// Sleep for a while to determine if we should terminate
|
||||
// A better approach is targeted as a future story
|
||||
this_thread::sleep_for(chrono::milliseconds(500));
|
||||
}
|
||||
|
||||
// Terminate the unified polling thread.
|
||||
pthread_join(thread, nullptr);
|
||||
}
|
||||
|
||||
void modbus_master_service_run(const GlueVariablesBinding& binding,
|
||||
volatile bool& run, const char* config) {
|
||||
auto cfg_stream = oplc::open_config();
|
||||
modbus_master_run(cfg_stream, config, binding, run);
|
||||
}
|
||||
|
||||
/** @}*/
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright 2015 Thiago Alves
|
||||
// Copyright 2019 Smarter Grid Solutions
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http ://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissionsand
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef RUNTIME_CORE_MODBUSMASTER_MASTER_H_
|
||||
#define RUNTIME_CORE_MODBUSMASTER_MASTER_H_
|
||||
|
||||
/** \addtogroup openplc_runtime
|
||||
* @{
|
||||
*/
|
||||
|
||||
class GlueVariablesBinding;
|
||||
|
||||
/// @brief Start the modbus master service.
|
||||
///
|
||||
/// @param glue_variables The glue variables that may be bound into this
|
||||
/// service.
|
||||
/// @param run A signal for running this service. This service terminates when
|
||||
/// this signal is false.
|
||||
/// @param config The custom configuration for this service.
|
||||
void modbus_master_service_run(const GlueVariablesBinding& binding,
|
||||
volatile bool& run, const char* config);
|
||||
|
||||
/** @}*/
|
||||
|
||||
#endif // RUNTIME_CORE_MODBUSSLAVE_MASTER_H_
|
|
@ -31,7 +31,7 @@
|
|||
#include "slave.h"
|
||||
#include "indexed_strategy.h"
|
||||
#include "mb_util.h"
|
||||
#include "../ladder.h"
|
||||
#include "../ladder.h"
|
||||
#include "../glue.h"
|
||||
#include "../ini_util.h"
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "interactive_server.h"
|
||||
#include "pstorage.h"
|
||||
#include "../modbusslave/slave.h"
|
||||
#include "../modbusmaster/master.h"
|
||||
#include "../dnp3s/dnp3.h"
|
||||
|
||||
using namespace std;
|
||||
|
@ -29,11 +30,13 @@ ServiceStartFunction pstorage_start_service_fn(pstorage_service_run);
|
|||
ServiceStartFunction dnp3s_start_service_fn(dnp3s_service_run);
|
||||
ServiceStartFunction interactive_start_service_fn(interactive_service_run);
|
||||
ServiceStartFunction modbus_slave_start_service_fn(modbus_slave_service_run);
|
||||
ServiceStartFunction modbus_master_start_service_fn(modbus_master_service_run);
|
||||
|
||||
ServiceDefinition* services[] = {
|
||||
new ServiceDefinition("interactive", interactive_start_service_fn),
|
||||
new ServiceDefinition("pstorage", pstorage_start_service_fn, pstorage_init_fn),
|
||||
new ServiceDefinition("modbusslave", modbus_slave_start_service_fn),
|
||||
new ServiceDefinition("modbusmaster", modbus_master_start_service_fn),
|
||||
#ifdef OPLC_DNP3_OUTSTATION
|
||||
new ServiceDefinition("dnp3s", dnp3s_start_service_fn),
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue