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:
commit
c55798a03f
|
@ -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)
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -113,7 +113,6 @@ void bootstrap() {
|
|||
updateBuffersOut();
|
||||
updateCustomOut();
|
||||
glueVars();
|
||||
mapUnusedIO();
|
||||
|
||||
//======================================================
|
||||
// SERVICE INITIALIZATION
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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_
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/** @}*/
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/** @}*/
|
|
@ -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_
|
|
@ -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_
|
|
@ -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);
|
||||
}
|
||||
|
||||
/** @}*/
|
|
@ -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_
|
|
@ -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");
|
||||
}
|
||||
|
||||
/** @}*/
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue