Merge pull request #98 from smartergridsolutions/feature/PR-777

Rework the modbus slave (server) so that it can follow the same service model
This commit is contained in:
Thiago Alves 2019-11-19 14:52:42 -05:00 committed by GitHub
commit c55798a03f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 1826 additions and 1127 deletions

View File

@ -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/service/*.cpp runtime/vendor/inih-r46/*.c)
file(GLOB oplc_SRC runtime/core/*.cpp runtime/core/dnp3s/*.cpp runtime/core/modbusslave/*.cpp runtime/core/service/*.cpp runtime/vendor/inih-r46/*.c)
include_directories(${OPLC_USER_DIR})
include_directories(runtime/core)

View File

@ -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)
file(GLOB oplc_SRC *.cpp dnp3s/*.cpp service/*.cpp modbusslave/*.cpp)
message("In runtime")
message(${oplc_SRC})

View File

@ -113,7 +113,6 @@ void bootstrap() {
updateBuffersOut();
updateCustomOut();
glueVars();
mapUnusedIO();
//======================================================
// SERVICE INITIALIZATION

View File

@ -88,6 +88,15 @@ Dnp3Receiver::Dnp3Receiver(const Dnp3IndexedGroup& binary_commands, const Dnp3In
}
}
Dnp3Receiver::~Dnp3Receiver() {
if (binary_commands_cache) {
delete[] binary_commands_cache;
}
if (analog_commands_cache) {
delete[] analog_commands_cache;
}
}
/// CROB
CommandStatus Dnp3Receiver::Select(const ControlRelayOutputBlock& command, uint16_t index) {
spdlog::trace("DNP3 select CROB index");

View File

@ -36,6 +36,7 @@ class Dnp3Receiver : public opendnp3::ICommandHandler {
/// @param binary_commands The glue variables for the binary commands.
/// @param analog_commands The glue variables for the analog commands.
Dnp3Receiver(const Dnp3IndexedGroup& binary_commands, const Dnp3IndexedGroup& analog_commands);
~Dnp3Receiver();
opendnp3::CommandStatus Select(const opendnp3::ControlRelayOutputBlock& command, std::uint16_t index) override;

View File

@ -615,13 +615,13 @@ int sendUnitData(struct enip_header *header, struct enip_data_Connected_0x70 *en
// response for it. The return value is the size of the response message in
// bytes.
//-----------------------------------------------------------------------------
int processEnipMessage(unsigned char *buffer, int buffer_size)
int processEnipMessage(unsigned char *buffer, int buffer_size, void* user_data)
{
// initialize logging system
char log_msg[1000];
char *p = log_msg;
// initailize structs
// initialize structs
struct enip_header header;
struct enip_data_Unknown enipDataUnknown;
struct enip_data_Unconnected enipDataUnconnected;

View File

@ -13,11 +13,14 @@
// limitations under the License.
#include <cstdlib>
#include <algorithm>
#include <string>
#include "glue.h"
#include "ladder.h"
using namespace std;
/// @brief Locates a partiular glue variable from the list of all glue
/// variables.
/// @param dir The direction of the variable.
@ -28,9 +31,9 @@
/// @return The variable, or nullptr if there is no such variable.
const GlueVariable* GlueVariablesBinding::find(IecLocationDirection dir,
IecLocationSize size,
std::uint16_t msi,
std::uint8_t lsi) const {
for (std::uint16_t i = 0; i < this->size; ++i) {
uint16_t msi,
uint8_t lsi) const {
for (uint16_t i = 0; i < this->size; ++i) {
const GlueVariable& cur_var = glue_variables[i];
if (cur_var.dir == dir && cur_var.size == size && cur_var.msi == msi && cur_var.lsi == lsi) {
return &glue_variables[i];
@ -40,7 +43,7 @@ const GlueVariable* GlueVariablesBinding::find(IecLocationDirection dir,
return nullptr;
}
const GlueVariable* GlueVariablesBinding::find(const std::string& location) const {
const GlueVariable* GlueVariablesBinding::find(const string& location) const {
if (location.length() < 4 || location[0] != '%') {
return nullptr;
}
@ -85,7 +88,7 @@ const GlueVariable* GlueVariablesBinding::find(const std::string& location) cons
long msi = strtol(location.c_str() + 3, &end_msi, 10);
// Do we have more characters left in the string to read for lsi?
std::size_t start_lsi = end_msi + 1 - location.c_str();
size_t start_lsi = end_msi + 1 - location.c_str();
if (start_lsi >= location.length()) {
find(direction, size, msi, 0);
}
@ -95,3 +98,17 @@ const GlueVariable* GlueVariablesBinding::find(const std::string& location) cons
return find(direction, size, msi, lsi);
}
int32_t GlueVariablesBinding::find_max_msi(IecGlueValueType type,
IecLocationDirection dir) const {
int32_t max_index(-1);
const GlueVariable* glue_variables = this->glue_variables;
for (size_t index = 0; index < this->size; ++index) {
if (type == glue_variables[index].type && dir == glue_variables[index].dir) {
max_index = max(max_index, static_cast<int32_t>(glue_variables[index].msi));
}
}
return max_index;
}

View File

@ -50,7 +50,7 @@ enum IecLocationSize {
IECLST_WORD,
/// Variables that are 4 bytes, including REAL
IECLST_DOUBLEWORD,
/// Variables that are 8 bytes, including LREAL
/// Variables that are 8 bytes (64-bit), including LREAL, LINT
IECLST_LONGWORD,
};
#endif // OPLC_IEC_GLUE_SIZE
@ -150,7 +150,7 @@ class GlueVariablesBinding {
/// @param glue_variables The glue variable binding definitions
/// @param checksum A checksum for the bindining definitions. That is
/// when we generated the bindings, a checking from the source.
GlueVariablesBinding(std::mutex* buffer_lock, const std::uint16_t size,
GlueVariablesBinding(std::mutex* buffer_lock, const std::size_t size,
const GlueVariable* glue_variables,
const char* checksum) :
buffer_lock(buffer_lock),
@ -168,7 +168,7 @@ class GlueVariablesBinding {
std::mutex* buffer_lock;
/// @brief The size of the glue variables array
std::uint16_t size;
std::size_t size;
/// @brief The glue variables array
const GlueVariable* glue_variables;
@ -192,6 +192,15 @@ class GlueVariablesBinding {
/// @return the variable or null if there is no variable that matches all
/// criteria in the specification.
const GlueVariable* find(const std::string& location) const;
/// @brief Find the maximum most significant index for glued variables
/// that match the specified type and direction.
/// @param type the type to match on.
/// @param dir the direction to match on.
/// @return The maximum MSI or less than 0 if there are none with the
/// specified type.
std::int32_t find_max_msi(IecGlueValueType type,
IecLocationDirection dir) const;
};
#endif // CORE_GLUE_H

View File

@ -51,14 +51,11 @@ const uint16_t BUFFER_MAX_SIZE(1024);
std::mutex command_mutex;
// TODO Globals to move into services
bool run_modbus = 0;
uint16_t modbus_port = 502;
bool run_enip = 0;
uint16_t enip_port = 44818;
time_t start_time;
//Global Threads
pthread_t modbus_thread;
pthread_t enip_thread;
//Log Buffer
@ -68,23 +65,13 @@ std::shared_ptr<buffered_sink> log_sink;
using namespace std;
///////////////////////////////////////////////////////////////////////////////
/// @brief Start the Modbus Thread
/// @param *arg
///////////////////////////////////////////////////////////////////////////////
void *modbusThread(void *arg)
{
startServer(modbus_port, MODBUS_PROTOCOL);
return nullptr;
}
///////////////////////////////////////////////////////////////////////////////
/// @brief Start the Enip Thread
/// @param *arg
///////////////////////////////////////////////////////////////////////////////
void *enipThread(void *arg)
{
startServer(enip_port, ENIP_PROTOCOL);
startServer(enip_port, run_enip, &processEnipMessage, nullptr);
return nullptr;
}
@ -243,39 +230,20 @@ void interactive_client_command(const char* command, int client_fd) {
if (strncmp(command, "quit()", 6) == 0)
{
spdlog::info("Issued quit() command");
if (run_modbus)
{
run_modbus = 0;
pthread_join(modbus_thread, NULL);
spdlog::info("Modbus server was stopped");
}
run_openplc = 0;
}
else if (strncmp(command, "start_modbus(", 13) == 0)
{
modbus_port = readCommandArgument(command);
spdlog::info("Issued start_modbus() command to start on port: {}", modbus_port);
if (run_modbus)
{
spdlog::info("Modbus server already active. Restarting on port: {}", modbus_port);
//Stop Modbus server
run_modbus = 0;
pthread_join(modbus_thread, NULL);
spdlog::info("Modbus server was stopped");
ServiceDefinition* def = services_find("modbusslave");
if (def && copy_command_config(command + 13, command_buffer, BUFFER_MAX_SIZE) == 0) {
def->start(command_buffer);
}
//Start Modbus server
run_modbus = 1;
pthread_create(&modbus_thread, NULL, modbusThread, NULL);
}
else if (strncmp(command, "stop_modbus()", 13) == 0)
{
spdlog::info("Issued stop_modbus() command");
if (run_modbus)
{
run_modbus = 0;
pthread_join(modbus_thread, NULL);
spdlog::info("Modbus server was stopped");
ServiceDefinition* def = services_find("modbusslave");
if (def) {
def->stop();
}
}
#ifdef OPLC_DNP3_OUTSTATION

View File

@ -13,6 +13,9 @@
// See the License for the specific language governing permissionsand
// limitations under the License.
#ifndef CORE_INTERACTIVE_SERVER_H_
#define CORE_INTERACTIVE_SERVER_H_
/** \addtogroup openplc_runtime
* @{
*/
@ -30,3 +33,5 @@ void interactive_service_run(const GlueVariablesBinding& binding,
volatile bool& run, const char* config);
/** @}*/
#endif // CORE_INTERACTIVE_SERVER_H_

View File

@ -26,10 +26,6 @@
* @{
*/
#define MODBUS_PROTOCOL 0
#define DNP3_PROTOCOL 1
#define ENIP_PROTOCOL 2
//Internal buffers for I/O and memory. These buffers are defined in the
//auto-generated glueVars.cpp file
#define BUFFER_SIZE 1024
@ -121,29 +117,24 @@ extern int ignored_int_outputs[];
//main.cpp
void sleep_until(struct timespec *ts, int delay);
void sleepms(int milliseconds);
bool pinNotPresent(int *ignored_vector, int vector_size, int pinNumber);
extern uint8_t run_openplc;
void handleSpecialFunctions();
//server.cpp
void startServer(uint16_t port, int protocol_type);
typedef int (*process_message_fn)(unsigned char *buffer, int buffer_size, void* user_data);
void startServer(uint16_t port, volatile bool& run_server, process_message_fn process_message, void* user_data);
int getSO_ERROR(int fd);
void closeSocket(int fd);
bool SetSocketBlockingEnabled(int fd, bool blocking);
//interactive_server.cpp
void initialize_logging(int argc,char **argv);
extern bool run_modbus;
extern bool run_enip;
extern time_t start_time;
//modbus.cpp
int processModbusMessage(unsigned char *buffer, int bufferSize);
void mapUnusedIO();
//enip.cpp
int processEnipMessage(unsigned char *buffer, int buffer_size);
int processEnipMessage(unsigned char *buffer, int buffer_size, void* user_data);
//pccc.cpp ADDED Ulmer
uint16_t processPCCCMessage(unsigned char *buffer, int buffer_size);

View File

@ -67,18 +67,6 @@ void sleep_until(struct timespec *ts, int delay)
clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, ts, NULL);
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Helper function - Makes the running thread sleep for the amount of
/// time in milliseconds
/// \param milliseconds to sleep
////////////////////////////////////////////////////////////////////////////////
void sleepms(int milliseconds)
{
struct timespec ts;
ts.tv_sec = milliseconds / 1000;
ts.tv_nsec = (milliseconds % 1000) * 1000000;
nanosleep(&ts, NULL);
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Verify if pin is present in one of the ignored vectors
@ -156,7 +144,7 @@ void handleSpecialFunctions()
int main(int argc,char **argv)
{
initialize_logging(argc, argv);
spdlog::info("OpenPLC Runtime starting...");
spdlog::info("OpenPLC Runtime starting...");
time(&start_time);
@ -165,51 +153,51 @@ int main(int argc,char **argv)
// automatically start
bootstrap();
//gets the starting point for the clock
spdlog::debug("Getting current time");
struct timespec timer_start;
clock_gettime(CLOCK_MONOTONIC, &timer_start);
//gets the starting point for the clock
spdlog::debug("Getting current time");
struct timespec timer_start;
clock_gettime(CLOCK_MONOTONIC, &timer_start);
//======================================================
// MAIN LOOP
//======================================================
while(run_openplc)
{
//make sure the buffer pointers are correct and
//attached to the user variables
glueVars();
//======================================================
// MAIN LOOP
//======================================================
while(run_openplc)
{
//make sure the buffer pointers are correct and
//attached to the user variables
glueVars();
updateBuffersIn(); //read input image
{
std::lock_guard<std::mutex> guard(bufferLock);
updateCustomIn();
updateBuffersIn_MB(); //update input image table with data from slave devices
handleSpecialFunctions();
config_run__(__tick++); // execute plc program logic
updateCustomOut();
updateBuffersOut_MB(); //update slave devices with data from the output image table
}
updateBuffersOut(); //write output image
updateBuffersIn(); //read input image
updateTime();
{
std::lock_guard<std::mutex> guard(bufferLock);
updateCustomIn();
updateBuffersIn_MB(); //update input image table with data from slave devices
handleSpecialFunctions();
config_run__(__tick++); // execute plc program logic
updateCustomOut();
updateBuffersOut_MB(); //update slave devices with data from the output image table
}
sleep_until(&timer_start, common_ticktime__);
}
updateBuffersOut(); //write output image
updateTime();
sleep_until(&timer_start, common_ticktime__);
}
//======================================================
// SHUTTING DOWN OPENPLC RUNTIME
//======================================================
// SHUTTING DOWN OPENPLC RUNTIME
//======================================================
services_stop();
services_finalize();
spdlog::debug("Disabling outputs...");
spdlog::debug("Disabling outputs...");
disableOutputs();
updateCustomOut();
updateBuffersOut();
spdlog::debug("Shutting down OpenPLC Runtime...");
finalizeHardware();
spdlog::debug("Shutting down OpenPLC Runtime...");
finalizeHardware();
exit(0);
}

View File

@ -1,912 +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 has all the MODBUS/TCP functions supported by the OpenPLC. If any
// other function is to be added to the project, it must be added here
// Thiago Alves, Dec 2015
//-----------------------------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <mutex>
#include "ladder.h"
/** \addtogroup openplc_runtime
* @{
*/
#define MAX_DISCRETE_INPUT 8192
#define MAX_COILS 8192
#define MAX_HOLD_REGS 8192
#define MAX_INP_REGS 1024
#define MIN_16B_RANGE 1024
#define MAX_16B_RANGE 2047
#define MIN_32B_RANGE 2048
#define MAX_32B_RANGE 4095
#define MIN_64B_RANGE 4096
#define MAX_64B_RANGE 8191
#define MB_FC_NONE 0
#define MB_FC_READ_COILS 1
#define MB_FC_READ_INPUTS 2
#define MB_FC_READ_HOLDING_REGISTERS 3
#define MB_FC_READ_INPUT_REGISTERS 4
#define MB_FC_WRITE_COIL 5
#define MB_FC_WRITE_REGISTER 6
#define MB_FC_WRITE_MULTIPLE_COILS 15
#define MB_FC_WRITE_MULTIPLE_REGISTERS 16
#define MB_FC_ERROR 255
#define ERR_NONE 0
#define ERR_ILLEGAL_FUNCTION 1
#define ERR_ILLEGAL_DATA_ADDRESS 2
#define ERR_ILLEGAL_DATA_VALUE 3
#define ERR_SLAVE_DEVICE_FAILURE 4
#define ERR_SLAVE_DEVICE_BUSY 6
#define bitRead(value, bit) (((value) >> (bit)) & 0x01)
#define bitSet(value, bit) ((value) |= (1UL << (bit)))
#define bitClear(value, bit) ((value) &= ~(1UL << (bit)))
#define bitWrite(value, bit, bitvalue) (bitvalue ? bitSet(value, bit) : bitClear(value, bit))
#define lowByte(w) ((unsigned char) ((w) & 0xff))
#define highByte(w) ((unsigned char) ((w) >> 8))
IEC_BOOL mb_discrete_input[MAX_DISCRETE_INPUT];
IEC_BOOL mb_coils[MAX_COILS];
IEC_UINT mb_input_regs[MAX_INP_REGS];
IEC_UINT mb_holding_regs[MAX_HOLD_REGS];
int MessageLength;
////////////////////////////////////////////////////////////////////////////////
/// \brief Concatenate two bytes into an int
////////////////////////////////////////////////////////////////////////////////
int word(unsigned char byte1, unsigned char byte2)
{
int returnValue;
returnValue = (int)(byte1 << 8) | (int)byte2;
return returnValue;
}
////////////////////////////////////////////////////////////////////////////////
/// \brief This function sets the internal NULL OpenPLC buffers to point to
/// valid positions on the Modbus buffer
////////////////////////////////////////////////////////////////////////////////
void mapUnusedIO()
{
std::lock_guard<std::mutex> guard(bufferLock);
for(int i = 0; i < MAX_DISCRETE_INPUT; i++)
{
if (bool_input[i/8][i%8] == NULL) bool_input[i/8][i%8] = &mb_discrete_input[i];
}
for(int i = 0; i < MAX_COILS; i++)
{
if (bool_output[i/8][i%8] == NULL) bool_output[i/8][i%8] = &mb_coils[i];
}
for (int i = 0; i < MAX_INP_REGS; i++)
{
if (int_input[i] == NULL) int_input[i] = &mb_input_regs[i];
}
for (int i = 0; i <= MAX_16B_RANGE; i++)
{
if (i < MIN_16B_RANGE)
{
if (int_output[i] == NULL)
{
int_output[i] = &mb_holding_regs[i];
}
}
else if (i >= MIN_16B_RANGE && i <= MAX_16B_RANGE)
{
if (int_memory[i - MIN_16B_RANGE] == NULL)
{
int_memory[i - MIN_16B_RANGE] = &mb_holding_regs[i];
}
}
}
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Response to a Modbus Error
/// \param *buffer
/// \param mb_error
////////////////////////////////////////////////////////////////////////////////
void ModbusError(unsigned char *buffer, int mb_error)
{
buffer[4] = 0;
buffer[5] = 3;
buffer[7] = buffer[7] | 0x80; //set the highest bit
buffer[8] = mb_error;
MessageLength = 9;
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Implementation of Modbus/TCP Read Coils
/// \param *buffer
/// \param bufferSize
////////////////////////////////////////////////////////////////////////////////
void ReadCoils(unsigned char *buffer, int bufferSize)
{
int Start, ByteDataLength, CoilDataLength;
int mb_error = ERR_NONE;
//this request must have at least 12 bytes. If it doesn't, it's a corrupted message
if (bufferSize < 12)
{
ModbusError(buffer, ERR_ILLEGAL_DATA_VALUE);
return;
}
Start = word(buffer[8], buffer[9]);
CoilDataLength = word(buffer[10], buffer[11]);
ByteDataLength = CoilDataLength / 8; //calculating the size of the message in bytes
if(ByteDataLength * 8 < CoilDataLength) ByteDataLength++;
//asked for too many coils
if (ByteDataLength > 255)
{
ModbusError(buffer, ERR_ILLEGAL_DATA_ADDRESS);
return;
}
//preparing response
buffer[4] = highByte(ByteDataLength + 3);
buffer[5] = lowByte(ByteDataLength + 3); //Number of bytes after this one
buffer[8] = ByteDataLength; //Number of bytes of data
std::lock_guard<std::mutex> guard(bufferLock);
for(int i = 0; i < ByteDataLength ; i++)
{
for(int j = 0; j < 8; j++)
{
int position = Start + i * 8 + j;
if (position < MAX_COILS)
{
if (bool_output[position/8][position%8] != NULL)
{
bitWrite(buffer[9 + i], j, *bool_output[position/8][position%8]);
}
else
{
bitWrite(buffer[9 + i], j, 0);
}
}
else //invalid address
{
mb_error = ERR_ILLEGAL_DATA_ADDRESS;
}
}
}
if (mb_error != ERR_NONE)
{
ModbusError(buffer, mb_error);
}
else
{
MessageLength = ByteDataLength + 9;
}
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Implementation of Modbus/TCP Read Discrete Inputs
/// \param *buffer
/// \param bufferSize
////////////////////////////////////////////////////////////////////////////////
void ReadDiscreteInputs(unsigned char *buffer, int bufferSize)
{
int Start, ByteDataLength, InputDataLength;
int mb_error = ERR_NONE;
//this request must have at least 12 bytes. If it doesn't, it's a corrupted message
if (bufferSize < 12)
{
ModbusError(buffer, ERR_ILLEGAL_DATA_VALUE);
return;
}
Start = word(buffer[8],buffer[9]);
InputDataLength = word(buffer[10],buffer[11]);
ByteDataLength = InputDataLength / 8;
if(ByteDataLength * 8 < InputDataLength) ByteDataLength++;
//asked for too many inputs
if (ByteDataLength > 255)
{
ModbusError(buffer, ERR_ILLEGAL_DATA_ADDRESS);
return;
}
//Preparing response
buffer[4] = highByte(ByteDataLength + 3);
buffer[5] = lowByte(ByteDataLength + 3); //Number of bytes after this one
buffer[8] = ByteDataLength; //Number of bytes of data
std::lock_guard<std::mutex> guard(bufferLock);
for(int i = 0; i < ByteDataLength ; i++)
{
for(int j = 0; j < 8; j++)
{
int position = Start + i * 8 + j;
if (position < MAX_DISCRETE_INPUT)
{
if (bool_input[position/8][position%8] != NULL)
{
bitWrite(buffer[9 + i], j, *bool_input[position/8][position%8]);
}
else
{
bitWrite(buffer[9 + i], j, 0);
}
}
else //invalid address
{
mb_error = ERR_ILLEGAL_DATA_ADDRESS;
}
}
}
if (mb_error != ERR_NONE)
{
ModbusError(buffer, mb_error);
}
else
{
MessageLength = ByteDataLength + 9;
}
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Implementation of Modbus/TCP Read Holding Registers
/// \param *buffer
/// \param bufferSize
////////////////////////////////////////////////////////////////////////////////
void ReadHoldingRegisters(unsigned char *buffer, int bufferSize)
{
int Start, WordDataLength, ByteDataLength;
int mb_error = ERR_NONE;
//this request must have at least 12 bytes. If it doesn't, it's a corrupted message
if (bufferSize < 12)
{
ModbusError(buffer, ERR_ILLEGAL_DATA_VALUE);
return;
}
Start = word(buffer[8],buffer[9]);
WordDataLength = word(buffer[10],buffer[11]);
ByteDataLength = WordDataLength * 2;
//asked for too many registers
if (ByteDataLength > 255)
{
ModbusError(buffer, ERR_ILLEGAL_DATA_ADDRESS);
return;
}
//preparing response
buffer[4] = highByte(ByteDataLength + 3);
buffer[5] = lowByte(ByteDataLength + 3); //Number of bytes after this one
buffer[8] = ByteDataLength; //Number of bytes of data
std::lock_guard<std::mutex> guard(bufferLock);
for(int i = 0; i < WordDataLength; i++)
{
int position = Start + i;
if (position <= MIN_16B_RANGE)
{
if (int_output[position] != NULL)
{
buffer[ 9 + i * 2] = highByte(*int_output[position]);
buffer[10 + i * 2] = lowByte(*int_output[position]);
}
else
{
buffer[ 9 + i * 2] = 0;
buffer[10 + i * 2] = 0;
}
}
//accessing memory
//16-bit registers
else if (position >= MIN_16B_RANGE && position <= MAX_16B_RANGE)
{
if (int_memory[position - MIN_16B_RANGE] != NULL)
{
buffer[ 9 + i * 2] = highByte(*int_memory[position - MIN_16B_RANGE]);
buffer[10 + i * 2] = lowByte(*int_memory[position - MIN_16B_RANGE]);
}
else
{
buffer[ 9 + i * 2] = 0;
buffer[10 + i * 2] = 0;
}
}
//32-bit registers
else if (position >= MIN_32B_RANGE && position <= MAX_32B_RANGE)
{
if (dint_memory[(position - MIN_32B_RANGE)/2] != NULL)
{
if ((position - MIN_32B_RANGE) % 2 == 0) //first word
{
uint16_t tempValue = (uint16_t)(*dint_memory[(position - MIN_32B_RANGE)/2] >> 16);
buffer[ 9 + i * 2] = highByte(tempValue);
buffer[10 + i * 2] = lowByte(tempValue);
}
else //second word
{
uint16_t tempValue = (uint16_t)(*dint_memory[(position - MIN_32B_RANGE)/2] & 0xffff);
buffer[ 9 + i * 2] = highByte(tempValue);
buffer[10 + i * 2] = lowByte(tempValue);
}
}
else
{
buffer[ 9 + i * 2] = mb_holding_regs[position];
buffer[10 + i * 2] = mb_holding_regs[position];
}
}
//64-bit registers
else if (position >= MIN_64B_RANGE && position <= MAX_64B_RANGE)
{
if (lint_memory[(position - MIN_64B_RANGE)/4] != NULL)
{
if ((position - MIN_64B_RANGE) % 4 == 0) //first word
{
uint16_t tempValue = (uint16_t)(*lint_memory[(position - MIN_64B_RANGE)/4] >> 48);
buffer[ 9 + i * 2] = highByte(tempValue);
buffer[10 + i * 2] = lowByte(tempValue);
}
else if ((position - MIN_64B_RANGE) % 4 == 1)//second word
{
uint16_t tempValue = (uint16_t)((*lint_memory[(position - MIN_64B_RANGE)/4] >> 32) & 0xffff);
buffer[ 9 + i * 2] = highByte(tempValue);
buffer[10 + i * 2] = lowByte(tempValue);
}
else if ((position - MIN_64B_RANGE) % 4 == 2)//third word
{
uint16_t tempValue = (uint16_t)((*lint_memory[(position - MIN_64B_RANGE)/4] >> 16) & 0xffff);
buffer[ 9 + i * 2] = highByte(tempValue);
buffer[10 + i * 2] = lowByte(tempValue);
}
else if ((position - MIN_64B_RANGE) % 4 == 3)//fourth word
{
uint16_t tempValue = (uint16_t)(*lint_memory[(position - MIN_64B_RANGE)/4] & 0xffff);
buffer[ 9 + i * 2] = highByte(tempValue);
buffer[10 + i * 2] = lowByte(tempValue);
}
}
else
{
buffer[ 9 + i * 2] = mb_holding_regs[position];
buffer[10 + i * 2] = mb_holding_regs[position];
}
}
//invalid address
else
{
mb_error = ERR_ILLEGAL_DATA_ADDRESS;
}
}
if (mb_error != ERR_NONE)
{
ModbusError(buffer, mb_error);
}
else
{
MessageLength = ByteDataLength + 9;
}
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Implementation of Modbus/TCP Read Input Registers
/// \param *buffer
/// \param bufferSize
////////////////////////////////////////////////////////////////////////////////
void ReadInputRegisters(unsigned char *buffer, int bufferSize)
{
int Start, WordDataLength, ByteDataLength;
int mb_error = ERR_NONE;
//this request must have at least 12 bytes. If it doesn't, it's a corrupted message
if (bufferSize < 12)
{
ModbusError(buffer, ERR_ILLEGAL_DATA_VALUE);
return;
}
Start = word(buffer[8],buffer[9]);
WordDataLength = word(buffer[10],buffer[11]);
ByteDataLength = WordDataLength * 2;
//asked for too many registers
if (ByteDataLength > 255)
{
ModbusError(buffer, ERR_ILLEGAL_DATA_ADDRESS);
return;
}
//preparing response
buffer[4] = highByte(ByteDataLength + 3);
buffer[5] = lowByte(ByteDataLength + 3); //Number of bytes after this one
buffer[8] = ByteDataLength; //Number of bytes of data
std::lock_guard<std::mutex> guard(bufferLock);
for(int i = 0; i < WordDataLength; i++)
{
int position = Start + i;
if (position < MAX_INP_REGS)
{
if (int_input[position] != NULL)
{
buffer[ 9 + i * 2] = highByte(*int_input[position]);
buffer[10 + i * 2] = lowByte(*int_input[position]);
}
else
{
buffer[ 9 + i * 2] = 0;
buffer[10 + i * 2] = 0;
}
}
else //invalid address
{
mb_error = ERR_ILLEGAL_DATA_ADDRESS;
}
}
if (mb_error != ERR_NONE)
{
ModbusError(buffer, mb_error);
}
else
{
MessageLength = ByteDataLength + 9;
}
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Implementation of Modbus/TCP Write Coil
/// \param *buffer
/// \param bufferSize
////////////////////////////////////////////////////////////////////////////////
void WriteCoil(unsigned char *buffer, int bufferSize)
{
int Start;
int mb_error = ERR_NONE;
//this request must have at least 12 bytes. If it doesn't, it's a corrupted message
if (bufferSize < 12)
{
ModbusError(buffer, ERR_ILLEGAL_DATA_VALUE);
return;
}
Start = word(buffer[8], buffer[9]);
if (Start < MAX_COILS)
{
unsigned char value;
if (word(buffer[10], buffer[11]) > 0)
{
value = 1;
}
else
{
value = 0;
}
std::lock_guard<std::mutex> guard(bufferLock);
if (bool_output[Start/8][Start%8] != NULL)
{
*bool_output[Start/8][Start%8] = value;
}
}
else //invalid address
{
mb_error = ERR_ILLEGAL_DATA_ADDRESS;
}
if (mb_error != ERR_NONE)
{
ModbusError(buffer, mb_error);
}
else
{
buffer[4] = 0;
buffer[5] = 6; //Number of bytes after this one.
MessageLength = 12;
}
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Implementation of Modbus/TCP Write Holding Register
/// \param *buffer
/// \param bufferSize
////////////////////////////////////////////////////////////////////////////////
void WriteRegister(unsigned char *buffer, int bufferSize)
{
int Start;
int mb_error = ERR_NONE;
//this request must have at least 12 bytes. If it doesn't, it's a corrupted message
if (bufferSize < 12)
{
ModbusError(buffer, ERR_ILLEGAL_DATA_VALUE);
return;
}
Start = word(buffer[8],buffer[9]);
std::lock_guard<std::mutex> guard(bufferLock);
//analog outputs
if (Start <= MIN_16B_RANGE)
{
if (int_output[Start] != NULL)
{
*int_output[Start] = word(buffer[10],buffer[11]);
}
}
//accessing memory
//16-bit registers
else if (Start >= MIN_16B_RANGE && Start <= MAX_16B_RANGE)
{
if (int_memory[Start - MIN_16B_RANGE] != NULL)
{
*int_memory[Start - MIN_16B_RANGE] = word(buffer[10],buffer[11]);
}
}
//32-bit registers
else if (Start >= MIN_32B_RANGE && Start <= MAX_32B_RANGE)
{
if (dint_memory[(Start - MIN_32B_RANGE)/2] != NULL)
{
uint32_t tempValue = (uint32_t)word(buffer[10],buffer[11]);
if ((Start - MIN_32B_RANGE) % 2 == 0) //first word
{
*dint_memory[(Start - MIN_32B_RANGE) / 2] = *dint_memory[(Start - MIN_32B_RANGE) / 2] & 0x0000ffff;
*dint_memory[(Start - MIN_32B_RANGE) / 2] = *dint_memory[(Start - MIN_32B_RANGE) / 2] | (tempValue << 16);
}
else //second word
{
*dint_memory[(Start - MIN_32B_RANGE) / 2] = *dint_memory[(Start - MIN_32B_RANGE) / 2] & 0xffff0000;
*dint_memory[(Start - MIN_32B_RANGE) / 2] = *dint_memory[(Start - MIN_32B_RANGE) / 2] | tempValue;
}
}
else
{
mb_holding_regs[Start] = word(buffer[10],buffer[11]);
}
}
//64-bit registers
else if (Start >= MIN_64B_RANGE && Start <= MAX_64B_RANGE)
{
if (lint_memory[(Start - MIN_64B_RANGE)/4] != NULL)
{
uint64_t tempValue = (uint64_t)word(buffer[10],buffer[11]);
if ((Start - MIN_64B_RANGE) % 4 == 0) //first word
{
*lint_memory[(Start - MIN_64B_RANGE) / 4] = *lint_memory[(Start - MIN_64B_RANGE) / 4] & 0x0000ffffffffffff;
*lint_memory[(Start - MIN_64B_RANGE) / 4] = *lint_memory[(Start - MIN_64B_RANGE) / 4] | (tempValue << 48);
}
else if ((Start - MIN_64B_RANGE) % 4 == 1) //second word
{
*lint_memory[(Start - MIN_64B_RANGE) / 4] = *lint_memory[(Start - MIN_64B_RANGE) / 4] & 0xffff0000ffffffff;
*lint_memory[(Start - MIN_64B_RANGE) / 4] = *lint_memory[(Start - MIN_64B_RANGE) / 4] | (tempValue << 32);
}
else if ((Start - MIN_64B_RANGE) % 4 == 2) //third word
{
*lint_memory[(Start - MIN_64B_RANGE) / 4] = *lint_memory[(Start - MIN_64B_RANGE) / 4] & 0xffffffff0000ffff;
*lint_memory[(Start - MIN_64B_RANGE) / 4] = *lint_memory[(Start - MIN_64B_RANGE) / 4] | (tempValue << 16);
}
else if ((Start - MIN_64B_RANGE) % 4 == 3) //fourth word
{
*lint_memory[(Start - MIN_64B_RANGE) / 4] = *lint_memory[(Start - MIN_64B_RANGE) / 4] & 0xffffffffffff0000;
*lint_memory[(Start - MIN_64B_RANGE) / 4] = *lint_memory[(Start - MIN_64B_RANGE) / 4] | tempValue;
}
}
else
{
mb_holding_regs[Start] = word(buffer[10],buffer[11]);
}
}
else //invalid address
{
mb_error = ERR_ILLEGAL_DATA_ADDRESS;
}
if (mb_error != ERR_NONE)
{
ModbusError(buffer, mb_error);
}
else
{
buffer[4] = 0;
buffer[5] = 6; //Number of bytes after this one.
MessageLength = 12;
}
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Implementation of Modbus/TCP Write Multiple Coils
/// \param *buffer
/// \param bufferSize
////////////////////////////////////////////////////////////////////////////////
void WriteMultipleCoils(unsigned char *buffer, int bufferSize)
{
int Start, ByteDataLength, CoilDataLength;
int mb_error = ERR_NONE;
//this request must have at least 12 bytes. If it doesn't, it's a corrupted message
if (bufferSize < 12)
{
ModbusError(buffer, ERR_ILLEGAL_DATA_VALUE);
return;
}
Start = word(buffer[8],buffer[9]);
CoilDataLength = word(buffer[10],buffer[11]);
ByteDataLength = CoilDataLength / 8;
if(ByteDataLength * 8 < CoilDataLength) ByteDataLength++;
//this request must have all the bytes it wants to write. If it doesn't, it's a corrupted message
if ( (bufferSize < (13 + ByteDataLength)) || (buffer[12] != ByteDataLength) )
{
ModbusError(buffer, ERR_ILLEGAL_DATA_VALUE);
return;
}
//preparing response
buffer[4] = 0;
buffer[5] = 6; //Number of bytes after this one.
std::lock_guard<std::mutex> guard(bufferLock);
for(int i = 0; i < ByteDataLength ; i++)
{
for(int j = 0; j < 8; j++)
{
int position = Start + i * 8 + j;
if (position < MAX_COILS)
{
if (bool_output[position/8][position%8] != NULL) *bool_output[position/8][position%8] = bitRead(buffer[13 + i], j);
}
else //invalid address
{
mb_error = ERR_ILLEGAL_DATA_ADDRESS;
}
}
}
if (mb_error != ERR_NONE)
{
ModbusError(buffer, mb_error);
}
else
{
MessageLength = 12;
}
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Implementation of Modbus/TCP Write Multiple Registers
/// \param *buffer
/// \param bufferSize
////////////////////////////////////////////////////////////////////////////////
void WriteMultipleRegisters(unsigned char *buffer, int bufferSize)
{
int Start, WordDataLength, ByteDataLength;
int mb_error = ERR_NONE;
//this request must have at least 12 bytes. If it doesn't, it's a corrupted message
if (bufferSize < 12)
{
ModbusError(buffer, ERR_ILLEGAL_DATA_VALUE);
return;
}
Start = word(buffer[8],buffer[9]);
WordDataLength = word(buffer[10],buffer[11]);
ByteDataLength = WordDataLength * 2;
//this request must have all the bytes it wants to write. If it doesn't, it's a corrupted message
if ( (bufferSize < (13 + ByteDataLength)) || (buffer[12] != ByteDataLength) )
{
ModbusError(buffer, ERR_ILLEGAL_DATA_VALUE);
return;
}
//preparing response
buffer[4] = 0;
buffer[5] = 6; //Number of bytes after this one.
std::lock_guard<std::mutex> guard(bufferLock);
for(int i = 0; i < WordDataLength; i++)
{
int position = Start + i;
//analog outputs
if (position <= MIN_16B_RANGE)
{
if (int_output[position] != NULL) *int_output[position] = word(buffer[13 + i * 2], buffer[14 + i * 2]);
}
//accessing memory
//16-bit registers
else if (position >= MIN_16B_RANGE && position <= MAX_16B_RANGE)
{
if (int_memory[position - MIN_16B_RANGE] != NULL) *int_memory[position - MIN_16B_RANGE] = word(buffer[13 + i * 2], buffer[14 + i * 2]);
}
//32-bit registers
else if (position >= MIN_32B_RANGE && position <= MAX_32B_RANGE)
{
if (dint_memory[(Start - MIN_32B_RANGE)/2] != NULL)
{
uint32_t tempValue = (uint32_t)word(buffer[13 + i * 2], buffer[14 + i * 2]);
if ((position - MIN_32B_RANGE) % 2 == 0) //first word
{
*dint_memory[(position - MIN_32B_RANGE) / 2] = *dint_memory[(position - MIN_32B_RANGE) / 2] & 0x0000ffff;
*dint_memory[(position - MIN_32B_RANGE) / 2] = *dint_memory[(position - MIN_32B_RANGE) / 2] | (tempValue << 16);
}
else //second word
{
*dint_memory[(position - MIN_32B_RANGE) / 2] = *dint_memory[(position - MIN_32B_RANGE) / 2] & 0xffff0000;
*dint_memory[(position - MIN_32B_RANGE) / 2] = *dint_memory[(position - MIN_32B_RANGE) / 2] | tempValue;
}
}
else
{
mb_holding_regs[position] = word(buffer[13 + i * 2], buffer[14 + i * 2]);
}
}
//64-bit registers
else if (position >= MIN_64B_RANGE && position <= MAX_64B_RANGE)
{
if (lint_memory[(position - MIN_64B_RANGE)/4] != NULL)
{
uint64_t tempValue = (uint64_t)word(buffer[13 + i * 2], buffer[14 + i * 2]);
if ((position - MIN_64B_RANGE) % 4 == 0) //first word
{
*lint_memory[(position - MIN_64B_RANGE) / 4] = *lint_memory[(position - MIN_64B_RANGE) / 4] & 0x0000ffffffffffff;
*lint_memory[(position - MIN_64B_RANGE) / 4] = *lint_memory[(position - MIN_64B_RANGE) / 4] | (tempValue << 48);
}
else if ((Start - MIN_64B_RANGE) % 4 == 1) //second word
{
*lint_memory[(position - MIN_64B_RANGE) / 4] = *lint_memory[(position - MIN_64B_RANGE) / 4] & 0xffff0000ffffffff;
*lint_memory[(position - MIN_64B_RANGE) / 4] = *lint_memory[(position - MIN_64B_RANGE) / 4] | (tempValue << 32);
}
else if ((Start - MIN_64B_RANGE) % 4 == 2) //third word
{
*lint_memory[(position - MIN_64B_RANGE) / 4] = *lint_memory[(position - MIN_64B_RANGE) / 4] & 0xffffffff0000ffff;
*lint_memory[(position - MIN_64B_RANGE) / 4] = *lint_memory[(position - MIN_64B_RANGE) / 4] | (tempValue << 16);
}
else if ((Start - MIN_64B_RANGE) % 4 == 3) //fourth word
{
*lint_memory[(position - MIN_64B_RANGE) / 4] = *lint_memory[(position - MIN_64B_RANGE) / 4] & 0xffffffffffff0000;
*lint_memory[(position - MIN_64B_RANGE) / 4] = *lint_memory[(position - MIN_64B_RANGE) / 4] | tempValue;
}
}
else
{
mb_holding_regs[Start] = word(buffer[10],buffer[11]);
}
}
else //invalid address
{
mb_error = ERR_ILLEGAL_DATA_ADDRESS;
}
}
if (mb_error != ERR_NONE)
{
ModbusError(buffer, mb_error);
}
else
{
MessageLength = 12;
}
}
////////////////////////////////////////////////////////////////////////////////
/// \brief This function must parse and process the client request and write
/// back theresponse for it.
/// \param *buffer
/// \param bufferSize
/// \return size of the response message in bytes
////////////////////////////////////////////////////////////////////////////////
int processModbusMessage(unsigned char *buffer, int bufferSize)
{
MessageLength = 0;
//check if the message is long enough
if (bufferSize < 8)
{
ModbusError(buffer, ERR_ILLEGAL_FUNCTION);
}
//****************** Read Coils **********************
else if(buffer[7] == MB_FC_READ_COILS)
{
ReadCoils(buffer, bufferSize);
}
//*************** Read Discrete Inputs ***************
else if(buffer[7] == MB_FC_READ_INPUTS)
{
ReadDiscreteInputs(buffer, bufferSize);
}
//****************** Read Holding Registers ******************
else if(buffer[7] == MB_FC_READ_HOLDING_REGISTERS)
{
ReadHoldingRegisters(buffer, bufferSize);
}
//****************** Read Input Registers ******************
else if(buffer[7] == MB_FC_READ_INPUT_REGISTERS)
{
ReadInputRegisters(buffer, bufferSize);
}
//****************** Write Coil **********************
else if(buffer[7] == MB_FC_WRITE_COIL)
{
WriteCoil(buffer, bufferSize);
}
//****************** Write Register ******************
else if(buffer[7] == MB_FC_WRITE_REGISTER)
{
WriteRegister(buffer, bufferSize);
}
//****************** Write Multiple Coils **********************
else if(buffer[7] == MB_FC_WRITE_MULTIPLE_COILS)
{
WriteMultipleCoils(buffer, bufferSize);
}
//****************** Write Multiple Registers ******************
else if(buffer[7] == MB_FC_WRITE_MULTIPLE_REGISTERS)
{
WriteMultipleRegisters(buffer, bufferSize);
}
//****************** Function Code Error ******************
else
{
ModbusError(buffer, ERR_ILLEGAL_FUNCTION);
}
return MessageLength;
}
/** @}*/

View File

@ -28,9 +28,11 @@
#include <string.h>
#include <pthread.h>
#include <chrono>
#include <iostream>
#include <fstream>
#include <string>
#include <thread>
#include <spdlog/spdlog.h>
@ -348,7 +350,6 @@ void parseConfig()
//*/
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Thread to poll each slave device
////////////////////////////////////////////////////////////////////////////////
@ -558,7 +559,7 @@ void *querySlaveDevices(void *arg)
}
}
}
sleepms(polling_period);
this_thread::sleep_for(chrono::milliseconds(polling_period));
}
return 0;

View File

@ -0,0 +1,387 @@
// 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 <cstdint>
#include <cstring>
#include <algorithm>
#include "indexed_strategy.h"
#include "mb_util.h"
#include "../glue.h"
/** \addtogroup openplc_runtime
* @{
*/
#define MIN_16B_RANGE 1024
#define MAX_16B_RANGE 2047
#define MIN_32B_RANGE 2048
#define MAX_32B_RANGE 4095
#define MIN_64B_RANGE 4096
#define MAX_64B_RANGE 8191
using namespace std;
/// How many booleans do we have in a group for the glue variables.
/// We have 8!
const uint8_t BOOL_PER_GROUP(8);
/// Simple indexed masks to get or set bit values in the Modbus packed
/// structure where we use every bit in the byte.
const uint8_t BOOL_BIT_MASK[8] = {
0x01,
0x02,
0x04,
0x08,
0x10,
0x20,
0x40,
0x80,
};
inline void initialize_mapped_from_group(uint16_t msi,
const GlueBoolGroup* group,
vector<MappedBool>& buffer) {
auto start_index = msi * BOOL_PER_GROUP;
for (size_t bool_index = 0; bool_index < BOOL_PER_GROUP; ++bool_index) {
auto mapped_index = start_index + bool_index;
if (group->values[bool_index]) {
buffer[mapped_index].value = group->values[bool_index];
buffer[mapped_index].cached_value = *group->values[bool_index];
}
}
}
IndexedStrategy::IndexedStrategy(const GlueVariablesBinding& bindings) :
glue_mutex(bindings.buffer_lock)
{
lock_guard<mutex> guard(*this->glue_mutex);
// This constructor is pretty long - what we are doing here is
// setting up structures that map between caches and the bound
// glue. These structures give fast (index -based) read/write of
// values. The caches ensure that we are unlikely to have to wait
// for a lock.
// Allocate a big enough read and write buffers. For the "int" types,
// we know the size in advance. For the boolean types, we don't, so
// figure out how big of a buffer we need for boolean types.
const int32_t max_coil_index = bindings.find_max_msi(IECVT_BOOL, IECLDT_OUT);
const int32_t max_di_index = bindings.find_max_msi(IECVT_BOOL, IECLDT_IN);
if (max_coil_index >= 0) {
coil_read_buffer.resize((max_coil_index + 1) * BOOL_PER_GROUP);
coil_write_buffer.resize((max_coil_index + 1) * BOOL_PER_GROUP);
}
if (max_di_index >= 0) {
di_read_buffer.resize((max_di_index + 1) * BOOL_PER_GROUP);
}
// Now go through the items and assign the pointers and initial values
// Note that if we have persistent storage, then the values have already
// been initialized.
const GlueVariable* glue_variables = bindings.glue_variables;
for (size_t index = 0; index < bindings.size; ++index) {
IecGlueValueType type = glue_variables[index].type;
IecLocationDirection dir = glue_variables[index].dir;
uint16_t msi = glue_variables[index].msi;
if (type == IECVT_BOOL) {
const GlueBoolGroup* group = reinterpret_cast<const GlueBoolGroup*>(glue_variables[index].value);
// The first coil index for this variable is always
// multiplied by the number of booleans in that group
// If this index is out of range, then skip it.
if (dir == IECLDT_OUT) {
initialize_mapped_from_group(msi, group, coil_read_buffer);
} else if (dir == IECLDT_IN) {
initialize_mapped_from_group(msi, group, di_read_buffer);
}
}
else if (type == IECVT_INT) {
if (dir == IECLDT_OUT) {
int_register_read_buffer[msi].init(reinterpret_cast<IEC_INT*>(glue_variables[index].value));
} else if (dir == IECLDT_MEM && msi >= MIN_16B_RANGE && msi < MAX_16B_RANGE) {
intm_register_read_buffer[msi - MIN_16B_RANGE].init(reinterpret_cast<IEC_INT*>(glue_variables[index].value));
} else if (dir == IECLDT_IN) {
int_input_read_buffer[msi].init(reinterpret_cast<IEC_INT*>(glue_variables[index].value));
}
}
else if (type == IECVT_DINT && dir == IECLDT_MEM && msi <= MIN_32B_RANGE && msi < MAX_32B_RANGE) {
dintm_register_read_buffer[msi - MIN_32B_RANGE].init(reinterpret_cast<IEC_DINT*>(glue_variables[index].value));
}
else if (type == IECVT_LINT && dir == IECLDT_MEM && msi <= MIN_64B_RANGE && msi < MAX_64B_RANGE) {
lintm_register_read_buffer[msi - MIN_64B_RANGE].init(reinterpret_cast<IEC_LINT*>(glue_variables[index].value));
}
}
}
/// Exchange values between the read and write buffers. This would normally
/// be called periodically so that we transfer values between the cache
/// that we maintain here and the glue variables that the runtime uses.
/// @param write_buffer The write buffer we are exchanging with that contains
/// writes that have not yet be flushed to the glue.
/// @param read_buffer The read buffer that contains the glue variable
/// and a local cache of the value.
template <typename T>
void exchange(array<PendingValue<T>, NUM_REGISTER_VALUES>& write_buffer,
array<MappedValue<T>, NUM_REGISTER_VALUES>& read_buffer) {
for (size_t index = 0; index < write_buffer.size(); ++index) {
// Skip any index that is not mapped to a located variable.
if (!read_buffer[index].value) {
continue;
}
// If there was a write that hasn't be written, then transfer the
// value to the read buffer.
if (write_buffer[index].has_pending) {
*read_buffer[index].value = write_buffer[index].value;
}
// Finally, update our cached value so that we can read the value
// without waiting.
read_buffer[index].cached_value = *read_buffer[index].value;
}
}
void IndexedStrategy::Exchange() {
lock_guard<mutex> guard(*this->glue_mutex);
// Since we already figured out the mapping in an efficient structure
// the process of exchange is simply going through the items. We first
// handle populating writes into the structure.
// Update the read caches for coils and discrete inputs.
// Only the coils can be set, so we only only to check for pending
// writes to those.
for (size_t index = 0; index < coil_write_buffer.size(); ++index) {
if (coil_write_buffer[index].has_pending) {
*coil_read_buffer[index].value = coil_write_buffer[index].value;
}
coil_read_buffer[index].update_cache();
}
// Update the boolean values.
for (size_t index = 0; index < di_read_buffer.size(); ++index) {
di_read_buffer[index].update_cache();
}
exchange(int_register_write_buffer, int_register_read_buffer);
exchange(intm_register_write_buffer, intm_register_read_buffer);
exchange(dintm_register_write_buffer, dintm_register_read_buffer);
exchange(lintm_register_write_buffer, lintm_register_read_buffer);
for (size_t index = 0; index < int_input_read_buffer.size(); ++index) {
int_input_read_buffer[index].update_cache();
}
}
modbus_errno IndexedStrategy::WriteCoil(uint16_t coil_index, bool value) {
lock_guard<mutex> guard(buffer_mutex);
if (coil_index < coil_write_buffer.size()
&& coil_read_buffer[coil_index].value) {
coil_write_buffer[coil_index].set(value);
return 0;
}
return -1;
}
modbus_errno IndexedStrategy::WriteMultipleCoils(uint16_t coil_start_index,
uint16_t num_coils,
uint8_t* values) {
if (coil_start_index + num_coils >= coil_write_buffer.size()) {
return -1;
}
lock_guard<mutex> guard(buffer_mutex);
for (uint16_t index = 0; index < num_coils; ++index) {
// Get the value from the packed structure
bool value = values[index / 8] & BOOL_BIT_MASK[index % 8];
coil_write_buffer[coil_start_index + index].set(value);
}
return 0;
}
modbus_errno IndexedStrategy::ReadCoils(uint16_t coil_start_index,
uint16_t num_values,
uint8_t* values) {
return this->ReadBools(coil_read_buffer, coil_start_index, num_values, values);
}
modbus_errno IndexedStrategy::ReadDiscreteInputs(uint16_t di_start_index,
uint16_t num_values,
uint8_t* values) {
return this->ReadBools(di_read_buffer, di_start_index, num_values, values);
}
modbus_errno IndexedStrategy::ReadBools(const vector<MappedBool>& buffer,
uint16_t start_index,
uint16_t num_values,
uint8_t* values) {
auto max_index = start_index + num_values - 1;
if (max_index >= buffer.size()) {
return -1;
}
// Start by filling with 0 - we know the buffer is bigger than this can be
memset(values, 0, num_values / 8 + 1);
lock_guard<mutex> guard(buffer_mutex);
for (uint16_t index = 0; index < num_values; ++index) {
values[index / 8] |= buffer[start_index + index].cached_value ? BOOL_BIT_MASK[index % 8] : 0;
}
return 0;
}
modbus_errno IndexedStrategy::WriteHoldingRegisters(uint16_t hr_start_index,
uint16_t num_registers,
uint8_t* value) {
// Each holding register is 2 bytes. Depending on the destination value
// we are either updating the entire value or part of the value.
// Here we go through each index and update the appropriate part of the value.
lock_guard<mutex> guard(buffer_mutex);
for (uint16_t index = 0; index < num_registers; ++index) {
uint16_t hr_index = hr_start_index + index;
uint16_t word = mb_to_word(value[0], value[1]);
if (hr_index < MIN_16B_RANGE) {
int_register_write_buffer[hr_index].set(word);
} else if (hr_index < MAX_16B_RANGE) {
hr_index -= MIN_16B_RANGE;
intm_register_write_buffer[hr_index].set(word);
} else if (hr_index < MAX_32B_RANGE) {
hr_index -= MIN_32B_RANGE;
// The word we got is part of a larger 32-bit value, and we will
// bit shift to write the appropriate part. Resize to 32-bits
// so we can shift appropriately.
uint32_t partial_value = (uint32_t) word;
PendingValue<IEC_DINT>& dst = dintm_register_write_buffer[hr_index / 2];
dst.has_pending = true;
if (hr_index % 2 == 0) {
// First word
dst.value = dst.value & 0x0000ffff;
dst.value = dst.value | partial_value;
} else {
// Second word
dst.value = dst.value & 0xffff0000;
dst.value = dst.value | partial_value;
}
} else if (hr_index < MAX_64B_RANGE) {
hr_index -= MIN_64B_RANGE;
// Same as with a 32-bit value, here we are updating part of a
// 64-bit value, so resize so we can bit-shift appropriately.
uint64_t partial_value = (uint64_t) word;
PendingValue<IEC_LINT>& dst = lintm_register_write_buffer[hr_index / 4];
dst.has_pending = true;
auto word_index = hr_index % 4;
switch (word_index) {
case 0:
// First word
dst.value = dst.value & 0x0000ffffffffffff;
dst.value = dst.value | (partial_value << 48);
break;
case 1:
// Second word
dst.value = dst.value & 0xffff0000ffffffff;
dst.value = dst.value | (partial_value << 32);
break;
case 2:
// Third word
dst.value = dst.value & 0xffffffff0000ffff;
dst.value = dst.value | (partial_value << 16);
break;
case 3:
// Fourth word
dst.value = dst.value & 0xffffffffffff0000;
dst.value = dst.value | partial_value;
break;
}
} else {
return -1;
}
// Move our buffer pointer so that we will handle the next register
value += 2;
}
return 0;
}
modbus_errno IndexedStrategy::ReadHoldingRegisters(uint16_t hr_start_index, uint16_t num_registers, uint8_t* value) {
lock_guard<mutex> guard(buffer_mutex);
for (uint16_t index = 0; index < num_registers; ++index) {
uint16_t hr_index = hr_start_index + index;
uint16_t val;
if (hr_index < MIN_16B_RANGE) {
val = int_register_read_buffer[hr_index].cached_value;
} else if (hr_index < MAX_16B_RANGE) {
hr_index -= MIN_16B_RANGE;
val = intm_register_read_buffer[hr_index].cached_value;
} else if (hr_index < MAX_32B_RANGE) {
hr_index -= MIN_32B_RANGE;
if (hr_index % 2 == 0) {
val = (uint16_t)(dintm_register_read_buffer[hr_index / 2].cached_value >> 16);
} else {
val = (uint16_t)(dintm_register_read_buffer[hr_index / 2].cached_value & 0xffff);
}
} else if (hr_index < MAX_64B_RANGE) {
hr_index -= MIN_64B_RANGE;
if (hr_index %4 == 0) {
val = (uint16_t)(lintm_register_read_buffer[hr_index / 4].cached_value >> 48);
} else if (hr_index %4 == 1) {
val = (uint16_t)(lintm_register_read_buffer[hr_index / 4].cached_value >> 32);
} else if (hr_index %4 == 2) {
val = (uint16_t)(lintm_register_read_buffer[hr_index / 4].cached_value >> 16);
} else if (hr_index %4 == 3) {
val = (uint16_t)(lintm_register_read_buffer[hr_index / 4].cached_value & 0xffff);
}
} else {
return -1;
}
value[0] = mb_high_byte(val);
value[1] = mb_low_byte(val);
// Move to the next 16-bit position
value += 2;
}
return 0;
}
modbus_errno IndexedStrategy::ReadInputRegisters(uint16_t hr_start_index, uint16_t num_registers, uint8_t* value) {
lock_guard<mutex> guard(buffer_mutex);
for (uint16_t index = 0; index < num_registers; ++index) {
uint16_t hr_index = hr_start_index + index;
if (hr_index >= MIN_16B_RANGE) {
return -1;
}
uint16_t val = int_input_read_buffer[hr_index].cached_value;
value[0] = mb_high_byte(val);
value[1] = mb_low_byte(val);
// Move to the next 16-bit position
value += 2;
}
return 0;
}
/** @}*/

View File

@ -0,0 +1,202 @@
// 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 CORE_MODBUSSLAVE_INDEXED_STRATEGY_H_
#define CORE_MODBUSSLAVE_INDEXED_STRATEGY_H_
#include <mutex>
#include <vector>
#include "lib/iec_types_all.h"
/** \addtogroup openplc_runtime
* @{
*/
class GlueVariablesBinding;
const std::uint16_t NUM_REGISTER_VALUES(1024);
/// Defines the mapping between a located boolean value
/// and a cache of the value for reading with modbus.
struct MappedBool {
MappedBool() : cached_value(0), value(nullptr) {}
IEC_BOOL cached_value;
IEC_BOOL *value;
inline void update_cache() {
if (this->value) {
this->cached_value = *this->value;
}
}
};
/// Defines a write that has been submitted via Modbus
/// but may not have been applied to the located variable yet.
struct PendingBool {
PendingBool() : has_pending(false), value(0) {}
bool has_pending;
IEC_BOOL value;
/// Set the value and mark it as updated.
inline void set(IEC_BOOL val) {
this->has_pending = true;
this->value = val;
}
};
/// Defines the mapping between a located value
/// and a cache of the value for reading with modbus.
template <typename T>
struct MappedValue {
MappedValue() : cached_value(0), value(nullptr) {}
T cached_value;
T* value;
/// Initialize the glue link and the cached value.
/// @param val The glue variable to initialize from.
inline void init(T* val) {
this->value = val;
this->cached_value = *val;
}
inline void update_cache() {
if (this->value) {
this->cached_value = *this->value;
}
}
};
/// Defines a write that has been submitted via Modbus
/// but may not have been applied to the located variable yet.
template <typename T>
struct PendingValue {
PendingValue() : has_pending(false), value(0) {}
bool has_pending;
T value;
/// Set the value and mark it as updated.
inline void set(T val) {
this->has_pending = true;
this->value = val;
}
};
typedef std::uint8_t modbus_errno;
/// Implements a strategy that maps between modbus and located variables
/// based on the data type and the index.
/// For performance reasons, we determine the mapping when this is
/// constructed so that we can quickly apply changes. This obviously uses
/// more memory, but is far more efficient over the lifetime of the
/// application.
class IndexedStrategy {
public:
/// Initialize a new instance of the strategy using the bindings.
IndexedStrategy(const GlueVariablesBinding& bindings);
/// Exchange data between the cache and the runtime.
void Exchange();
/// Write a coil value into the cache.
/// @param coil_index The index of the void to write
/// @param value The value to write
modbus_errno WriteCoil(std::uint16_t coil_index, bool value);
/// Write multiple coils, where the bits are packed into bytes
/// @param coil_index The index of the first coil to write.
/// @param num_coils The number of coils that have values in the values array.
/// @param values Byte-packed values.
modbus_errno WriteMultipleCoils(std::uint16_t coil_index,
std::uint16_t num_coils,
std::uint8_t* values);
/// Read the specified number of coils into the data buffer. Coils are
/// read such that they are packed into the destination array, 8 values
/// per 1-byte of data.
/// @param coil_start_index The index of the first coil to read
/// @param num_value The number of coils to read.
/// @param values The bytes to pack the coils into.
modbus_errno ReadCoils(std::uint16_t coil_start_index,
std::uint16_t num_values,
std::uint8_t* values);
/// Read the specified number of discrete inputs into the data buffer.
/// Inputs are read such that they are packed into the destination array,
/// 8 values per 1-byte of data.
modbus_errno ReadDiscreteInputs(std::uint16_t coil_start_index,
std::uint16_t num_coils,
std::uint8_t* values);
/// Write the values from holding resisters into the cache.
/// @param hr_start_index Index of the first holding register that
/// was written.
/// @param num_registers Number of register to write - 2 bytes per item.
/// @param value An array of bytes to read from.
modbus_errno WriteHoldingRegisters(std::uint16_t hr_start_index,
std::uint16_t num_registers,
std::uint8_t* value);
/// Read the holding registers into the buffer.
/// @param hr_start_index Index of the first holding register that
/// was written.
/// @param num_registers Number of register to write - 2 bytes per item.
/// @param value An array of bytes to write into.
modbus_errno ReadHoldingRegisters(std::uint16_t hr_start_index,
std::uint16_t num_registers,
std::uint8_t* value);
/// Read the input registers into the buffer.
/// @param hr_start_index Index of the first holding register that
/// was written.
/// @param num_registers Number of register to write - 2 bytes per item.
/// @param value An array of bytes to write into.
modbus_errno ReadInputRegisters(std::uint16_t hr_start_index,
std::uint16_t num_registers,
std::uint8_t* value);
private:
/// Read the boolean values from the mapped structure into the values.
modbus_errno ReadBools(const std::vector<MappedBool>& buffer,
std::uint16_t coil_start_index,
std::uint16_t num_values,
std::uint8_t* values);
private:
std::vector<MappedBool> coil_read_buffer;
std::vector<PendingBool> coil_write_buffer;
std::vector<MappedBool> di_read_buffer;
std::array<PendingValue<IEC_INT>, NUM_REGISTER_VALUES> int_register_write_buffer;
std::array<MappedValue<IEC_INT>, NUM_REGISTER_VALUES> int_register_read_buffer;
std::array<PendingValue<IEC_INT>, NUM_REGISTER_VALUES> intm_register_write_buffer;
std::array<MappedValue<IEC_INT>, NUM_REGISTER_VALUES> intm_register_read_buffer;
std::array<PendingValue<IEC_DINT>, NUM_REGISTER_VALUES> dintm_register_write_buffer;
std::array<MappedValue<IEC_DINT>, NUM_REGISTER_VALUES> dintm_register_read_buffer;
std::array<PendingValue<IEC_LINT>, NUM_REGISTER_VALUES> lintm_register_write_buffer;
std::array<MappedValue<IEC_LINT>, NUM_REGISTER_VALUES> lintm_register_read_buffer;
std::array<MappedValue<IEC_INT>, NUM_REGISTER_VALUES> int_input_read_buffer;
// Protects access to the cached values in this class.
std::mutex buffer_mutex;
std::mutex* glue_mutex;
};
/** @}*/
#endif // CORE_MODBUSSLAVE_INDEXED_STRATEGY_H_

View File

@ -0,0 +1,40 @@
// 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 CORE_MODBUSSLAVE_MB_UTIL_H_
#define CORE_MODBUSSLAVE_MB_UTIL_H_
#include <cstdint>
/** \addtogroup openplc_runtime
* @{
*/
inline std::int16_t mb_to_word(std::uint8_t byte1, std::uint8_t byte2) {
std::int16_t returnValue = (std::int16_t)(byte1 << 8) | (std::int16_t)byte2;
return returnValue;
}
inline std::uint8_t mb_low_byte(std::uint16_t w) {
return (std::uint8_t) ((w) & 0xff);
}
inline std::uint8_t mb_high_byte(std::uint16_t w) {
return (std::uint8_t) ((w) >> 8);
}
/** @}*/
#endif // CORE_MODBUSSLAVE_MB_UTIL_H_

View File

@ -0,0 +1,461 @@
// 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.
// This file has all the MODBUS/TCP functions supported by the OpenPLC. If any
// other function is to be added to the project, it must be added here
// Thiago Alves, Dec 2015
//-----------------------------------------------------------------------------
#include <array>
#include <functional>
#include <fstream>
#include <memory>
#include <string>
#include <ini.h>
#include <spdlog/spdlog.h>
#include "slave.h"
#include "indexed_strategy.h"
#include "mb_util.h"
#include "../ladder.h"
#include "../glue.h"
#include "../ini_util.h"
/** \addtogroup openplc_runtime
* @{
*/
#define MAX_COILS 8192
#define MB_FC_NONE 0
#define MB_FC_READ_COILS 1
#define MB_FC_READ_INPUTS 2
#define MB_FC_READ_HOLDING_REGISTERS 3
#define MB_FC_READ_INPUT_REGISTERS 4
#define MB_FC_WRITE_COIL 5
#define MB_FC_WRITE_REGISTER 6
#define MB_FC_WRITE_MULTIPLE_COILS 15
#define MB_FC_WRITE_MULTIPLE_REGISTERS 16
#define MB_FC_ERROR 255
#define ERR_NONE 0
#define ERR_ILLEGAL_FUNCTION 1
#define ERR_ILLEGAL_DATA_ADDRESS 2
#define ERR_ILLEGAL_DATA_VALUE 3
#define ERR_SLAVE_DEVICE_FAILURE 4
#define ERR_SLAVE_DEVICE_BUSY 6
using namespace std;
/// \brief Response to a Modbus Error
/// \param *buffer
/// \param mb_error
int modbus_error(unsigned char *buffer, int mb_error)
{
buffer[4] = 0;
buffer[5] = 3;
buffer[7] = buffer[7] | 0x80; //set the highest bit
buffer[8] = mb_error;
return 9;
}
inline int read_sizes(unsigned char* buffer, int buffer_size, int16_t& start,
int16_t& num_items) {
// This request must have at least 12 bytes. If it doesn't, it's a corrupted message
if (buffer_size < 12)
{
return modbus_error(buffer, ERR_ILLEGAL_DATA_VALUE);
}
start = mb_to_word(buffer[8], buffer[9]);
num_items = mb_to_word(buffer[10], buffer[11]);
return 0;
}
inline int read_sized_bytes(unsigned char* buffer, int buffer_size, int16_t& start,
int16_t& num_coils, int16_t& num_bytes) {
int ret = read_sizes(buffer, buffer_size, start, num_coils);
// Calculate the size of the message in bytes - they are packed into
// 8 coils per byte. Round up to make sure we cross the byte boundary
// if that many were requested.
num_bytes = ((num_coils + 7) / 8);
if (num_bytes > 255) {
return modbus_error(buffer, ERR_ILLEGAL_DATA_ADDRESS);
}
return 0;
}
/// @brief Implementation of Modbus/TCP Read Coils
/// @param buffer
/// @param buffer_size
int read_coils(unsigned char *buffer, int buffer_size, IndexedStrategy* strategy)
{
int16_t start;
int16_t coil_data_length;
int16_t byte_data_length;
int ret = read_sized_bytes(buffer, buffer_size, start, coil_data_length, byte_data_length);
if (ret != 0) {
return ret;
}
// Preparing response
buffer[4] = mb_high_byte(byte_data_length + 3);
// Number of bytes after this one
buffer[5] = mb_low_byte(byte_data_length + 3);
// Number of bytes of data
buffer[8] = byte_data_length;
modbus_errno err = strategy->ReadCoils(start, coil_data_length, reinterpret_cast<uint8_t*>(buffer + 9));
if (err) {
return modbus_error(buffer, ERR_ILLEGAL_DATA_ADDRESS);
}
return byte_data_length + 9;
}
/// @brief Implementation of Modbus/TCP Read Discrete Inputs
/// @param buffer
/// @param buffer_size
int read_discrete_inputs(unsigned char *buffer, int buffer_size, IndexedStrategy* strategy)
{
int16_t start;
int16_t input_data_length;
int16_t byte_data_length;
int ret = read_sized_bytes(buffer, buffer_size, start, input_data_length, byte_data_length);
if (ret != 0) {
return ret;
}
//Preparing response
buffer[4] = mb_high_byte(byte_data_length + 3);
buffer[5] = mb_low_byte(byte_data_length + 3); //Number of bytes after this one
buffer[8] = byte_data_length; //Number of bytes of data
modbus_errno err = strategy->ReadDiscreteInputs(start, input_data_length, reinterpret_cast<uint8_t*>(buffer + 9));
if (err) {
return modbus_error(buffer, ERR_ILLEGAL_DATA_ADDRESS);
}
return byte_data_length + 9;
}
/// @brief Implementation of Modbus/TCP Read Holding Registers
/// @param *buffer
/// @param bufferSize
int read_holding_registers(unsigned char *buffer, int buffer_size, IndexedStrategy* strategy)
{
int16_t start;
int16_t num_registers;
int ret = read_sizes(buffer, buffer_size, start, num_registers);
if (ret != 0) {
return ret;
}
int16_t byte_data_length = num_registers * 2;
//preparing response
buffer[4] = mb_high_byte(byte_data_length + 3);
buffer[5] = mb_low_byte(byte_data_length + 3); //Number of bytes after this one
buffer[8] = byte_data_length; //Number of bytes of data
modbus_errno err = strategy->ReadHoldingRegisters(start, num_registers, reinterpret_cast<uint8_t*>(buffer + 9));
if (err) {
return modbus_error(buffer, ERR_ILLEGAL_DATA_ADDRESS);
}
return byte_data_length + 9;
}
/// @brief Implementation of Modbus/TCP Read Input Registers
/// @param *buffer
/// @param bufferSize
int read_input_registers(unsigned char *buffer, int buffer_size, IndexedStrategy* strategy)
{
int16_t start;
int16_t num_registers;
int ret = read_sizes(buffer, buffer_size, start, num_registers);
if (ret != 0) {
return ret;
}
int16_t byte_data_length = num_registers * 2;
//preparing response
buffer[4] = mb_high_byte(byte_data_length + 3);
buffer[5] = mb_low_byte(byte_data_length + 3); //Number of bytes after this one
buffer[8] = byte_data_length; //Number of bytes of data
modbus_errno err = strategy->ReadInputRegisters(start, num_registers, reinterpret_cast<uint8_t*>(buffer + 9));
if (err) {
return modbus_error(buffer, ERR_ILLEGAL_DATA_ADDRESS);
}
return byte_data_length + 9;
}
int write_coil(unsigned char* buffer, int buffer_size, IndexedStrategy* strategy)
{
int16_t start = mb_to_word(buffer[8], buffer[9]);
bool value = mb_to_word(buffer[10], buffer[11]) != 0;
modbus_errno err = strategy->WriteCoil(start, value);
if (err) {
return modbus_error(buffer, ERR_ILLEGAL_DATA_ADDRESS);
}
buffer[4] = 0;
//Number of bytes after this one.
buffer[5] = 6;
return 12;
}
int write_holding_register(unsigned char* buffer, int buffer_size, IndexedStrategy* strategy)
{
int16_t start = mb_to_word(buffer[8], buffer[9]);
modbus_errno err = strategy->WriteHoldingRegisters(start, 1, buffer + 9);
if (err) {
return modbus_error(buffer, ERR_ILLEGAL_DATA_ADDRESS);
}
buffer[4] = 0;
//Number of bytes after this one.
buffer[5] = 6;
return 12;
}
int write_multiple_coils(unsigned char* buffer, int buffer_size, IndexedStrategy* strategy)
{
int16_t start;
int16_t input_data_length;
int16_t byte_data_length;
int ret = read_sized_bytes(buffer, buffer_size, start, input_data_length, byte_data_length);
if (ret != 0) {
return ret;
}
// Check that we have enough bytes
if (buffer_size < (byte_data_length + 13) || buffer[12] != byte_data_length) {
return modbus_error(buffer, ERR_ILLEGAL_DATA_VALUE);
}
modbus_errno err = strategy->WriteMultipleCoils(start, input_data_length, buffer + 13);
if (err) {
return modbus_error(buffer, ERR_ILLEGAL_DATA_ADDRESS);
}
//preparing response
buffer[4] = 0;
buffer[5] = 6; //Number of bytes after this one.
return 12;
}
int write_multiple_registers(unsigned char* buffer, int buffer_size, IndexedStrategy* strategy)
{
int16_t start;
int16_t num_registers;
int ret = read_sizes(buffer, buffer_size, start, num_registers);
if (ret != 0) {
return ret;
}
// Check that we have enough bytes
if (buffer_size < (num_registers * 2 + 13) || buffer[12] != num_registers * 2) {
return modbus_error(buffer, ERR_ILLEGAL_DATA_VALUE);
}
modbus_errno err = strategy->WriteHoldingRegisters(start, num_registers, buffer + 13);
if (err) {
return modbus_error(buffer, ERR_ILLEGAL_DATA_ADDRESS);
}
//preparing response
buffer[4] = 0;
buffer[5] = 6; //Number of bytes after this one.
return 12;
}
/// Parse and process the client request and write back the response for it.
/// @param buffer
/// @param buffer_size
/// @param user_data The mapping strategy.
/// @return size of the response message in bytes
int modbus_process_message(unsigned char *buffer, int buffer_size, void* user_data)
{
auto strategy = reinterpret_cast<IndexedStrategy*>(user_data);
//check if the message is long enough
if (buffer_size < 8)
{
return modbus_error(buffer, ERR_ILLEGAL_FUNCTION);
}
switch (buffer[7]) {
case MB_FC_READ_COILS:
return read_coils(buffer, buffer_size, strategy);
case MB_FC_READ_INPUTS:
return read_discrete_inputs(buffer, buffer_size, strategy);
case MB_FC_READ_HOLDING_REGISTERS:
return read_holding_registers(buffer, buffer_size, strategy);
case MB_FC_READ_INPUT_REGISTERS:
return read_input_registers(buffer, buffer_size, strategy);
case MB_FC_WRITE_COIL:
return write_coil(buffer, buffer_size, strategy);
case MB_FC_WRITE_REGISTER:
return write_holding_register(buffer, buffer_size, strategy);
case MB_FC_WRITE_MULTIPLE_COILS:
return write_multiple_coils(buffer, buffer_size, strategy);
case MB_FC_WRITE_MULTIPLE_REGISTERS:
return write_multiple_registers(buffer, buffer_size, strategy);
default:
return modbus_error(buffer, ERR_ILLEGAL_FUNCTION);
}
}
/// Arguments that are passed to the thread to exchange modbus data with the
/// runtime.
struct ModbusExchangeArgs {
IndexedStrategy* strategy;
volatile bool* run;
std::chrono::milliseconds interval;
};
/// The main function for the thread that is responsible for exchanging
/// modbus data with the located variables.
void* modbus_exchange_data(void* args) {
auto exchange_args = reinterpret_cast<ModbusExchangeArgs*>(args);
while (*exchange_args->run) {
spdlog::trace("Exchanging modbus master data");
exchange_args->strategy->Exchange();
this_thread::sleep_for(exchange_args->interval);
}
delete exchange_args;
return nullptr;
}
/// Container for reading in configuration from the config.ini
/// This is populated with values from the config file.
struct ModbusSlaveConfig {
ModbusSlaveConfig() :
address("127.0.0.1"),
port(502),
// The sizes and offsets here are
// to match the original modbus implementation
bits_start(0),
bits_size(8192),
input_bits_start(0),
input_bits_size(8192),
registers_start(0),
registers_size(8192),
input_registers_start(0),
input_registers_size(1024)
{}
string address;
uint16_t port;
uint16_t bits_start;
uint16_t bits_size;
uint16_t input_bits_start;
uint16_t input_bits_size;
uint16_t registers_start;
uint16_t registers_size;
uint16_t input_registers_start;
uint16_t input_registers_size;
uint8_t num_connections;
};
int modbus_slave_cfg_handler(void* user_data, const char* section,
const char* name, const char* value) {
if (strcmp("modbuslave", section) != 0) {
return 0;
}
auto config = reinterpret_cast<ModbusSlaveConfig*>(user_data);
if (strcmp(name, "port") == 0) {
config->port = atoi(value);
} else if (strcmp(name, "address") == 0) {
config->address = 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;
}
int8_t modbus_slave_run(std::unique_ptr<std::istream, std::function<void(std::istream*)>>& cfg_stream,
const char* cfg_overrides,
const GlueVariablesBinding& bindings,
volatile bool& run) {
ModbusSlaveConfig config;
ini_parse_stream(istream_fgets, cfg_stream.get(), modbus_slave_cfg_handler, &config);
cfg_stream.reset(nullptr);
IndexedStrategy strategy(bindings);
pthread_t exchange_data_thread;
auto args = new ModbusExchangeArgs {
.strategy=&strategy,
.run=&run,
.interval=std::chrono::milliseconds(100)
};
int ret = pthread_create(&exchange_data_thread, NULL, modbus_exchange_data, args);
if (ret == 0) {
pthread_detach(exchange_data_thread);
} else {
delete args;
}
spdlog::info("Starting modbus slave on port {}", config.port);
startServer(config.port, run, &modbus_process_message, &strategy);
pthread_join(exchange_data_thread, nullptr);
return 0;
}
void modbus_slave_service_run(const GlueVariablesBinding& binding,
volatile bool& run, const char* config) {
unique_ptr<istream, function<void(istream*)>> cfg_stream(new ifstream("../etc/config.ini"), [](istream* s)
{
reinterpret_cast<ifstream*>(s)->close();
delete s;
});
modbus_slave_run(cfg_stream, config, binding, run);
}
/** @}*/

View File

@ -0,0 +1,41 @@
// 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 CORE_MODBUSSLAVE_SLAVE_H_
#define CORE_MODBUSSLAVE_SLAVE_H_
/** \addtogroup openplc_runtime
* @{
*/
class GlueVariablesBinding;
/// @brief Process an individual modbus message.
int modbus_process_message(unsigned char *buffer, int buffer_size,
void* user_data);
/// @brief Start the modbus slave server.
///
/// @param glue_variables The glue variables that may be bound into this
/// server.
/// @param run A signal for running this server. This server terminates when
/// this signal is false.
/// @param config The custom configuration for this service.
void modbus_slave_service_run(const GlueVariablesBinding& binding,
volatile bool& run, const char* config);
/** @}*/
#endif // CORE_MODBUSSLAVE_SLAVE_H_

View File

@ -26,6 +26,8 @@
#include <string.h>
#include <pthread.h>
#include <fcntl.h>
#include <chrono>
#include <thread>
#include <spdlog/spdlog.h>
#include "ladder.h"
@ -34,6 +36,8 @@
* @{
*/
using namespace std;
#define NET_BUFFER_SIZE 10000
@ -128,26 +132,20 @@ int createSocket(uint16_t port)
////////////////////////////////////////////////////////////////////////////////
/// @brief Blocking call. Wait here for the client to connect.
/// @param socket_fd
/// @param protocol_type
/// @param socket_fd The socket file descriptor.
/// @param run_server A flag to terminate this client.
/// @return file descriptor to communicate with the client
////////////////////////////////////////////////////////////////////////////////
int waitForClient(int socket_fd, int protocol_type)
int waitForClient(int socket_fd, volatile bool& run_server)
{
int client_fd;
struct sockaddr_in client_addr;
bool *run_server;
socklen_t client_len;
if (protocol_type == MODBUS_PROTOCOL)
run_server = &run_modbus;
else if (protocol_type == ENIP_PROTOCOL)
run_server = &run_enip;
spdlog::debug("Server: waiting for new client...");
client_len = sizeof(client_addr);
while (*run_server)
while (run_server)
{
client_fd = accept(socket_fd, (struct sockaddr *)&client_addr, &client_len); //non-blocking call
if (client_fd > 0)
@ -155,7 +153,8 @@ int waitForClient(int socket_fd, int protocol_type)
SetSocketBlockingEnabled(client_fd, true);
break;
}
sleepms(100);
this_thread::sleep_for(chrono::milliseconds(100));
}
return client_fd;
@ -175,125 +174,103 @@ int listenToClient(int client_fd, unsigned char *buffer)
return n;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief Process client's request
/// @param *buffer
/// @param bufferSize
/// @param client_fd
/// @param protocol_type
////////////////////////////////////////////////////////////////////////////////
void processMessage(unsigned char *buffer, int bufferSize, int client_fd, int protocol_type)
{
if (protocol_type == MODBUS_PROTOCOL)
{
int messageSize = processModbusMessage(buffer, bufferSize);
write(client_fd, buffer, messageSize);
}
else if (protocol_type == ENIP_PROTOCOL)
{
int messageSize = processEnipMessage(buffer, bufferSize);
write(client_fd, buffer, messageSize);
}
}
/// Arguments passed to the server thread.
struct ServerArgs {
/// The client file descriptor for reading and writing.
int client_fd;
/// Set to false when the server should terminate.
volatile bool* run;
/// A function to handle received message buffers.
process_message_fn process_message;
/// A user provided data structure for the process message function.
void* user_data;
};
////////////////////////////////////////////////////////////////////////////////
/// @brief Thread to handle requests for each connected client
////////////////////////////////////////////////////////////////////////////////
void *handleConnections(void *arguments)
{
int *args = (int *)arguments;
int client_fd = args[0];
int protocol_type = args[1];
auto args = reinterpret_cast<ServerArgs*>(arguments);
unsigned char buffer[NET_BUFFER_SIZE];
int messageSize;
bool *run_server;
if (protocol_type == MODBUS_PROTOCOL)
run_server = &run_modbus;
else if (protocol_type == ENIP_PROTOCOL)
run_server = &run_enip;
spdlog::debug("Server: Thread created for client ID: {}", client_fd);
spdlog::debug("Server: Thread created for client ID: {}", args->client_fd);
while(*run_server)
while(*args->run)
{
//unsigned char buffer[NET_BUFFER_SIZE];
//int messageSize;
messageSize = listenToClient(client_fd, buffer);
messageSize = listenToClient(args->client_fd, buffer);
if (messageSize <= 0 || messageSize > NET_BUFFER_SIZE)
{
// something has gone wrong or the client has closed connection
if (messageSize == 0)
{
spdlog::debug("Server: client ID: {} has closed the connection", client_fd);
spdlog::debug("Server: client ID: {} has closed the connection", args->client_fd);
}
else
{
spdlog::error("Server: Something is wrong with the client ID: {} message Size : {}", client_fd, messageSize);
spdlog::error("Server: Something is wrong with the client ID: {} message Size : {}", args->client_fd, messageSize);
}
break;
}
processMessage(buffer, messageSize, client_fd, protocol_type);
int messageSize = args->process_message(buffer, NET_BUFFER_SIZE, args->user_data);
write(args->client_fd, buffer, messageSize);
}
spdlog::debug("Closing client socket and calling pthread_exit");
close(client_fd);
close(args->client_fd);
spdlog::info("Terminating server connections thread");
pthread_exit(NULL);
delete args;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief Function to start the server.
/// @brief Function to start a socket server.
///
/// It receives the port number as argument and
/// creates an infinite loop to listen and parse the messages sent by the
/// clients
/// @param port
/// @param protocol_type
/// @see stopServer()
////////////////////////////////////////////////////////////////////////////////
void startServer(uint16_t port, int protocol_type)
/// @param port The port to listen on.
/// @param process_message A function to run to process socket messages.
/// @param user_data Passed into the process_message function as client data.
void startServer(uint16_t port, volatile bool& run_server, process_message_fn process_message, void* user_data)
{
int socket_fd, client_fd;
bool *run_server;
socket_fd = createSocket(port);
if (protocol_type == MODBUS_PROTOCOL)
while(run_server)
{
//mapUnusedIO();
run_server = &run_modbus;
}
else if (protocol_type == ENIP_PROTOCOL)
run_server = &run_enip;
while(*run_server)
{
client_fd = waitForClient(socket_fd, protocol_type); //block until a client connects
client_fd = waitForClient(socket_fd, run_server); //block until a client connects
if (client_fd < 0)
{
spdlog::info("Server: Error accepting client!");
spdlog::error("Server: Error accepting client!");
continue;
}
else
{
int arguments[2];
pthread_t thread;
int ret = -1;
spdlog::debug("Server: Client accepted! Creating thread for the new client ID: {}...", client_fd);
arguments[0] = client_fd;
arguments[1] = protocol_type;
ret = pthread_create(&thread, NULL, handleConnections, (void*)arguments);
if (ret==0)
{
pthread_detach(thread);
}
pthread_t thread;
auto args = new ServerArgs {
.client_fd=client_fd,
.run=&run_server,
.process_message=process_message,
.user_data=user_data
};
spdlog::trace("Server: Client accepted! Creating thread for the new client ID: {}...", client_fd);
int success = pthread_create(&thread, NULL, handleConnections, args);
if (success == 0) {
pthread_detach(thread);
} else {
delete args;
}
}
close(socket_fd);
close(client_fd);
spdlog::info("Terminating server thread");
spdlog::debug("Terminating server thread");
}
/** @}*/

View File

@ -17,18 +17,21 @@
#include "service_definition.h"
#include "service_registry.h"
#include "pstorage.h"
#include "interactive_server.h"
#include "pstorage.h"
#include "../modbusslave/slave.h"
#include "../dnp3s/dnp3.h"
ServiceInitFunction pstorage_init_fn(pstorage_service_init);
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);
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),
#ifdef OPLC_DNP3_OUTSTATION
new ServiceDefinition("dnp3s", dnp3s_start_service_fn),
#endif

View File

@ -29,7 +29,7 @@ endif()
# This is all of our test files
file(GLOB oplctest_SRC *.cpp **/*.cpp)
file(GLOB oplc_core_SRC ../core/pstorage.cpp ../core/dnp3s/*.cpp)
file(GLOB oplc_core_SRC ../core/pstorage.cpp ../core/server.cpp ../core/dnp3s/*.cpp ../core/modbusslave/*.cpp)
add_executable(oplc_unit_test ${oplctest_SRC} ${oplc_core_SRC} ../core/glue.cpp ../vendor/inih-r46/ini.c)

View File

@ -0,0 +1,227 @@
// 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 <mutex>
#include "catch.hpp"
#include "glue.h"
#include "modbusslave/indexed_strategy.h"
using namespace std;
SCENARIO("indexed_strategy", "")
{
mutex glue_mutex;
GIVEN("glue variables with single bit output glue variable")
{
IEC_BOOL bool_val(0);
auto group = GlueBoolGroup { .index=0, .values={ &bool_val, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr } };
const GlueVariable glue_vars[] = {
{ IECLDT_OUT, IECLST_BIT, 0, 0, IECVT_BOOL, &group },
};
GlueVariablesBinding bindings(&glue_mutex, 1, glue_vars, nullptr);
IndexedStrategy strategy(bindings);
uint8_t buffer[1];
WHEN("read coils when value is false")
{
bool_val = 0;
strategy.Exchange();
REQUIRE(strategy.ReadCoils(0, 1, buffer) == 0);
REQUIRE(buffer[0] == 0);
}
WHEN("read coils when value is true")
{
bool_val = 1;
strategy.Exchange();
REQUIRE(strategy.ReadCoils(0, 1, buffer) == 0);
REQUIRE(buffer[0] == 1);
}
WHEN("write coils when value is true")
{
bool_val = 0;
REQUIRE(strategy.WriteCoil(0, true) == 0);
strategy.Exchange();
REQUIRE(bool_val == 1);
}
}
GIVEN("glue variables with single int output glue variable")
{
IEC_INT int_val(0);
const GlueVariable glue_vars[] = {
{ IECLDT_OUT, IECLST_WORD, 0, 0, IECVT_INT, &int_val },
};
GlueVariablesBinding bindings(&glue_mutex, 1, glue_vars, nullptr);
IndexedStrategy strategy(bindings);
uint8_t buffer[1];
WHEN("read holding register when value is 0")
{
int_val = 0;
uint8_t buffer[2];
strategy.Exchange();
REQUIRE(strategy.ReadHoldingRegisters(0, 1, buffer) == 0);
REQUIRE(buffer[0] == 0);
REQUIRE(buffer[1] == 0);
}
WHEN("read holding register when value is 1")
{
int_val = 1;
uint8_t buffer[2];
strategy.Exchange();
REQUIRE(strategy.ReadHoldingRegisters(0, 1, buffer) == 0);
REQUIRE(buffer[0] == 0);
REQUIRE(buffer[1] == 1);
}
WHEN("write holding register when value is 1")
{
int_val = 0;
uint8_t buffer[2] = { 0, 1 };
REQUIRE(strategy.WriteHoldingRegisters(0, 1, buffer) == 0);
strategy.Exchange();
REQUIRE(int_val == 1);
}
}
GIVEN("glue variables with single int mem glue variable")
{
IEC_INT int_val(0);
const GlueVariable glue_vars[] = {
{ IECLDT_MEM, IECLST_WORD, 1024, 0, IECVT_INT, &int_val },
};
GlueVariablesBinding bindings(&glue_mutex, 1, glue_vars, nullptr);
IndexedStrategy strategy(bindings);
uint8_t buffer[1];
WHEN("read holding register when value is 0")
{
int_val = 0;
uint8_t buffer[2];
strategy.Exchange();
REQUIRE(strategy.ReadHoldingRegisters(1024, 1, buffer) == 0);
REQUIRE(buffer[0] == 0);
REQUIRE(buffer[1] == 0);
}
WHEN("read holding register when value is 1")
{
int_val = 1;
uint8_t buffer[2];
strategy.Exchange();
REQUIRE(strategy.ReadHoldingRegisters(1024, 1, buffer) == 0);
REQUIRE(buffer[0] == 0);
REQUIRE(buffer[1] == 1);
}
WHEN("write holding register when value is 1")
{
int_val = 0;
uint8_t buffer[2] = { 0, 1 };
REQUIRE(strategy.WriteHoldingRegisters(1024, 1, buffer) == 0);
strategy.Exchange();
REQUIRE(int_val == 1);
}
}
GIVEN("glue variables with single dint mem glue variable")
{
IEC_DINT dint_val(0);
const GlueVariable glue_vars[] = {
{ IECLDT_MEM, IECLST_DOUBLEWORD, 2048, 0, IECVT_DINT, &dint_val },
};
GlueVariablesBinding bindings(&glue_mutex, 1, glue_vars, nullptr);
IndexedStrategy strategy(bindings);
uint8_t buffer[1];
WHEN("read holding register when value is 0")
{
dint_val = 0;
uint8_t buffer[4];
strategy.Exchange();
REQUIRE(strategy.ReadHoldingRegisters(2048, 2, buffer) == 0);
REQUIRE(buffer[0] == 0);
REQUIRE(buffer[1] == 0);
REQUIRE(buffer[2] == 0);
REQUIRE(buffer[3] == 0);
}
WHEN("read holding register when value is 1")
{
dint_val = 1;
uint8_t buffer[4];
strategy.Exchange();
REQUIRE(strategy.ReadHoldingRegisters(2048, 2, buffer) == 0);
REQUIRE(buffer[0] == 0);
REQUIRE(buffer[1] == 0);
REQUIRE(buffer[2] == 0);
REQUIRE(buffer[3] == 1);
}
}
GIVEN("glue variables with single lint mem glue variable")
{
IEC_LINT lint_val(0);
const GlueVariable glue_vars[] = {
{ IECLDT_MEM, IECLST_LONGWORD, 4096, 0, IECVT_LINT, &lint_val },
};
GlueVariablesBinding bindings(&glue_mutex, 1, glue_vars, nullptr);
IndexedStrategy strategy(bindings);
uint8_t buffer[1];
WHEN("read holding register when value is 0")
{
lint_val = 0;
uint8_t buffer[8];
strategy.Exchange();
REQUIRE(strategy.ReadHoldingRegisters(4096, 4, buffer) == 0);
REQUIRE(buffer[0] == 0);
REQUIRE(buffer[1] == 0);
REQUIRE(buffer[2] == 0);
REQUIRE(buffer[3] == 0);
REQUIRE(buffer[4] == 0);
REQUIRE(buffer[5] == 0);
REQUIRE(buffer[6] == 0);
REQUIRE(buffer[7] == 0);
}
WHEN("read holding register when value is 1")
{
lint_val = 1;
uint8_t buffer[8];
strategy.Exchange();
REQUIRE(strategy.ReadHoldingRegisters(4096, 4, buffer) == 0);
REQUIRE(buffer[0] == 0);
REQUIRE(buffer[1] == 0);
REQUIRE(buffer[2] == 0);
REQUIRE(buffer[3] == 0);
REQUIRE(buffer[4] == 0);
REQUIRE(buffer[5] == 0);
REQUIRE(buffer[6] == 0);
REQUIRE(buffer[7] == 1);
}
}
}

View File

@ -0,0 +1,285 @@
// 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 <mutex>
#include "catch.hpp"
#include "glue.h"
#include "modbusslave/slave.h"
#include "modbusslave/indexed_strategy.h"
using namespace std;
#define HEADER 0, 0, 0, 0, 0, 0, 0
SCENARIO("slave", "")
{
mutex glue_mutex;
GIVEN("glue variables with multiple bit outputs")
{
IEC_BOOL bool_val0(1);
IEC_BOOL bool_val1(0);
IEC_BOOL bool_val2(1);
IEC_BOOL bool_val3(1);
IEC_BOOL bool_val4(0);
IEC_BOOL bool_val5(0);
IEC_BOOL bool_val6(1);
IEC_BOOL bool_val7(1);
IEC_BOOL bool_val8(1);
auto group0 = GlueBoolGroup {
.index=0,
.values={
&bool_val0, &bool_val1, &bool_val2, &bool_val3, &bool_val4, &bool_val5, &bool_val6, &bool_val7
}
};
auto group1 = GlueBoolGroup {
.index=1,
.values={
&bool_val8, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr
}
};
const GlueVariable glue_vars[] = {
{ IECLDT_OUT, IECLST_BIT, 0, 0, IECVT_BOOL, &group0 },
{ IECLDT_OUT, IECLST_BIT, 1, 0, IECVT_BOOL, &group1 },
};
GlueVariablesBinding bindings(&glue_mutex, 2, glue_vars, nullptr);
IndexedStrategy strategy(bindings);
const uint8_t BUF_SIZE(255);
WHEN("read starts from 0 and covers multiple bytes")
{
// FS, Start, Num, CRC
uint8_t buffer[BUF_SIZE] = { HEADER, 1, 0, 0, 0, 9, 0 };
int size = modbus_process_message(buffer, BUF_SIZE, &strategy);
REQUIRE(buffer[7] == 0x01);
REQUIRE(buffer[8] == 0x02);
REQUIRE(buffer[9] == 0xCD);
REQUIRE(buffer[10] == 0x01);
REQUIRE(size == 11);
}
WHEN("read starts from 1 and covers exactly one byte")
{
// FS, Start, Num, CRC
uint8_t buffer[BUF_SIZE] = { HEADER, 1, 0, 1, 0, 8, 0 };
int size = modbus_process_message(buffer, BUF_SIZE, &strategy);
REQUIRE(buffer[7] == 0x01);
REQUIRE(buffer[8] == 0x01);
REQUIRE(buffer[9] == 0xE6);
REQUIRE(size == 10);
}
WHEN("write starts from 0 and covers exactly two byte")
{
// FS, Start, Num, By, Values, CRC
uint8_t buffer[BUF_SIZE] = { HEADER, 15, 0, 0, 0, 9, 2, 0xCD, 0x01, 0 };
int size = modbus_process_message(buffer, BUF_SIZE, &strategy);
REQUIRE(buffer[7] == 15);
REQUIRE(buffer[8] == 0);
REQUIRE(buffer[9] == 0);
REQUIRE(buffer[10] == 0);
REQUIRE(buffer[11] == 9);
REQUIRE(size == 12);
}
}
GIVEN("glue variables with multiple bit inputs")
{
IEC_BOOL bool_val0(1);
IEC_BOOL bool_val1(0);
IEC_BOOL bool_val2(1);
IEC_BOOL bool_val3(1);
IEC_BOOL bool_val4(0);
IEC_BOOL bool_val5(0);
IEC_BOOL bool_val6(1);
IEC_BOOL bool_val7(1);
IEC_BOOL bool_val8(1);
auto group0 = GlueBoolGroup {
.index=0,
.values={
&bool_val0, &bool_val1, &bool_val2, &bool_val3, &bool_val4, &bool_val5, &bool_val6, &bool_val7
}
};
auto group1 = GlueBoolGroup {
.index=1,
.values={
&bool_val8, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr
}
};
const GlueVariable glue_vars[] = {
{ IECLDT_IN, IECLST_BIT, 0, 0, IECVT_BOOL, &group0 },
{ IECLDT_IN, IECLST_BIT, 1, 0, IECVT_BOOL, &group1 },
};
GlueVariablesBinding bindings(&glue_mutex, 2, glue_vars, nullptr);
IndexedStrategy strategy(bindings);
const uint8_t BUF_SIZE(255);
WHEN("starts from 0 and covers multiple bytes")
{
// FS, Start, Num, CRC
uint8_t buffer[BUF_SIZE] = { HEADER, 2, 0, 0, 0, 9, 0 };
int size = modbus_process_message(buffer, BUF_SIZE, &strategy);
REQUIRE(buffer[7] == 0x02);
REQUIRE(buffer[8] == 0x02);
REQUIRE(buffer[9] == 0xCD);
REQUIRE(buffer[10] == 0x01);
REQUIRE(size == 11);
}
WHEN("starts from 1 and covers exactly one byte")
{
// FS, Start, Num, CRC
uint8_t buffer[BUF_SIZE] = { HEADER, 2, 0, 1, 0, 8, 0 };
int size = modbus_process_message(buffer, BUF_SIZE, &strategy);
REQUIRE(buffer[7] == 0x02);
REQUIRE(buffer[8] == 0x01);
REQUIRE(buffer[9] == 0xE6);
REQUIRE(size == 10);
}
}
GIVEN("glue variables with multiple types output")
{
IEC_INT int_val1(1);
IEC_INT int_val2(2);
IEC_DINT dint_val(3);
IEC_LINT lint_val(4);
const GlueVariable glue_vars[] = {
{ IECLDT_OUT, IECLST_WORD, 0, 0, IECVT_INT, &int_val1 },
{ IECLDT_MEM, IECLST_WORD, 1024, 0, IECVT_INT, &int_val2 },
{ IECLDT_MEM, IECLST_DOUBLEWORD, 2048, 0, IECVT_DINT, &dint_val },
{ IECLDT_MEM, IECLST_LONGWORD, 4096, 0, IECVT_LINT, &lint_val },
};
GlueVariablesBinding bindings(&glue_mutex, 4, glue_vars, nullptr);
IndexedStrategy strategy(bindings);
const uint8_t BUF_SIZE(255);
WHEN("reads int output value")
{
// FS, Start, Num, CRC
uint8_t buffer[BUF_SIZE] = { HEADER, 3, 0, 0, 0, 1, 0 };
int size = modbus_process_message(buffer, BUF_SIZE, &strategy);
REQUIRE(buffer[7] == 0x03);
REQUIRE(buffer[8] == 0x02);
REQUIRE(buffer[9] == 0x00);
REQUIRE(buffer[10] == 0x01);
REQUIRE(size == 11);
}
WHEN("reads int mem value")
{
// FS, Start, Num, CRC
uint8_t buffer[BUF_SIZE] = { HEADER, 3, 0x04, 0, 0, 1, 0 };
int size = modbus_process_message(buffer, BUF_SIZE, &strategy);
REQUIRE(buffer[7] == 0x03);
REQUIRE(buffer[8] == 0x02);
REQUIRE(buffer[9] == 0x00);
REQUIRE(buffer[10] == 0x02);
REQUIRE(size == 11);
}
WHEN("write int output value")
{
// FS, Start, Val, CRC
uint8_t buffer[BUF_SIZE] = { HEADER, 6, 0, 0, 0, 0x03, 0 };
int size = modbus_process_message(buffer, BUF_SIZE, &strategy);
REQUIRE(buffer[7] == 0x06);
REQUIRE(buffer[8] == 0x00);
REQUIRE(buffer[9] == 0x00);
REQUIRE(buffer[10] == 0x00);
REQUIRE(buffer[11] == 0x03);
REQUIRE(size == 12);
}
WHEN("write int output values")
{
// FS, Start, Num, By, Val, CRC
uint8_t buffer[BUF_SIZE] = { HEADER, 16, 0, 0, 0, 1, 2, 0, 0x02, 0 };
int size = modbus_process_message(buffer, BUF_SIZE, &strategy);
REQUIRE(buffer[7] == 16);
REQUIRE(buffer[8] == 0x00);
REQUIRE(buffer[9] == 0x00);
REQUIRE(buffer[10] == 0x00);
REQUIRE(buffer[11] == 0x01);
REQUIRE(size == 12);
strategy.Exchange();
REQUIRE(int_val1 == 2);
}
}
GIVEN("glue variables with input")
{
IEC_INT int_val1(2);
const GlueVariable glue_vars[] = {
{ IECLDT_IN, IECLST_WORD, 0, 0, IECVT_INT, &int_val1 },
};
GlueVariablesBinding bindings(&glue_mutex, 1, glue_vars, nullptr);
IndexedStrategy strategy(bindings);
const uint8_t BUF_SIZE(255);
WHEN("reads int input value")
{
// FS, Start, Num, CRC
uint8_t buffer[BUF_SIZE] = { HEADER, 4, 0, 0, 0, 1, 0 };
int size = modbus_process_message(buffer, BUF_SIZE, &strategy);
REQUIRE(buffer[7] == 0x04);
REQUIRE(buffer[8] == 0x02);
REQUIRE(buffer[9] == 0x00);
REQUIRE(buffer[10] == 0x02);
REQUIRE(size == 11);
}
}
GIVEN("glue variables with output bool")
{
IEC_BOOL bool_val1(0);
auto group1 = GlueBoolGroup {
.index=0,
.values={
&bool_val1, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr
}
};
const GlueVariable glue_vars[] = {
{ IECLDT_OUT, IECLST_BIT, 0, 0, IECVT_BOOL, &group1 },
};
GlueVariablesBinding bindings(&glue_mutex, 1, glue_vars, nullptr);
IndexedStrategy strategy(bindings);
const uint8_t BUF_SIZE(255);
WHEN("write one coil")
{
// FS, Start, Num, CRC
uint8_t buffer[BUF_SIZE] = { HEADER, 5, 0, 0, 0xFF, 0, 0 };
int size = modbus_process_message(buffer, BUF_SIZE, &strategy);
REQUIRE(buffer[7] == 0x05);
REQUIRE(buffer[8] == 0x00);
REQUIRE(buffer[9] == 0x00);
REQUIRE(buffer[10] == 0xFF);
REQUIRE(buffer[11] == 0x00);
REQUIRE(size == 12);
strategy.Exchange();
REQUIRE(bool_val1 == 1);
}
}
}

View File

@ -440,7 +440,7 @@ void generateBoolGroups(ostream& glueVars, list<IecVar>& all_vars) {
void generateIntegratedGlue(ostream& glueVars, const list<IecVar>& all_vars) {
glueVars << "/// The size of the array of glue variables.\n";
glueVars << "extern std::uint16_t const OPLCGLUE_GLUE_SIZE(";
glueVars << "extern std::size_t const OPLCGLUE_GLUE_SIZE(";
glueVars << all_vars.size() << ");\n";
glueVars << "/// The packed glue variables.\n";

View File

@ -29,19 +29,19 @@ using namespace std;
#define PREFIX "void glueVars()\n{\n"
#define POSTFIX "}\n\n"
#define EMPTY_INPUT "/// The size of the array of input variables.\n\
extern std::uint16_t const OPLCGLUE_INPUT_SIZE(0);\n\
extern std::size_t const OPLCGLUE_INPUT_SIZE(0);\n\
GlueVariable oplc_input_vars[] = {\n\
};\n\n"
#define EMPTY_OUTPUT "/// The size of the array of output variables.\n\
extern std::uint16_t const OPLCGLUE_OUTPUT_SIZE(0);\n\
extern std::size_t const OPLCGLUE_OUTPUT_SIZE(0);\n\
GlueVariable oplc_output_vars[] = {\n\
};\n\n"
#define EMPTY_INPUT_BOOL "/// Size of the array of input boolean variables.\n\
extern std::uint16_t const OPLCGLUE_INPUT_BOOL_SIZE(0);\n\
extern std::size_t const OPLCGLUE_INPUT_BOOL_SIZE(0);\n\
GlueBoolGroup oplc_bool_input_vars[] = {\n\
};\n\n"
#define EMPTY_OUTPUT_BOOL "/// Size of the array of output boolean variables.\n\
extern std::uint16_t const OPLCGLUE_OUTPUT_BOOL_SIZE(0);\n\
extern std::size_t const OPLCGLUE_OUTPUT_BOOL_SIZE(0);\n\
GlueBoolGroup oplc_bool_output_vars[] = {\n\
};\n\n"
#define GLUE_PREFIX "/// The size of the array of glue variables.\n"
@ -73,7 +73,7 @@ SCENARIO("", "") {
"GlueBoolGroup ___IG0 { .index=0, .values={ __IX0, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, } };\n"
"GlueBoolGroup* __IG0(&___IG0);\n"
GLUE_PREFIX
"extern std::uint16_t const OPLCGLUE_GLUE_SIZE(1);\n"
"extern std::size_t const OPLCGLUE_GLUE_SIZE(1);\n"
"/// The packed glue variables.\n"
"extern const GlueVariable oplc_glue_vars[] = {\n"
" { IECLDT_IN, IECLST_BIT, 0, 0, IECVT_BOOL, __IG0 },\n"
@ -88,7 +88,7 @@ SCENARIO("", "") {
"GlueBoolGroup ___QG0 { .index=0, .values={ __QX0, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, } };\n"
"GlueBoolGroup* __QG0(&___QG0);\n"
GLUE_PREFIX
"extern std::uint16_t const OPLCGLUE_GLUE_SIZE(1);\n"
"extern std::size_t const OPLCGLUE_GLUE_SIZE(1);\n"
"/// The packed glue variables.\n"
"extern const GlueVariable oplc_glue_vars[] = {\n"
" { IECLDT_OUT, IECLST_BIT, 0, 0, IECVT_BOOL, __QG0 },\n"
@ -105,7 +105,7 @@ SCENARIO("", "") {
"GlueBoolGroup ___QG1 { .index=1, .values={ nullptr, nullptr, nullptr, __QX1_3, nullptr, nullptr, nullptr, nullptr, } };\n"
"GlueBoolGroup* __QG1(&___QG1);\n"
GLUE_PREFIX
"extern std::uint16_t const OPLCGLUE_GLUE_SIZE(2);\n"
"extern std::size_t const OPLCGLUE_GLUE_SIZE(2);\n"
"/// The packed glue variables.\n"
"extern const GlueVariable oplc_glue_vars[] = {\n"
" { IECLDT_OUT, IECLST_BIT, 0, 0, IECVT_BOOL, __QG0 },\n"
@ -118,7 +118,7 @@ SCENARIO("", "") {
std::stringstream input_stream("__LOCATED_VAR(BYTE,__IB0,I,B,0)");
generateBody(input_stream, output_stream, digest);
const char* expected = PREFIX"\tbyte_input[0] = __IB0;\n" POSTFIX GLUE_PREFIX
"extern std::uint16_t const OPLCGLUE_GLUE_SIZE(1);\n"
"extern std::size_t const OPLCGLUE_GLUE_SIZE(1);\n"
"/// The packed glue variables.\n"
"extern const GlueVariable oplc_glue_vars[] = {\n"
" { IECLDT_IN, IECLST_BYTE, 0, 0, IECVT_BYTE, __IB0 },\n"
@ -130,7 +130,7 @@ SCENARIO("", "") {
std::stringstream input_stream("__LOCATED_VAR(SINT,__IB1,I,B,1)");
generateBody(input_stream, output_stream, digest);
const char* expected = PREFIX "\tbyte_input[1] = __IB1;\n" POSTFIX GLUE_PREFIX
"extern std::uint16_t const OPLCGLUE_GLUE_SIZE(1);\n"
"extern std::size_t const OPLCGLUE_GLUE_SIZE(1);\n"
"/// The packed glue variables.\n"
"extern const GlueVariable oplc_glue_vars[] = {\n"
" { IECLDT_IN, IECLST_BYTE, 1, 0, IECVT_SINT, __IB1 },\n"
@ -142,7 +142,7 @@ SCENARIO("", "") {
std::stringstream input_stream("__LOCATED_VAR(SINT,__QB1,Q,B,1)");
generateBody(input_stream, output_stream, digest);
const char* expected = PREFIX "\tbyte_output[1] = __QB1;\n" POSTFIX GLUE_PREFIX
"extern std::uint16_t const OPLCGLUE_GLUE_SIZE(1);\n"
"extern std::size_t const OPLCGLUE_GLUE_SIZE(1);\n"
"/// The packed glue variables.\n"
"extern const GlueVariable oplc_glue_vars[] = {\n"
" { IECLDT_OUT, IECLST_BYTE, 1, 0, IECVT_SINT, __QB1 },\n"
@ -154,7 +154,7 @@ SCENARIO("", "") {
std::stringstream input_stream("__LOCATED_VAR(USINT,__IB2,I,B,2)");
generateBody(input_stream, output_stream, digest);
const char* expected = PREFIX "\tbyte_input[2] = __IB2;\n" POSTFIX GLUE_PREFIX
"extern std::uint16_t const OPLCGLUE_GLUE_SIZE(1);\n"
"extern std::size_t const OPLCGLUE_GLUE_SIZE(1);\n"
"/// The packed glue variables.\n"
"extern const GlueVariable oplc_glue_vars[] = {\n"
" { IECLDT_IN, IECLST_BYTE, 2, 0, IECVT_USINT, __IB2 },\n"
@ -166,7 +166,7 @@ SCENARIO("", "") {
std::stringstream input_stream("__LOCATED_VAR(WORD,__IW0,I,W,0)");
generateBody(input_stream, output_stream, digest);
const char* expected = PREFIX "\tint_input[0] = __IW0;\n" POSTFIX GLUE_PREFIX
"extern std::uint16_t const OPLCGLUE_GLUE_SIZE(1);\n"
"extern std::size_t const OPLCGLUE_GLUE_SIZE(1);\n"
"/// The packed glue variables.\n"
"extern const GlueVariable oplc_glue_vars[] = {\n"
" { IECLDT_IN, IECLST_WORD, 0, 0, IECVT_WORD, __IW0 },\n"
@ -178,7 +178,7 @@ SCENARIO("", "") {
std::stringstream input_stream("__LOCATED_VAR(WORD,__QW0,Q,W,0)");
generateBody(input_stream, output_stream, digest);
const char* expected = PREFIX "\tint_output[0] = __QW0;\n" POSTFIX GLUE_PREFIX
"extern std::uint16_t const OPLCGLUE_GLUE_SIZE(1);\n"
"extern std::size_t const OPLCGLUE_GLUE_SIZE(1);\n"
"/// The packed glue variables.\n"
"extern const GlueVariable oplc_glue_vars[] = {\n"
" { IECLDT_OUT, IECLST_WORD, 0, 0, IECVT_WORD, __QW0 },\n"
@ -190,7 +190,7 @@ SCENARIO("", "") {
std::stringstream input_stream("__LOCATED_VAR(INT,__IW1,I,W,1)");
generateBody(input_stream, output_stream, digest);
const char* expected = PREFIX "\tint_input[1] = __IW1;\n" POSTFIX GLUE_PREFIX
"extern std::uint16_t const OPLCGLUE_GLUE_SIZE(1);\n"
"extern std::size_t const OPLCGLUE_GLUE_SIZE(1);\n"
"/// The packed glue variables.\n"
"extern const GlueVariable oplc_glue_vars[] = {\n"
" { IECLDT_IN, IECLST_WORD, 1, 0, IECVT_INT, __IW1 },\n"
@ -203,7 +203,7 @@ SCENARIO("", "") {
generateBody(input_stream, output_stream, digest);
const char* expected = PREFIX "\tint_input[2] = __IW2;\n" POSTFIX GLUE_PREFIX
"extern std::uint16_t const OPLCGLUE_GLUE_SIZE(1);\n"
"extern std::size_t const OPLCGLUE_GLUE_SIZE(1);\n"
"/// The packed glue variables.\n"
"extern const GlueVariable oplc_glue_vars[] = {\n"
" { IECLDT_IN, IECLST_WORD, 2, 0, IECVT_UINT, __IW2 },\n"
@ -217,7 +217,7 @@ SCENARIO("", "") {
// Note that the type-separate glue does not support REAL types
const char* expected = PREFIX POSTFIX GLUE_PREFIX
"extern std::uint16_t const OPLCGLUE_GLUE_SIZE(2);\n"
"extern std::size_t const OPLCGLUE_GLUE_SIZE(2);\n"
"/// The packed glue variables.\n"
"extern const GlueVariable oplc_glue_vars[] = {\n"
" { IECLDT_IN, IECLST_DOUBLEWORD, 0, 0, IECVT_REAL, __ID0 },\n"
@ -230,7 +230,7 @@ SCENARIO("", "") {
std::stringstream input_stream("__LOCATED_VAR(INT,__MW2,M,W,2)");
generateBody(input_stream, output_stream, digest);
const char* expected = PREFIX "\tint_memory[2] = __MW2;\n" POSTFIX GLUE_PREFIX
"extern std::uint16_t const OPLCGLUE_GLUE_SIZE(1);\n"
"extern std::size_t const OPLCGLUE_GLUE_SIZE(1);\n"
"/// The packed glue variables.\n"
"extern const GlueVariable oplc_glue_vars[] = {\n"
" { IECLDT_MEM, IECLST_WORD, 2, 0, IECVT_INT, __MW2 },\n"
@ -242,7 +242,7 @@ SCENARIO("", "") {
std::stringstream input_stream("__LOCATED_VAR(DWORD,__MD2,M,D,2)");
generateBody(input_stream, output_stream, digest);
const char* expected = PREFIX "\tdint_memory[2] = (IEC_DINT *)__MD2;\n" POSTFIX GLUE_PREFIX
"extern std::uint16_t const OPLCGLUE_GLUE_SIZE(1);\n"
"extern std::size_t const OPLCGLUE_GLUE_SIZE(1);\n"
"/// The packed glue variables.\n"
"extern const GlueVariable oplc_glue_vars[] = {\n"
" { IECLDT_MEM, IECLST_DOUBLEWORD, 2, 0, IECVT_DWORD, __MD2 },\n"
@ -254,7 +254,7 @@ SCENARIO("", "") {
std::stringstream input_stream("__LOCATED_VAR(LINT,__ML1,M,L,1)");
generateBody(input_stream, output_stream, digest);
const char* expected = PREFIX "\tlint_memory[1] = (IEC_LINT *)__ML1;\n" POSTFIX GLUE_PREFIX
"extern std::uint16_t const OPLCGLUE_GLUE_SIZE(1);\n"
"extern std::size_t const OPLCGLUE_GLUE_SIZE(1);\n"
"/// The packed glue variables.\n"
"extern const GlueVariable oplc_glue_vars[] = {\n"
" { IECLDT_MEM, IECLST_LONGWORD, 1, 0, IECVT_LINT, __ML1 },\n"
@ -266,7 +266,7 @@ SCENARIO("", "") {
std::stringstream input_stream("__LOCATED_VAR(LINT,__ML1024,M,L,1024)");
generateBody(input_stream, output_stream, digest);
const char* expected = PREFIX "\tspecial_functions[0] = (IEC_LINT *)__ML1024;\n" POSTFIX GLUE_PREFIX
"extern std::uint16_t const OPLCGLUE_GLUE_SIZE(1);\n"
"extern std::size_t const OPLCGLUE_GLUE_SIZE(1);\n"
"/// The packed glue variables.\n"
"extern const GlueVariable oplc_glue_vars[] = {\n"
" { IECLDT_MEM, IECLST_LONGWORD, 1024, 0, IECVT_LINT, __ML1024 },\n"