Merge pull request #101 from smartergridsolutions/feature/PR-786

Try to follow a consistent code style across recently contributed code
This commit is contained in:
Thiago Alves 2019-11-21 09:54:09 -05:00 committed by GitHub
commit 70ed7d68b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 1455 additions and 1457 deletions

View File

@ -41,10 +41,9 @@ struct PlcConfig {
/// Handle reading values from the configuration file
int config_handler(void* user_data, const char* section,
const char* name, const char* value) {
auto config = reinterpret_cast<PlcConfig*>(user_data);
if (ini_matches("logging", "level", section, name)) {
if (oplc::ini_matches("logging", "level", section, name)) {
if (strcmp(value, "trace") == 0) {
spdlog::set_level(spdlog::level::trace);
} else if (strcmp(value, "debug") == 0) {
@ -56,11 +55,15 @@ int config_handler(void* user_data, const char* section,
} else if (strcmp(value, "error") == 0) {
spdlog::set_level(spdlog::level::err);
}
} else if (strcmp("enabled", name) == 0 && ini_atob(value) && services_find(section)) {
} else if (strcmp("enabled", name) == 0
&& oplc::ini_atob(value)
&& services_find(section)) {
// This is the name of service that we can enable, so add it
// to the list of services that we will enable.
config->services.push_back(section);
}
return 0;
}
/// Initialize the PLC runtime
@ -130,16 +133,14 @@ void bootstrap() {
struct sched_param sp;
sp.sched_priority = 30;
spdlog::info("Setting main thread priority to RT");
if(pthread_setschedparam(pthread_self(), SCHED_FIFO, &sp))
{
spdlog::warn("WARNING: Failed to set main thread to real-time priority");
if (pthread_setschedparam(pthread_self(), SCHED_FIFO, &sp)) {
spdlog::warn("Failed to set main thread to real-time priority");
}
// Lock memory to ensure no swapping is done.
spdlog::info("Locking main thread memory");
if(mlockall(MCL_FUTURE|MCL_CURRENT))
{
spdlog::warn("WARNING: Failed to lock memory");
if (mlockall(MCL_FUTURE|MCL_CURRENT)) {
spdlog::warn("Failed to lock memory");
}
#endif
@ -150,8 +151,7 @@ void bootstrap() {
// Our next step here is to start the main loop, so start any
// services that we want now.
for (auto it = config.services.begin(); it != config.services.end(); ++it)
{
for (auto it = config.services.begin(); it != config.services.end(); ++it) {
const char* service_config = "";
ServiceDefinition* def = services_find(it->c_str());
def->start(service_config);

View File

@ -35,6 +35,7 @@
#include <tuple>
#include <thread>
#include <utility>
#include <vector>
#include <asiodnp3/DNP3Manager.h>
#include <asiodnp3/PrintingSOEHandler.h>
@ -73,18 +74,15 @@ using namespace asiodnp3;
/// Implements the ILogHandler interface from OpenDNP3 to forward log messages
/// from OpenDNP3 to spdlog. This allows those log messages to be accessed
/// via the log message API.
class Dnp3ToSpdLogger final : public openpal::ILogHandler, private openpal::Uncopyable
{
public:
class Dnp3ToSpdLogger final : public openpal::ILogHandler, private openpal::Uncopyable {
public:
virtual void Log(const openpal::LogEntry& entry) override {
spdlog::info("{}", entry.message);
}
static std::shared_ptr<openpal::ILogHandler>Create()
{
static std::shared_ptr<openpal::ILogHandler>Create() {
return std::make_shared<Dnp3ToSpdLogger>();
};
}
Dnp3ToSpdLogger() {}
};
@ -199,7 +197,7 @@ void bind_variables(const vector<string>& binding_defs,
const GlueVariable* var = binding.find(name);
if (!var) {
spdlog::error("Unable to bind DNP3 location {} because it is not defined in the application", name);
spdlog::error("Unable to bind DNP3 location {} because it is not defined in the application", name);
continue;
}
@ -316,7 +314,7 @@ int dnp3s_cfg_handler(void* user_data, const char* section,
config->link.KeepAliveTimeout = openpal::TimeDuration::Seconds(atoi(value));
}
} else if (strcmp(name, "enable_unsolicited") == 0) {
config->outstation.params.allowUnsolicited = ini_atob(value);
config->outstation.params.allowUnsolicited = oplc::ini_atob(value);
} else if (strcmp(name, "select_timeout") == 0) {
config->outstation.params.selectTimeout = openpal::TimeDuration::Seconds(atoi(value));
} else if (strcmp(name, "max_controls_per_request") == 0) {
@ -358,7 +356,8 @@ OutstationStackConfig dnp3_create_config(istream& cfg_stream,
// everything into a map, then get the database size, and finally
// process the remaining items
Dnp3Config dnp3_config;
ini_parse_stream(istream_fgets, &cfg_stream, dnp3s_cfg_handler, &dnp3_config);
ini_parse_stream(oplc::istream_fgets, &cfg_stream,
dnp3s_cfg_handler, &dnp3_config);
// We need to know the size of the DNP3 database (the size of each of the
// groups) before we can create the configuration. We can figure that out
@ -368,15 +367,14 @@ OutstationStackConfig dnp3_create_config(istream& cfg_stream,
analog_commands, measurements);
auto config = asiodnp3::OutstationStackConfig(DatabaseSizes(
measurements.group_size(1), // Binary
measurements.group_size(3), // Double binary
measurements.group_size(30), // Analog
measurements.group_size(20), // Counter
measurements.group_size(21), // Frozen counter
measurements.group_size(10), // Binary output status
measurements.group_size(40), // Analog output status
measurements.group_size(50) // Time and interval
));
measurements.group_size(1), // Binary
measurements.group_size(3), // Double binary
measurements.group_size(30), // Analog
measurements.group_size(20), // Counter
measurements.group_size(21), // Frozen counter
measurements.group_size(10), // Binary output status
measurements.group_size(40), // Analog output status
measurements.group_size(50))); // Time and interval
config.outstation = dnp3_config.outstation;
config.link = dnp3_config.link;

View File

@ -12,16 +12,19 @@
// See the License for the specific language governing permissionsand
// limitations under the License.
#ifndef CORE_DNP3S_DNP3_H_
#define CORE_DNP3S_DNP3_H_
#ifndef RUNTIME_CORE_DNP3S_DNP3_H_
#define RUNTIME_CORE_DNP3S_DNP3_H_
#include <cstdint>
#include <chrono>
#include <istream>
#include <memory>
#include <utility>
#include <sstream>
#include <string>
#include "ini_util.h"
namespace asiodnp3 {
class OutstationStackConfig;
}
@ -100,20 +103,23 @@ struct Dnp3MappedGroup {
////////////////////////////////////////////////////////////////////////////////
/// @brief The mapping of glue variables into this DNP3 outstation.
////////////////////////////////////////////////////////////////////////////////
struct Dnp3BoundGlueVariables {
class Dnp3BoundGlueVariables {
public:
Dnp3BoundGlueVariables(
std::mutex* buffer_lock,
std::uint16_t binary_commands_size,
std::uint16_t analog_commands_size,
std::uint16_t measurements_size
) :
std::uint16_t measurements_size) :
buffer_lock(buffer_lock),
binary_commands({ .size=0, .items=nullptr }),
analog_commands({ .size=0, .items=nullptr }),
measurements({ .size=0, .items=nullptr })
binary_commands({ .size = 0, .items = nullptr }),
analog_commands({ .size = 0, .items = nullptr }),
measurements({ .size = 0, .items = nullptr })
{}
private:
Dnp3BoundGlueVariables(Dnp3BoundGlueVariables&);
public:
/// @brief Mutex for the glue variables associated with this structures.
std::mutex* buffer_lock;
@ -156,7 +162,7 @@ asiodnp3::OutstationStackConfig dnp3_create_config(std::istream& cfg_stream,
/// this signal is false.
/// @param glue_variables The glue variables that may be bound into this
/// server.
void dnp3s_start_server(std::unique_ptr<std::istream, std::function<void(std::istream*)>>& cfg_stream,
void dnp3s_start_server(oplc::config_stream& cfg_stream,
const char* cfg_overrides,
volatile bool& run,
const GlueVariablesBinding& glue_variables);
@ -176,4 +182,4 @@ void dnp3s_service_run(const GlueVariablesBinding& binding,
/** @}*/
#endif // CORE_DNP3S_DNP3_H_
#endif // RUNTIME_CORE_DNP3S_DNP3_H_

View File

@ -121,7 +121,6 @@ uint32_t Dnp3Publisher::ExchangeGlue() {
} else {
builder.Update(BinaryOutputStatus(*(bool_group->values[0])), point_index_number);
}
} else if (group == GROUP_ANALOG_INPUT || group == GROUP_ANALOG_OUTPUT_STATUS) {
double double_val = cast_variable<double>(var);
if (group == GROUP_ANALOG_INPUT) {
@ -129,7 +128,6 @@ uint32_t Dnp3Publisher::ExchangeGlue() {
} else {
builder.Update(AnalogOutputStatus(double_val), point_index_number);
}
} else if (group == GROUP_COUNTER || group == GROUP_FROZEN_COUNTER) {
uint32_t int_val = cast_variable<uint32_t>(var);
if (group == GROUP_COUNTER) {

View File

@ -12,8 +12,8 @@
// See the License for the specific language governing permissionsand
// limitations under the License.
#ifndef CORE_DNP3S_DNP3_PUBLISHER_H_
#define CORE_DNP3s_DNP3_PUBLISHER_H_
#ifndef RUNTIME_CORE_DNP3S_DNP3_PUBLISHER_H_
#define RUNTIME_CORE_DNP3s_DNP3_PUBLISHER_H_
#include <cstdint>
#include <memory>
@ -60,6 +60,6 @@ class Dnp3Publisher {
const Dnp3MappedGroup& measurements;
};
#endif // CORE_DNP3S_DNP3_PUBLISHER_H_
/** @}*/
/** @}*/
#endif // RUNTIME_CORE_DNP3S_DNP3_PUBLISHER_H_

View File

@ -300,4 +300,4 @@ void Dnp3Receiver::End() {
#endif // OPLC_DNP3_OUTSTATION
/** @}*/
/** @}*/

View File

@ -12,8 +12,8 @@
// See the License for the specific language governing permissionsand
// limitations under the License.
#ifndef CORE_DNP3_DNP3S_RECEIVER_H_
#define CORE_DNP3_DNP3S_RECEIVER_H_
#ifndef RUNTIME_CORE_DNP3_DNP3S_RECEIVER_H_
#define RUNTIME_CORE_DNP3_DNP3S_RECEIVER_H_
#include <cstdint>
#include <opendnp3/outstation/SimpleCommandHandler.h>
@ -84,6 +84,6 @@ class Dnp3Receiver : public opendnp3::ICommandHandler {
CacheItem<double>* const analog_commands_cache;
};
#endif // CORE_DNP3S_DNP3_RECEIVER_H_
/** @}*/
#endif // RUNTIME_CORE_DNP3S_DNP3_RECEIVER_H_

File diff suppressed because it is too large Load Diff

View File

@ -35,7 +35,8 @@ const GlueVariable* GlueVariablesBinding::find(IecLocationDirection dir,
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) {
if (cur_var.dir == dir && cur_var.size == size
&& cur_var.msi == msi && cur_var.lsi == lsi) {
return &glue_variables[i];
}
}
@ -85,7 +86,7 @@ const GlueVariable* GlueVariablesBinding::find(const string& location) const {
}
char* end_msi;
long msi = strtol(location.c_str() + 3, &end_msi, 10);
uint16_t msi = strtol(location.c_str() + 3, &end_msi, 10);
// Do we have more characters left in the string to read for lsi?
size_t start_lsi = end_msi + 1 - location.c_str();
@ -94,7 +95,7 @@ const GlueVariable* GlueVariablesBinding::find(const string& location) const {
}
char* end_lsi;
long lsi = strtol(end_msi + 1, &end_lsi, 10);
uint8_t lsi = strtol(end_msi + 1, &end_lsi, 10);
return find(direction, size, msi, lsi);
}

View File

@ -5,20 +5,21 @@
// 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_GLUE_H
#define CORE_GLUE_H
#ifndef RUNTIME_CORE_GLUE_H_
#define RUNTIME_CORE_GLUE_H_
#include <cstdint>
#include <iostream>
#include <memory>
#include <mutex>
#include <string>
#include "iec_types.h"
@ -84,7 +85,7 @@ enum IecGlueValueType {
/// and have a way to indicate a value that is not assigned a type.
IECVT_UNASSIGNED
};
#endif // OPLC_IEC_GLUE_VALUE_TYPE
#endif // OPLC_IEC_GLUE_VALUE_TYPE
#ifndef OPLC_GLUE_BOOL_GROUP
#define OPLC_GLUE_BOOL_GROUP
@ -102,14 +103,14 @@ struct GlueBoolGroup {
/// value at the index points to nullptr.
IEC_BOOL* values[8];
};
#endif // OPLC_GLUE_BOOL_GROUP
#endif // OPLC_GLUE_BOOL_GROUP
#ifndef OPLC_GLUE_VARIABLE
#define OPLC_GLUE_VARIABLE
/// @brief Defines the mapping for a glued variable. This defines a simple,
/// space efficient lookup table. It has all of the mapping information that
/// space efficient lookup table. It has all of the mapping information that
/// you need to find the variable based on the location name (e.g. %IB1.1).
/// While this is space efficient, this should be searched once to construct a
/// fast lookup into this table used for the remainder of the application
@ -142,8 +143,7 @@ struct GlueVariable {
/// allows a straightforward way to inject definitions into tests, so it is
/// preferred to use this structure rather than globals.
class GlueVariablesBinding {
public:
public:
/// Initialize a new instance of the glue bindings.
/// @param buffer_lock A lock for accessing the value part of bindings
/// @param size The number of bindings we have
@ -159,11 +159,11 @@ class GlueVariablesBinding {
checksum(checksum)
{}
private:
private:
// Don't allow copying of the bindings.
GlueVariablesBinding(const GlueVariablesBinding& copy);
public:
public:
/// @brief Mutex for the glue variables
std::mutex* buffer_lock;
@ -189,9 +189,10 @@ class GlueVariablesBinding {
/// @brief Find a glue variable based on the location of the variable, for
/// example %IX0.1
/// @param loc The location name to find.
/// @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;
const GlueVariable* find(const std::string& loc) const;
/// @brief Find the maximum most significant index for glued variables
/// that match the specified type and direction.
@ -203,6 +204,6 @@ class GlueVariablesBinding {
IecLocationDirection dir) const;
};
#endif // CORE_GLUE_H
#endif // RUNTIME_CORE_GLUE_H_
/** @}*/

View File

@ -60,17 +60,17 @@ void finalizeHardware()
////////////////////////////////////////////////////////////////////////////////
void updateBuffersIn()
{
std::lock_guard<std::mutex> lock(bufferLock); //lock mutex
std::lock_guard<std::mutex> lock(bufferLock); //lock mutex
/*********READING AND WRITING TO I/O**************
/*********READING AND WRITING TO I/O**************
*bool_input[0][0] = read_digital_input(0);
write_digital_output(0, *bool_output[0][0]);
*bool_input[0][0] = read_digital_input(0);
write_digital_output(0, *bool_output[0][0]);
*int_input[0] = read_analog_input(0);
write_analog_output(0, *int_output[0]);
*int_input[0] = read_analog_input(0);
write_analog_output(0, *int_output[0]);
**************************************************/
**************************************************/
}
////////////////////////////////////////////////////////////////////////////////
@ -82,17 +82,17 @@ void updateBuffersIn()
////////////////////////////////////////////////////////////////////////////////
void updateBuffersOut()
{
std::lock_guard<std::mutex> lock(bufferLock); //lock mutex
std::lock_guard<std::mutex> lock(bufferLock); //lock mutex
/*********READING AND WRITING TO I/O**************
/*********READING AND WRITING TO I/O**************
*bool_input[0][0] = read_digital_input(0);
write_digital_output(0, *bool_output[0][0]);
*bool_input[0][0] = read_digital_input(0);
write_digital_output(0, *bool_output[0][0]);
*int_input[0] = read_analog_input(0);
write_analog_output(0, *int_output[0]);
*int_input[0] = read_analog_input(0);
write_analog_output(0, *int_output[0]);
**************************************************/
**************************************************/
}
/** @} */

View File

@ -12,15 +12,19 @@
// See the License for the specific language governing permissionsand
// limitations under the License.
#ifndef CORE_INI_UTIL_H_
#define CORE_INI_UTIL_H_
#ifndef RUNTIME_CORE_INI_UTIL_H_
#define RUNTIME_CORE_INI_UTIL_H_
/** \addtogroup openplc_runtime
* @{
*/
#include <cstring>
#include <fstream>
#include <istream>
#include <memory>
namespace oplc {
/// Convert a boolean value in the INI file to a boolean.
/// The value must be "true", otherwise it is interpreted as false.
@ -61,6 +65,22 @@ static char* istream_fgets(char* str, int num, void* stream) {
return str;
}
typedef std::unique_ptr<std::istream, std::function<void(std::istream*)>> config_stream;
/// Open the standard configuration file as an closable stream.
/// @return A stream for the configuration file.
inline config_stream open_config() {
return config_stream(
new std::ifstream("../etc/config.ini"),
[] (std::istream* s) {
reinterpret_cast<std::ifstream*>(s)->close();
delete s;
}
);
}
} // namespace oplc
/** @}*/
#endif // CORE_INI_UTIL_H_
#endif // RUNTIME_CORE_INI_UTIL_H_

View File

@ -6,7 +6,7 @@
// 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.
@ -15,13 +15,18 @@
// This is the file for the interactive server. It has procedures to create
// a socket, bind it, start network communication, and process commands. The
// a socket, bind it, start network communication, and process commands. The
// interactive server only responds to localhost and it is used to communicate
// with the Python webserver GUI only.
//
// Thiago Alves, Jun 2018
//-----------------------------------------------------------------------------
#include <stdio.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <spdlog/spdlog.h>
#include <algorithm>
#include <cstdint>
#include <chrono>
#include <fstream>
@ -29,17 +34,14 @@
#include <istream>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <stdio.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <spdlog/spdlog.h>
#include "glue.h"
#include "ini_util.h"
#include "ladder.h"
#include "logsink.h"
#include "interactive_server.h"
#include "service/service_definition.h"
#include "service/service_registry.h"
@ -55,47 +57,46 @@ bool run_enip = 0;
uint16_t enip_port = 44818;
time_t start_time;
//Global Threads
// Global Threads
pthread_t enip_thread;
//Log Buffer
// Log Buffer
#define LOG_BUFFER_SIZE 1000000
unsigned char log_buffer[LOG_BUFFER_SIZE];
std::shared_ptr<buffered_sink> log_sink;
using namespace std;
///////////////////////////////////////////////////////////////////////////////
/// @brief Start the Enip Thread
/// @param *arg
///////////////////////////////////////////////////////////////////////////////
void *enipThread(void *arg)
{
void *enipThread(void *arg) {
startServer(enip_port, run_enip, &processEnipMessage, nullptr);
return nullptr;
}
///////////////////////////////////////////////////////////////////////////////
/// @brief Read the argument from a command function
/// @param *command
///////////////////////////////////////////////////////////////////////////////
int readCommandArgument(const char *command)
{
int readCommandArgument(const char *command) {
int i = 0;
int j = 0;
char argument[BUFFER_MAX_SIZE];
while (command[i] != '(' && command[i] != '\0') i++;
if (command[i] == '(') i++;
while (command[i] != ')' && command[i] != '\0')
{
while (command[i] != '(' && command[i] != '\0') {
i++;
}
if (command[i] == '(') {
i++;
}
while (command[i] != ')' && command[i] != '\0') {
argument[j] = command[i];
i++;
j++;
argument[j] = '\0';
}
return atoi((char *)argument);
return atoi(reinterpret_cast<char *>(argument));
}
/// Copy the configuration argument from the command into the buffer
@ -106,8 +107,7 @@ int readCommandArgument(const char *command)
/// fails, it marks the target as an empty string so it is still safe
/// to read the string but it will be empty.
std::int8_t copy_command_config(const char *source, char target[],
size_t target_size)
{
size_t target_size) {
// Search through source until we find the closing ")"
size_t end_index = 0;
while (source[end_index] != ')' && source[end_index] != '\0') {
@ -138,41 +138,42 @@ std::int8_t copy_command_config(const char *source, char target[],
/// @param port
/// @return the file descriptor for the socket, or less than 0 if a socket
/// if an error occurred.
int interactive_open_socket(uint16_t port)
{
int interactive_open_socket(uint16_t port) {
int socket_fd;
struct sockaddr_in server_addr;
//Create TCP Socket
// Create TCP Socket
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if (socket_fd < 0)
{
spdlog::error("Interactive Server: error creating stream socket => {}", strerror(errno));
if (socket_fd < 0) {
spdlog::error("Interactive Server: error creating stream socket => {}",
strerror(errno));
return -1;
}
//Set SO_REUSEADDR
// Set SO_REUSEADDR
int enable = 1;
if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) {
if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR,
&enable, sizeof(int)) < 0) {
perror("setsockopt(SO_REUSEADDR) failed");
}
SetSocketBlockingEnabled(socket_fd, false);
//Initialize Server Struct
// Initialize Server Struct
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
server_addr.sin_port = htons(port);
//Bind socket
if (bind(socket_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
{
spdlog::error("Interactive Server: error binding socket => {}", strerror(errno));
// Bind socket
if (bind(socket_fd, (struct sockaddr *)&server_addr,
sizeof(server_addr)) < 0) {
spdlog::error("Interactive Server: error binding socket => {}",
strerror(errno));
return -2;
}
// we accept max 5 pending connections
// We accept max 5 pending connections
listen(socket_fd, 5);
spdlog::info("Interactive Server: Listening on port {}", port);
@ -186,8 +187,7 @@ int interactive_open_socket(uint16_t port)
/// @param run A flag that is set to false when we should stop polling.
/// @param socket_fd The socket file descriptor we are listening on.
/// @return the client file descriptor.
int interactive_wait_new_client(volatile bool& run, int socket_fd)
{
int interactive_wait_new_client(volatile bool& run, int socket_fd) {
int client_fd;
struct sockaddr_in client_addr;
socklen_t client_len;
@ -195,11 +195,12 @@ int interactive_wait_new_client(volatile bool& run, int socket_fd)
spdlog::trace("Interactive Server: waiting for new client...");
client_len = sizeof(client_addr);
while (run)
{
client_fd = accept(socket_fd, (struct sockaddr *)&client_addr, &client_len); //non-blocking call
if (client_fd > 0)
{
while (run) {
// Non-blocking call
client_fd = accept(socket_fd, (struct sockaddr *)&client_addr,
&client_len);
if (client_fd > 0) {
SetSocketBlockingEnabled(client_fd, true);
break;
}
@ -219,7 +220,7 @@ void interactive_client_command(const char* command, int client_fd) {
std::unique_lock<std::mutex> lock(command_mutex, std::defer_lock);
if (!lock.try_lock()) {
spdlog::trace("Process command skipped because already processing {}", command);
spdlog::trace("Command skipped - already processing {}", command);
int count_char = sprintf(command_buffer, "Another command in progress...\n");
write(client_fd, command_buffer, count_char);
return;
@ -227,47 +228,40 @@ void interactive_client_command(const char* command, int client_fd) {
spdlog::trace("Process command received {}", command);
if (strncmp(command, "quit()", 6) == 0)
{
if (strncmp(command, "quit()", 6) == 0) {
spdlog::info("Issued quit() command");
run_openplc = 0;
}
else if (strncmp(command, "start_modbus(", 13) == 0)
{
else if (strncmp(command, "start_modbus(", 13) == 0) {
ServiceDefinition* def = services_find("modbusslave");
if (def && copy_command_config(command + 13, command_buffer, BUFFER_MAX_SIZE) == 0) {
def->start(command_buffer);
}
}
else if (strncmp(command, "stop_modbus()", 13) == 0)
{
else if (strncmp(command, "stop_modbus()", 13) == 0) {
ServiceDefinition* def = services_find("modbusslave");
if (def) {
def->stop();
}
}
#ifdef OPLC_DNP3_OUTSTATION
else if (strncmp(command, "start_dnp3(", 11) == 0)
{
else if (strncmp(command, "start_dnp3(", 11) == 0) {
ServiceDefinition* def = services_find("dnp3s");
if (def && copy_command_config(command + 11, command_buffer, BUFFER_MAX_SIZE) == 0) {
def->start(command_buffer);
}
}
else if (strncmp(command, "stop_dnp3()", 11) == 0)
{
else if (strncmp(command, "stop_dnp3()", 11) == 0) {
ServiceDefinition* def = services_find("dnp3s");
if (def) {
def->stop();
}
}
#endif // OPLC_DNP3_OUTSTATION
else if (strncmp(command, "start_enip(", 11) == 0)
{
else if (strncmp(command, "start_enip(", 11) == 0) {
spdlog::info("Issued start_enip() command to start on port: {}", readCommandArgument(command));
enip_port = readCommandArgument(command);
if (run_enip)
{
if (run_enip) {
spdlog::info("EtherNet/IP server already active. Restarting on port: {}", enip_port);
//Stop Enip server
run_enip = 0;
@ -278,8 +272,7 @@ void interactive_client_command(const char* command, int client_fd) {
run_enip = 1;
pthread_create(&enip_thread, NULL, enipThread, NULL);
}
else if (strncmp(command, "stop_enip()", 11) == 0)
{
else if (strncmp(command, "stop_enip()", 11) == 0) {
spdlog::info("Issued stop_enip() command");
if (run_enip)
{
@ -288,42 +281,37 @@ void interactive_client_command(const char* command, int client_fd) {
spdlog::info("EtherNet/IP server was stopped");
}
}
else if (strncmp(command, "start_pstorage(", 15) == 0)
{
else if (strncmp(command, "start_pstorage(", 15) == 0) {
ServiceDefinition* def = services_find("pstorage");
if (def && copy_command_config(command + 15, command_buffer, BUFFER_MAX_SIZE) == 0) {
def->start(command_buffer);
}
}
else if (strncmp(command, "stop_pstorage()", 15) == 0)
{
else if (strncmp(command, "stop_pstorage()", 15) == 0) {
ServiceDefinition* def = services_find("pstorage");
if (def) {
def->stop();
}
}
else if (strncmp(command, "runtime_logs()", 14) == 0)
{
else if (strncmp(command, "runtime_logs()", 14) == 0) {
spdlog::debug("Issued runtime_logs() command");
std::string data = log_sink->data();
write(client_fd, data.c_str(), data.size());
return;
}
else if (strncmp(command, "exec_time()", 11) == 0)
{
else if (strncmp(command, "exec_time()", 11) == 0) {
time_t end_time;
time(&end_time);
int count_char = sprintf(command_buffer, "%llu\n", (unsigned long long)difftime(end_time, start_time));
write(client_fd, command_buffer, count_char);
return;
}
else
{
else {
int count_char = sprintf(command_buffer, "Error: unrecognized command\n");
write(client_fd, command_buffer, count_char);
return;
}
int count_char = sprintf(command_buffer, "OK\n");
write(client_fd, command_buffer, count_char);
}
@ -385,10 +373,10 @@ void* interactive_client_run(void* arguments) {
/// Run the interactive server for responding to control and measurement
/// requests through the socket API. This is the primary means for
/// controlling the runtime.
int8_t interactive_run(std::unique_ptr<istream, function<void(istream*)>>& cfg_stream,
const char* cfg_overrides,
const GlueVariablesBinding& bindings,
volatile bool& run) {
void interactive_run(oplc::config_stream& cfg_stream,
const char* cfg_overrides,
const GlueVariablesBinding& bindings,
volatile bool& run) {
const uint16_t port = 43628;
int socket_fd = interactive_open_socket(port);
@ -403,14 +391,14 @@ int8_t interactive_run(std::unique_ptr<istream, function<void(istream*)>>& cfg_s
}
pthread_t thread;
auto client_args = new ClientArgs { .client_fd=client_fd, .run=&run };
auto args = new ClientArgs { .client_fd = client_fd, .run = &run };
spdlog::trace("Interactive Server: Client accepted! Creating thread for the new client ID: {}", client_fd);
int ret = pthread_create(&thread, NULL, interactive_client_run, client_args);
int ret = pthread_create(&thread, NULL, interactive_client_run, args);
if (ret == 0) {
pthread_detach(thread);
} else {
delete client_args;
delete args;
}
}
@ -419,17 +407,11 @@ int8_t interactive_run(std::unique_ptr<istream, function<void(istream*)>>& cfg_s
void interactive_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;
});
auto cfg_stream = oplc::open_config();
interactive_run(cfg_stream, config, binding, run);
}
void initialize_logging(int argc,char **argv)
{
void initialize_logging(int argc, char **argv) {
log_sink.reset(new buffered_sink(log_buffer, LOG_BUFFER_SIZE));
spdlog::default_logger()->sinks().push_back(log_sink);
}

View File

@ -6,15 +6,15 @@
// 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_INTERACTIVE_SERVER_H_
#define CORE_INTERACTIVE_SERVER_H_
#ifndef RUNTIME_CORE_INTERACTIVE_SERVER_H_
#define RUNTIME_CORE_INTERACTIVE_SERVER_H_
/** \addtogroup openplc_runtime
* @{
@ -34,4 +34,4 @@ void interactive_service_run(const GlueVariablesBinding& binding,
/** @}*/
#endif // CORE_INTERACTIVE_SERVER_H_
#endif // RUNTIME_CORE_INTERACTIVE_SERVER_H_

View File

@ -5,7 +5,7 @@
// 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.
@ -26,9 +26,9 @@
* @{
*/
//Internal buffers for I/O and memory. These buffers are defined in the
//auto-generated glueVars.cpp file
#define BUFFER_SIZE 1024
// Internal buffers for I/O and memory. These buffers are defined in the
// auto-generated glueVars.cpp file
#define BUFFER_SIZE 1024
/*********************/
/* IEC Types defs */
/*********************/
@ -53,30 +53,30 @@ typedef uint64_t IEC_LWORD;
typedef float IEC_REAL;
typedef double IEC_LREAL;
//Booleans
// Booleans
extern IEC_BOOL *bool_input[BUFFER_SIZE][8];
extern IEC_BOOL *bool_output[BUFFER_SIZE][8];
//Bytes
// Bytes
extern IEC_BYTE *byte_input[BUFFER_SIZE];
extern IEC_BYTE *byte_output[BUFFER_SIZE];
//Analog I/O
// Analog I/O
extern IEC_UINT *int_input[BUFFER_SIZE];
extern IEC_UINT *int_output[BUFFER_SIZE];
//Memory
// Memory
extern IEC_UINT *int_memory[BUFFER_SIZE];
extern IEC_DINT *dint_memory[BUFFER_SIZE];
extern IEC_LINT *lint_memory[BUFFER_SIZE];
//Special Functions
// Special Functions
extern IEC_LINT *special_functions[BUFFER_SIZE];
//lock for the buffer
// lock for the buffer
extern std::mutex bufferLock;
//Common task timer
// Common task timer
extern unsigned long long common_ticktime__;
struct GlueVariable;
@ -88,25 +88,25 @@ extern const char OPLCGLUE_MD5_DIGEST[];
//----------------------------------------------------------------------
//FUNCTION PROTOTYPES
// FUNCTION PROTOTYPES
//----------------------------------------------------------------------
//MatIEC Compiler
// MatIEC Compiler
extern "C" {
void config_run__(unsigned long tick);
void config_init__(void);
}
//glueVars.cpp
// glueVars.cpp
void glueVars();
void updateTime();
//hardware_layer.cpp
// hardware_layer.cpp
void initializeHardware();
void finalizeHardware();
void updateBuffersIn();
void updateBuffersOut();
//custom_layer.h
// custom_layer.h
void initCustomLayer();
void updateCustomIn();
void updateCustomOut();
@ -115,31 +115,31 @@ extern int ignored_bool_outputs[];
extern int ignored_int_inputs[];
extern int ignored_int_outputs[];
//main.cpp
// main.cpp
void sleep_until(struct timespec *ts, int delay);
bool pinNotPresent(int *ignored_vector, int vector_size, int pinNumber);
extern uint8_t run_openplc;
void handleSpecialFunctions();
//server.cpp
// server.cpp
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);
// interactive_server.cpp
void initialize_logging(int argc, char **argv);
extern bool run_enip;
extern time_t start_time;
//enip.cpp
// enip.cpp
int processEnipMessage(unsigned char *buffer, int buffer_size, void* user_data);
//pccc.cpp ADDED Ulmer
// pccc.cpp ADDED Ulmer
uint16_t processPCCCMessage(unsigned char *buffer, int buffer_size);
//modbus_master.cpp
// modbus_master.cpp
void initializeMB();
void *querySlaveDevices(void *arg);
void updateBuffersIn_MB();

View File

@ -5,15 +5,15 @@
// 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_LOGSINK_H
#define CORE_LOGSINK_H
#ifndef RUNTIME_CORE_LOGSINK_H_
#define RUNTIME_CORE_LOGSINK_H_
#include <spdlog/sinks/base_sink.h>
#include <algorithm>
@ -33,31 +33,29 @@
/// use to query logs in memory so that they can be provided to a front
/// end in an efficient (but not necessarily complete) manner.
////////////////////////////////////////////////////////////////////////////////
class buffered_sink : public spdlog::sinks::base_sink <std::mutex>
{
public:
/// \brief Initialize a new instance of the sink with the provided buffer.
///
/// We require supplying the buffer as an argument so that in normal use
/// this can be statically allocated, but for the purpose of testing
/// the size is easy to configure.
/// \param buffer The buffer to write this. this sink maintains a mutex
/// will only read/write the buffer when the lock is acquired.
/// \param buffer_size The number of characters in the buffer.
buffered_sink(unsigned char* buffer, std::uint32_t buffer_size) :
buffer(buffer),
buffer_size(buffer_size)
{}
class buffered_sink : public spdlog::sinks::base_sink<std::mutex> {
public:
/// \brief Initialize a new instance of the sink with the provided buffer.
///
/// We require supplying the buffer as an argument so that in normal use
/// this can be statically allocated, but for the purpose of testing
/// the size is easy to configure.
/// \param buffer The buffer to write this. this sink maintains a mutex
/// will only read/write the buffer when the lock is acquired.
/// \param buffer_size The number of characters in the buffer.
buffered_sink(unsigned char* buffer, std::uint32_t buffer_size) :
buffer(buffer),
buffer_size(buffer_size)
{}
/// \brief Gets the data from this sink.
/// \return The data formatted as a string.
/// \return The data formatted as a string.
std::string data() {
return std::string(reinterpret_cast<char*>(this->buffer));
}
protected:
void sink_it_(const spdlog::details::log_msg& msg) override
{
protected:
void sink_it_(const spdlog::details::log_msg& msg) override {
fmt::memory_buffer formatted;
sink::formatter_->format(msg, formatted);
@ -72,28 +70,28 @@ protected:
}
// Add this message onto the buffer
std::size_t copy_len = std::min(msg_size, buffer_size - end_pos - 1);
std::size_t copy_len = std::min(msg_size, buffer_size - end_pos - 1);
std::memcpy(buffer + end_pos, formatted.data(), copy_len);
end_pos += copy_len;
// Just make sure that the string is null terminated so we don't run off the end.
end_pos += copy_len;
// Just make sure that the string is null terminated so we don't run
// off the end.
buffer[end_pos + 1] = '\0';
}
void flush_() override
{}
private:
// The current end of the string position. This is where we start inserting.
private:
// The current end of the string position. This is where we start inserting.
std::size_t end_pos = 0;
// The buffer that we are writing to.
// The buffer that we are writing to.
unsigned char* const buffer;
// The size of the buffer in characters.
const std::size_t buffer_size;;
// The size of the buffer in characters.
const std::size_t buffer_size;;
};
#endif // CORE_LOGSINK_H
/** @}*/
#endif // RUNTIME_CORE_LOGSINK_H_

View File

@ -5,7 +5,7 @@
// 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.
@ -41,14 +41,13 @@
#define OPLC_CYCLE 50000000
extern int opterr;
//extern int common_ticktime__;
IEC_BOOL __DEBUG;
IEC_LINT cycle_counter = 0;
unsigned long __tick = 0;
std::mutex bufferLock; //mutex for the internal buffers
uint8_t run_openplc = 1; //Variable to control OpenPLC Runtime execution
std::mutex bufferLock; // Mutex for the internal buffers
uint8_t run_openplc = 1; // Variable to control OpenPLC Runtime execution
////////////////////////////////////////////////////////////////////////////////
/// \brief Helper function - Makes the running thread sleep for the amount of
@ -56,18 +55,15 @@ uint8_t run_openplc = 1; //Variable to control OpenPLC Runtime execution
/// \param ts
/// \param delay in milliseconds
////////////////////////////////////////////////////////////////////////////////
void sleep_until(struct timespec *ts, int delay)
{
void sleep_until(struct timespec *ts, int delay) {
ts->tv_nsec += delay;
if(ts->tv_nsec >= 1000*1000*1000)
{
if (ts->tv_nsec >= 1000*1000*1000) {
ts->tv_nsec -= 1000*1000*1000;
ts->tv_sec++;
}
clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, ts, NULL);
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Verify if pin is present in one of the ignored vectors
/// \param
@ -75,40 +71,34 @@ void sleep_until(struct timespec *ts, int delay)
/// \param
/// \return
////////////////////////////////////////////////////////////////////////////////
bool pinNotPresent(int *ignored_vector, int vector_size, int pinNumber)
{
for (int i = 0; i < vector_size; i++)
{
if (ignored_vector[i] == pinNumber)
bool pinNotPresent(int *ignored_vector, int vector_size, int pinNumber) {
for (int i = 0; i < vector_size; i++) {
if (ignored_vector[i] == pinNumber) {
return false;
}
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Disables all outputs
////////////////////////////////////////////////////////////////////////////////
void disableOutputs()
{
//Disable digital outputs
for (int i = 0; i < BUFFER_SIZE; i++)
{
for (int j = 0; j < 8; j++)
{
void disableOutputs() {
// Disable digital outputs
for (int i = 0; i < BUFFER_SIZE; i++) {
for (int j = 0; j < 8; j++) {
if (bool_output[i][j] != NULL) *bool_output[i][j] = 0;
}
}
//Disable byte outputs
for (int i = 0; i < BUFFER_SIZE; i++)
{
// Disable byte outputs
for (int i = 0; i < BUFFER_SIZE; i++) {
if (byte_output[i] != NULL) *byte_output[i] = 0;
}
//Disable analog outputs
for (int i = 0; i < BUFFER_SIZE; i++)
{
// Disable analog outputs
for (int i = 0; i < BUFFER_SIZE; i++) {
if (int_output[i] != NULL) *int_output[i] = 0;
}
}
@ -116,33 +106,34 @@ void disableOutputs()
////////////////////////////////////////////////////////////////////////////////
/// \brief Special Functions
////////////////////////////////////////////////////////////////////////////////
void handleSpecialFunctions()
{
//current time [%ML1024]
void handleSpecialFunctions() {
// Current time [%ML1024]
struct tm *current_time;
time_t rawtime;
tzset();
time(&rawtime);
current_time = localtime(&rawtime);
rawtime = rawtime - timezone;
if (current_time->tm_isdst > 0) rawtime = rawtime + 3600;
if (special_functions[0] != NULL) *special_functions[0] = rawtime;
//number of cycles [%ML1025]
cycle_counter++;
if (special_functions[1] != NULL) *special_functions[1] = cycle_counter;
//comm error counter [%ML1026]
/* Implemented in modbus_master.cpp */
//insert other special functions below
rawtime = rawtime - timezone;
if (current_time->tm_isdst > 0) {
rawtime = rawtime + 3600;
}
if (special_functions[0] != NULL) {
*special_functions[0] = rawtime;
}
// Number of cycles [%ML1025]
cycle_counter++;
if (special_functions[1] != NULL) {
*special_functions[1] = cycle_counter;
}
// Insert other special functions below
}
int main(int argc,char **argv)
{
int main(int argc, char **argv) {
initialize_logging(argc, argv);
spdlog::info("OpenPLC Runtime starting...");
@ -153,7 +144,7 @@ int main(int argc,char **argv)
// automatically start
bootstrap();
//gets the starting point for the clock
// Gets the starting point for the clock
spdlog::debug("Getting current time");
struct timespec timer_start;
clock_gettime(CLOCK_MONOTONIC, &timer_start);
@ -161,31 +152,33 @@ int main(int argc,char **argv)
//======================================================
// MAIN LOOP
//======================================================
while(run_openplc)
{
//make sure the buffer pointers are correct and
//attached to the user variables
glueVars();
updateBuffersIn(); //read input image
while (run_openplc) {
// Read input image - this method tries to get the lock
// so don't put it in the lock context.
updateBuffersIn();
{
std::lock_guard<std::mutex> guard(bufferLock);
updateCustomIn();
updateBuffersIn_MB(); //update input image table with data from slave devices
// Update input image table with data from slave devices
updateBuffersIn_MB();
handleSpecialFunctions();
config_run__(__tick++); // execute plc program logic
// Execute plc program logic
config_run__(__tick++);
updateCustomOut();
updateBuffersOut_MB(); //update slave devices with data from the output image table
// Update slave devices with data from the output image table
updateBuffersOut_MB();
}
updateBuffersOut(); //write output image
// Write output image - this method tries to get the lock
// so don't put it in the lock context.
updateBuffersOut();
updateTime();
sleep_until(&timer_start, common_ticktime__);
}
//======================================================
// SHUTTING DOWN OPENPLC RUNTIME
//======================================================

View File

@ -42,9 +42,9 @@
* @{
*/
#define MB_TCP 1
#define MB_RTU 2
#define MAX_MB_IO 400
#define MB_TCP 1
#define MB_RTU 2
#define MAX_MB_IO 400
using namespace std;
@ -57,29 +57,29 @@ pthread_mutex_t ioLock;
struct MB_address
{
uint16_t start_address;
uint16_t num_regs;
uint16_t start_address;
uint16_t num_regs;
};
struct MB_device
{
modbus_t *mb_ctx;
char dev_name[100];
uint8_t protocol;
char dev_address[100];
uint16_t ip_port;
int rtu_baud;
char rtu_parity;
int rtu_data_bit;
int rtu_stop_bit;
uint8_t dev_id;
bool isConnected;
modbus_t *mb_ctx;
char dev_name[100];
uint8_t protocol;
char dev_address[100];
uint16_t ip_port;
int rtu_baud;
char rtu_parity;
int rtu_data_bit;
int rtu_stop_bit;
uint8_t dev_id;
bool isConnected;
struct MB_address discrete_inputs;
struct MB_address coils;
struct MB_address input_registers;
struct MB_address holding_read_registers;
struct MB_address holding_registers;
struct MB_address discrete_inputs;
struct MB_address coils;
struct MB_address input_registers;
struct MB_address holding_read_registers;
struct MB_address holding_registers;
};
struct MB_device *mb_devices;
@ -96,22 +96,22 @@ uint16_t timeout = 1000;
////////////////////////////////////////////////////////////////////////////////
void getData(char *line, char *buf, char separator1, char separator2)
{
int i=0, j=0;
buf[j] = '\0';
int i=0, j=0;
buf[j] = '\0';
while (line[i] != separator1 && line[i] != '\0')
{
i++;
}
i++;
while (line[i] != separator1 && line[i] != '\0')
{
i++;
}
i++;
while (line[i] != separator2 && line[i] != '\0')
{
buf[j] = line[i];
i++;
j++;
buf[j] = '\0';
}
while (line[i] != separator2 && line[i] != '\0')
{
buf[j] = line[i];
i++;
j++;
buf[j] = '\0';
}
}
////////////////////////////////////////////////////////////////////////////////
@ -121,18 +121,18 @@ void getData(char *line, char *buf, char separator1, char separator2)
////////////////////////////////////////////////////////////////////////////////
int getDeviceNumber(char *line)
{
char temp[5];
int i = 0, j = 6;
char temp[5];
int i = 0, j = 6;
while (line[j] != '.')
{
temp[i] = line[j];
i++;
j++;
temp[i] = '\0';
}
while (line[j] != '.')
{
temp[i] = line[j];
i++;
j++;
temp[i] = '\0';
}
return(atoi(temp));
return(atoi(temp));
}
////////////////////////////////////////////////////////////////////////////////
@ -142,21 +142,21 @@ int getDeviceNumber(char *line)
////////////////////////////////////////////////////////////////////////////////
void getFunction(char *line, char *parameter)
{
int i = 0, j = 0;
int i = 0, j = 0;
while (line[j] != '.')
{
j++;
}
j++;
while (line[j] != '.')
{
j++;
}
j++;
while (line[j] != ' ' && line[j] != '=' && line[j] != '(')
{
parameter[i] = line[j];
i++;
j++;
parameter[i] = '\0';
}
while (line[j] != ' ' && line[j] != '=' && line[j] != '(')
{
parameter[i] = line[j];
i++;
j++;
parameter[i] = '\0';
}
}
////////////////////////////////////////////////////////////////////////////////
@ -164,190 +164,190 @@ void getFunction(char *line, char *parameter)
////////////////////////////////////////////////////////////////////////////////
void parseConfig()
{
string line;
char line_str[1024];
ifstream cfgfile("mbconfig.cfg");
string line;
char line_str[1024];
ifstream cfgfile("mbconfig.cfg");
if (cfgfile.is_open())
{
while (getline(cfgfile, line))
{
strncpy(line_str, line.c_str(), 1024);
if (line_str[0] != '#' && strlen(line_str) > 1)
{
if (!strncmp(line_str, "Num_Devices", 11))
{
char temp_buffer[5];
getData(line_str, temp_buffer, '"', '"');
num_devices = atoi(temp_buffer);
mb_devices = (struct MB_device *)malloc(num_devices*sizeof(struct MB_device));
}
if (cfgfile.is_open())
{
while (getline(cfgfile, line))
{
strncpy(line_str, line.c_str(), 1024);
if (line_str[0] != '#' && strlen(line_str) > 1)
{
if (!strncmp(line_str, "Num_Devices", 11))
{
char temp_buffer[5];
getData(line_str, temp_buffer, '"', '"');
num_devices = atoi(temp_buffer);
mb_devices = (struct MB_device *)malloc(num_devices*sizeof(struct MB_device));
}
else if (!strncmp(line_str, "Polling_Period", 14))
{
{
char temp_buffer[10];
getData(line_str, temp_buffer, '"', '"');
polling_period = atoi(temp_buffer);
getData(line_str, temp_buffer, '"', '"');
polling_period = atoi(temp_buffer);
}
else if (!strncmp(line_str, "Timeout", 7))
{
{
char temp_buffer[10];
getData(line_str, temp_buffer, '"', '"');
timeout = atoi(temp_buffer);
getData(line_str, temp_buffer, '"', '"');
timeout = atoi(temp_buffer);
}
else if (!strncmp(line_str, "device", 6))
{
int deviceNumber = getDeviceNumber(line_str);
char functionType[100];
getFunction(line_str, functionType);
else if (!strncmp(line_str, "device", 6))
{
int deviceNumber = getDeviceNumber(line_str);
char functionType[100];
getFunction(line_str, functionType);
if (!strncmp(functionType, "name", 4))
{
getData(line_str, mb_devices[deviceNumber].dev_name, '"', '"');
}
else if (!strncmp(functionType, "protocol", 8))
{
char temp_buffer[5];
getData(line_str, temp_buffer, '"', '"');
if (!strncmp(functionType, "name", 4))
{
getData(line_str, mb_devices[deviceNumber].dev_name, '"', '"');
}
else if (!strncmp(functionType, "protocol", 8))
{
char temp_buffer[5];
getData(line_str, temp_buffer, '"', '"');
if (!strncmp(temp_buffer, "TCP", 3))
mb_devices[deviceNumber].protocol = MB_TCP;
else if (!strncmp(temp_buffer, "RTU", 3))
mb_devices[deviceNumber].protocol = MB_RTU;
}
else if (!strncmp(functionType, "slave_id", 8))
{
char temp_buffer[5];
getData(line_str, temp_buffer, '"', '"');
mb_devices[deviceNumber].dev_id = atoi(temp_buffer);
}
else if (!strncmp(functionType, "address", 7))
{
getData(line_str, mb_devices[deviceNumber].dev_address, '"', '"');
}
else if (!strncmp(functionType, "IP_Port", 7))
{
char temp_buffer[6];
getData(line_str, temp_buffer, '"', '"');
mb_devices[deviceNumber].ip_port = atoi(temp_buffer);
}
else if (!strncmp(functionType, "RTU_Baud_Rate", 13))
{
char temp_buffer[10];
getData(line_str, temp_buffer, '"', '"');
mb_devices[deviceNumber].rtu_baud = atoi(temp_buffer);
}
else if (!strncmp(functionType, "RTU_Parity", 10))
{
char temp_buffer[3];
getData(line_str, temp_buffer, '"', '"');
mb_devices[deviceNumber].rtu_parity = temp_buffer[0];
}
else if (!strncmp(functionType, "RTU_Data_Bits", 13))
{
char temp_buffer[6];
getData(line_str, temp_buffer, '"', '"');
mb_devices[deviceNumber].rtu_data_bit = atoi(temp_buffer);
}
else if (!strncmp(functionType, "RTU_Stop_Bits", 13))
{
char temp_buffer[20];
getData(line_str, temp_buffer, '"', '"');
mb_devices[deviceNumber].rtu_stop_bit = atoi(temp_buffer);
}
else if (!strncmp(functionType, "Discrete_Inputs_Start", 21))
{
char temp_buffer[10];
getData(line_str, temp_buffer, '"', '"');
mb_devices[deviceNumber].discrete_inputs.start_address = atoi(temp_buffer);
}
else if (!strncmp(functionType, "Discrete_Inputs_Size", 20))
{
char temp_buffer[10];
getData(line_str, temp_buffer, '"', '"');
mb_devices[deviceNumber].discrete_inputs.num_regs = atoi(temp_buffer);
}
else if (!strncmp(functionType, "Coils_Start", 11))
{
char temp_buffer[10];
getData(line_str, temp_buffer, '"', '"');
mb_devices[deviceNumber].coils.start_address = atoi(temp_buffer);
}
else if (!strncmp(functionType, "Coils_Size", 10))
{
char temp_buffer[10];
getData(line_str, temp_buffer, '"', '"');
mb_devices[deviceNumber].coils.num_regs = atoi(temp_buffer);
}
else if (!strncmp(functionType, "Input_Registers_Start", 21))
{
char temp_buffer[10];
getData(line_str, temp_buffer, '"', '"');
mb_devices[deviceNumber].input_registers.start_address = atoi(temp_buffer);
}
else if (!strncmp(functionType, "Input_Registers_Size", 20))
{
char temp_buffer[10];
getData(line_str, temp_buffer, '"', '"');
mb_devices[deviceNumber].input_registers.num_regs = atoi(temp_buffer);
}
else if (!strncmp(functionType, "Holding_Registers_Read_Start", 28))
{
char temp_buffer[10];
getData(line_str, temp_buffer, '"', '"');
mb_devices[deviceNumber].holding_read_registers.start_address = atoi(temp_buffer);
}
else if (!strncmp(functionType, "Holding_Registers_Read_Size", 27))
{
char temp_buffer[10];
getData(line_str, temp_buffer, '"', '"');
mb_devices[deviceNumber].holding_read_registers.num_regs = atoi(temp_buffer);
}
else if (!strncmp(functionType, "Holding_Registers_Start", 23))
{
char temp_buffer[10];
getData(line_str, temp_buffer, '"', '"');
mb_devices[deviceNumber].holding_registers.start_address = atoi(temp_buffer);
}
else if (!strncmp(functionType, "Holding_Registers_Size", 22))
{
char temp_buffer[10];
getData(line_str, temp_buffer, '"', '"');
mb_devices[deviceNumber].holding_registers.num_regs = atoi(temp_buffer);
}
}
}
}
}
if (!strncmp(temp_buffer, "TCP", 3))
mb_devices[deviceNumber].protocol = MB_TCP;
else if (!strncmp(temp_buffer, "RTU", 3))
mb_devices[deviceNumber].protocol = MB_RTU;
}
else if (!strncmp(functionType, "slave_id", 8))
{
char temp_buffer[5];
getData(line_str, temp_buffer, '"', '"');
mb_devices[deviceNumber].dev_id = atoi(temp_buffer);
}
else if (!strncmp(functionType, "address", 7))
{
getData(line_str, mb_devices[deviceNumber].dev_address, '"', '"');
}
else if (!strncmp(functionType, "IP_Port", 7))
{
char temp_buffer[6];
getData(line_str, temp_buffer, '"', '"');
mb_devices[deviceNumber].ip_port = atoi(temp_buffer);
}
else if (!strncmp(functionType, "RTU_Baud_Rate", 13))
{
char temp_buffer[10];
getData(line_str, temp_buffer, '"', '"');
mb_devices[deviceNumber].rtu_baud = atoi(temp_buffer);
}
else if (!strncmp(functionType, "RTU_Parity", 10))
{
char temp_buffer[3];
getData(line_str, temp_buffer, '"', '"');
mb_devices[deviceNumber].rtu_parity = temp_buffer[0];
}
else if (!strncmp(functionType, "RTU_Data_Bits", 13))
{
char temp_buffer[6];
getData(line_str, temp_buffer, '"', '"');
mb_devices[deviceNumber].rtu_data_bit = atoi(temp_buffer);
}
else if (!strncmp(functionType, "RTU_Stop_Bits", 13))
{
char temp_buffer[20];
getData(line_str, temp_buffer, '"', '"');
mb_devices[deviceNumber].rtu_stop_bit = atoi(temp_buffer);
}
else if (!strncmp(functionType, "Discrete_Inputs_Start", 21))
{
char temp_buffer[10];
getData(line_str, temp_buffer, '"', '"');
mb_devices[deviceNumber].discrete_inputs.start_address = atoi(temp_buffer);
}
else if (!strncmp(functionType, "Discrete_Inputs_Size", 20))
{
char temp_buffer[10];
getData(line_str, temp_buffer, '"', '"');
mb_devices[deviceNumber].discrete_inputs.num_regs = atoi(temp_buffer);
}
else if (!strncmp(functionType, "Coils_Start", 11))
{
char temp_buffer[10];
getData(line_str, temp_buffer, '"', '"');
mb_devices[deviceNumber].coils.start_address = atoi(temp_buffer);
}
else if (!strncmp(functionType, "Coils_Size", 10))
{
char temp_buffer[10];
getData(line_str, temp_buffer, '"', '"');
mb_devices[deviceNumber].coils.num_regs = atoi(temp_buffer);
}
else if (!strncmp(functionType, "Input_Registers_Start", 21))
{
char temp_buffer[10];
getData(line_str, temp_buffer, '"', '"');
mb_devices[deviceNumber].input_registers.start_address = atoi(temp_buffer);
}
else if (!strncmp(functionType, "Input_Registers_Size", 20))
{
char temp_buffer[10];
getData(line_str, temp_buffer, '"', '"');
mb_devices[deviceNumber].input_registers.num_regs = atoi(temp_buffer);
}
else if (!strncmp(functionType, "Holding_Registers_Read_Start", 28))
{
char temp_buffer[10];
getData(line_str, temp_buffer, '"', '"');
mb_devices[deviceNumber].holding_read_registers.start_address = atoi(temp_buffer);
}
else if (!strncmp(functionType, "Holding_Registers_Read_Size", 27))
{
char temp_buffer[10];
getData(line_str, temp_buffer, '"', '"');
mb_devices[deviceNumber].holding_read_registers.num_regs = atoi(temp_buffer);
}
else if (!strncmp(functionType, "Holding_Registers_Start", 23))
{
char temp_buffer[10];
getData(line_str, temp_buffer, '"', '"');
mb_devices[deviceNumber].holding_registers.start_address = atoi(temp_buffer);
}
else if (!strncmp(functionType, "Holding_Registers_Size", 22))
{
char temp_buffer[10];
getData(line_str, temp_buffer, '"', '"');
mb_devices[deviceNumber].holding_registers.num_regs = atoi(temp_buffer);
}
}
}
}
}
else
{
spdlog::info("Skipping configuration of Slave Devices (mbconfig.cfg file not found)");
spdlog::info("Skipping configuration of Slave Devices (mbconfig.cfg file not found)");
}
//Parser Debug
///*
for (int i = 0; i < num_devices; i++)
{
printf("Device %d\n", i);
printf("Name: %s\n", mb_devices[i].dev_name);
printf("Protocol: %d\n", mb_devices[i].protocol);
printf("Address: %s\n", mb_devices[i].dev_address);
printf("IP Port: %d\n", mb_devices[i].ip_port);
printf("Baud rate: %d\n", mb_devices[i].rtu_baud);
printf("Parity: %c\n", mb_devices[i].rtu_parity);
printf("Data Bits: %d\n", mb_devices[i].rtu_data_bit);
printf("Stop Bits: %d\n", mb_devices[i].rtu_stop_bit);
printf("DI Start: %d\n", mb_devices[i].discrete_inputs.start_address);
printf("DI Size: %d\n", mb_devices[i].discrete_inputs.num_regs);
printf("Coils Start: %d\n", mb_devices[i].coils.start_address);
printf("Coils Size: %d\n", mb_devices[i].coils.num_regs);
printf("IR Start: %d\n", mb_devices[i].input_registers.start_address);
printf("IR Size: %d\n", mb_devices[i].input_registers.num_regs);
printf("HR Start: %d\n", mb_devices[i].holding_registers.start_address);
printf("HR Size: %d\n", mb_devices[i].holding_registers.num_regs);
printf("\n\n");
}
//*/
//Parser Debug
///*
for (int i = 0; i < num_devices; i++)
{
printf("Device %d\n", i);
printf("Name: %s\n", mb_devices[i].dev_name);
printf("Protocol: %d\n", mb_devices[i].protocol);
printf("Address: %s\n", mb_devices[i].dev_address);
printf("IP Port: %d\n", mb_devices[i].ip_port);
printf("Baud rate: %d\n", mb_devices[i].rtu_baud);
printf("Parity: %c\n", mb_devices[i].rtu_parity);
printf("Data Bits: %d\n", mb_devices[i].rtu_data_bit);
printf("Stop Bits: %d\n", mb_devices[i].rtu_stop_bit);
printf("DI Start: %d\n", mb_devices[i].discrete_inputs.start_address);
printf("DI Size: %d\n", mb_devices[i].discrete_inputs.num_regs);
printf("Coils Start: %d\n", mb_devices[i].coils.start_address);
printf("Coils Size: %d\n", mb_devices[i].coils.num_regs);
printf("IR Start: %d\n", mb_devices[i].input_registers.start_address);
printf("IR Size: %d\n", mb_devices[i].input_registers.num_regs);
printf("HR Start: %d\n", mb_devices[i].holding_registers.start_address);
printf("HR Size: %d\n", mb_devices[i].holding_registers.num_regs);
printf("\n\n");
}
//*/
}
////////////////////////////////////////////////////////////////////////////////
@ -367,10 +367,10 @@ void *querySlaveDevices(void *arg)
//Verify if device is connected
if (!mb_devices[i].isConnected)
{
spdlog::info("Device {} is disconnected. Attempting to reconnect...", mb_devices[i].dev_name);
spdlog::info("Device {} is disconnected. Attempting to reconnect...", mb_devices[i].dev_name);
if (modbus_connect(mb_devices[i].mb_ctx) == -1)
{
spdlog::error("Connection failed on MB device {}: {}", mb_devices[i].dev_name, modbus_strerror(errno));
spdlog::error("Connection failed on MB device {}: {}", mb_devices[i].dev_name, modbus_strerror(errno));
if (special_functions[2] != NULL) *special_functions[2]++;
@ -383,7 +383,7 @@ void *querySlaveDevices(void *arg)
}
else
{
spdlog::info("Connected to MB device {}", mb_devices[i].dev_name);
spdlog::info("Connected to MB device {}", mb_devices[i].dev_name);
mb_devices[i].isConnected = true;
}
}
@ -410,7 +410,7 @@ void *querySlaveDevices(void *arg)
mb_devices[i].isConnected = false;
}
spdlog::info("Modbus Read Discrete Input Registers failed on MB device {}: {}", mb_devices[i].dev_name, modbus_strerror(errno));
spdlog::info("Modbus Read Discrete Input Registers failed on MB device {}: {}", mb_devices[i].dev_name, modbus_strerror(errno));
bool_input_index += (mb_devices[i].discrete_inputs.num_regs);
if (special_functions[2] != NULL) *special_functions[2]++;
}
@ -465,7 +465,7 @@ void *querySlaveDevices(void *arg)
uint16_t *tempBuff;
tempBuff = (uint16_t *)malloc(2*mb_devices[i].input_registers.num_regs);
nanosleep(&ts, NULL);
int return_val = modbus_read_input_registers( mb_devices[i].mb_ctx, mb_devices[i].input_registers.start_address,
int return_val = modbus_read_input_registers( mb_devices[i].mb_ctx, mb_devices[i].input_registers.start_address,
mb_devices[i].input_registers.num_regs, tempBuff);
if (return_val == -1)
{
@ -475,7 +475,7 @@ void *querySlaveDevices(void *arg)
mb_devices[i].isConnected = false;
}
spdlog::error("Modbus Read Input Registers failed on MB device {}: {}", mb_devices[i].dev_name, modbus_strerror(errno));
spdlog::error("Modbus Read Input Registers failed on MB device {}: {}", mb_devices[i].dev_name, modbus_strerror(errno));
int_input_index += (mb_devices[i].input_registers.num_regs);
if (special_functions[2] != NULL) *special_functions[2]++;
}
@ -508,7 +508,7 @@ void *querySlaveDevices(void *arg)
modbus_close(mb_devices[i].mb_ctx);
mb_devices[i].isConnected = false;
}
spdlog::error("Modbus Read Holding Registers failed on MB device {}: {}", mb_devices[i].dev_name, modbus_strerror(errno));
spdlog::error("Modbus Read Holding Registers failed on MB device {}: {}", mb_devices[i].dev_name, modbus_strerror(errno));
int_input_index += (mb_devices[i].holding_read_registers.num_regs);
if (special_functions[2] != NULL) *special_functions[2]++;
}
@ -551,7 +551,7 @@ void *querySlaveDevices(void *arg)
mb_devices[i].isConnected = false;
}
spdlog::error("Modbus Write Holding Registers failed on MB device {}: {}", mb_devices[i].dev_name, modbus_strerror(errno));
spdlog::error("Modbus Write Holding Registers failed on MB device {}: {}", mb_devices[i].dev_name, modbus_strerror(errno));
if (special_functions[2] != NULL) *special_functions[2]++;
}
@ -562,7 +562,7 @@ void *querySlaveDevices(void *arg)
this_thread::sleep_for(chrono::milliseconds(polling_period));
}
return 0;
return 0;
}
////////////////////////////////////////////////////////////////////////////////
@ -571,29 +571,29 @@ void *querySlaveDevices(void *arg)
////////////////////////////////////////////////////////////////////////////////
void initializeMB()
{
parseConfig();
parseConfig();
for (int i = 0; i < num_devices; i++)
{
if (mb_devices[i].protocol == MB_TCP)
{
mb_devices[i].mb_ctx = modbus_new_tcp(mb_devices[i].dev_address, mb_devices[i].ip_port);
}
else if (mb_devices[i].protocol == MB_RTU)
{
mb_devices[i].mb_ctx = modbus_new_rtu( mb_devices[i].dev_address, mb_devices[i].rtu_baud,
mb_devices[i].rtu_parity, mb_devices[i].rtu_data_bit,
mb_devices[i].rtu_stop_bit);
}
for (int i = 0; i < num_devices; i++)
{
if (mb_devices[i].protocol == MB_TCP)
{
mb_devices[i].mb_ctx = modbus_new_tcp(mb_devices[i].dev_address, mb_devices[i].ip_port);
}
else if (mb_devices[i].protocol == MB_RTU)
{
mb_devices[i].mb_ctx = modbus_new_rtu( mb_devices[i].dev_address, mb_devices[i].rtu_baud,
mb_devices[i].rtu_parity, mb_devices[i].rtu_data_bit,
mb_devices[i].rtu_stop_bit);
}
//slave id
modbus_set_slave(mb_devices[i].mb_ctx, mb_devices[i].dev_id);
modbus_set_slave(mb_devices[i].mb_ctx, mb_devices[i].dev_id);
//timeout
uint32_t to_sec = timeout / 1000;
uint32_t to_usec = (timeout % 1000) * 1000;
modbus_set_response_timeout(mb_devices[i].mb_ctx, to_sec, to_usec);
}
}
//Initialize comm error counter
if (special_functions[2] != NULL) *special_functions[2] = 0;
@ -615,15 +615,15 @@ void initializeMB()
////////////////////////////////////////////////////////////////////////////////
void updateBuffersIn_MB()
{
pthread_mutex_lock(&ioLock);
pthread_mutex_lock(&ioLock);
for (int i = 0; i < MAX_MB_IO; i++)
{
if (bool_input[100+(i/8)][i%8] != NULL) *bool_input[100+(i/8)][i%8] = bool_input_buf[i];
if (int_input[100+i] != NULL) *int_input[100+i] = int_input_buf[i];
}
for (int i = 0; i < MAX_MB_IO; i++)
{
if (bool_input[100+(i/8)][i%8] != NULL) *bool_input[100+(i/8)][i%8] = bool_input_buf[i];
if (int_input[100+i] != NULL) *int_input[100+i] = int_input_buf[i];
}
pthread_mutex_unlock(&ioLock);
pthread_mutex_unlock(&ioLock);
}
@ -633,15 +633,15 @@ void updateBuffersIn_MB()
////////////////////////////////////////////////////////////////////////////////
void updateBuffersOut_MB()
{
pthread_mutex_lock(&ioLock);
pthread_mutex_lock(&ioLock);
for (int i = 0; i < MAX_MB_IO; i++)
{
if (bool_output[100+(i/8)][i%8] != NULL) bool_output_buf[i] = *bool_output[100+(i/8)][i%8];
if (int_output[100+i] != NULL) int_output_buf[i] = *int_output[100+i];
}
for (int i = 0; i < MAX_MB_IO; i++)
{
if (bool_output[100+(i/8)][i%8] != NULL) bool_output_buf[i] = *bool_output[100+(i/8)][i%8];
if (int_output[100+i] != NULL) int_output_buf[i] = *int_output[100+i];
}
pthread_mutex_unlock(&ioLock);
pthread_mutex_unlock(&ioLock);
}
/** @}*/

View File

@ -74,7 +74,7 @@ IndexedStrategy::IndexedStrategy(const GlueVariablesBinding& bindings) :
// 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,
// Allocate 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);
@ -143,10 +143,11 @@ void exchange(array<PendingValue<T>, NUM_REGISTER_VALUES>& write_buffer,
continue;
}
// If there was a write that hasn't be written, then transfer the
// If there was a write that hasn't been written, then transfer the
// value to the read buffer.
if (write_buffer[index].has_pending) {
*read_buffer[index].value = write_buffer[index].value;
write_buffer[index].has_pending = false;
}
// Finally, update our cached value so that we can read the value
@ -163,10 +164,11 @@ void IndexedStrategy::Exchange() {
// 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
// Only the coils can be set, so we only need 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_write_buffer[index].has_pending = false;
*coil_read_buffer[index].value = coil_write_buffer[index].value;
}
coil_read_buffer[index].update_cache();

View File

@ -13,8 +13,8 @@
// 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_
#ifndef RUNTIME_CORE_MODBUSSLAVE_MB_UTIL_H_
#define RUNTIME_CORE_MODBUSSLAVE_MB_UTIL_H_
#include <cstdint>
@ -37,4 +37,4 @@ inline std::uint8_t mb_high_byte(std::uint16_t w) {
/** @}*/
#endif // CORE_MODBUSSLAVE_MB_UTIL_H_
#endif // RUNTIME_CORE_MODBUSSLAVE_MB_UTIL_H_

View File

@ -412,13 +412,13 @@ int modbus_slave_cfg_handler(void* user_data, const char* section,
return 0;
}
int8_t modbus_slave_run(std::unique_ptr<std::istream, std::function<void(std::istream*)>>& cfg_stream,
int8_t modbus_slave_run(oplc::config_stream& 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);
ini_parse_stream(oplc::istream_fgets, cfg_stream.get(), modbus_slave_cfg_handler, &config);
cfg_stream.reset(nullptr);
@ -449,12 +449,7 @@ int8_t modbus_slave_run(std::unique_ptr<std::istream, std::function<void(std::is
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;
});
auto cfg_stream = oplc::open_config();
modbus_slave_run(cfg_stream, config, binding, run);
}

View File

@ -13,8 +13,8 @@
// See the License for the specific language governing permissionsand
// limitations under the License.
#ifndef CORE_MODBUSSLAVE_SLAVE_H_
#define CORE_MODBUSSLAVE_SLAVE_H_
#ifndef RUNTIME_CORE_MODBUSSLAVE_SLAVE_H_
#define RUNTIME_CORE_MODBUSSLAVE_SLAVE_H_
/** \addtogroup openplc_runtime
* @{
@ -38,4 +38,4 @@ void modbus_slave_service_run(const GlueVariablesBinding& binding,
/** @}*/
#endif // CORE_MODBUSSLAVE_SLAVE_H_
#endif // RUNTIME_CORE_MODBUSSLAVE_SLAVE_H_

View File

@ -46,15 +46,15 @@
#define MAX_32B_RANGE 2047 //Holding Register Size 32bit (memory)
/*------------File Type for PCCC--------------*/
#define PCCC_INPUT_LOGICAL_SLOT 0x8c
#define PCCC_OUTPUT_LOGICAL_SLOT 0x8b
#define PCCC_INTEGER 0x89
#define PCCC_FLOATING_POINT 0x8A
#define PCCC_INPUT_LOGICAL_SLOT 0x8c
#define PCCC_OUTPUT_LOGICAL_SLOT 0x8b
#define PCCC_INTEGER 0x89
#define PCCC_FLOATING_POINT 0x8A
#define PCCC_FN_OUTPUT 0x00
#define PCCC_FN_INPUT 0x01
#define PCCC_FN_INT 0x07
#define PCCC_FN_FLOAT 0x08
#define PCCC_FN_OUTPUT 0x00
#define PCCC_FN_INPUT 0x01
#define PCCC_FN_INT 0x07
#define PCCC_FN_FLOAT 0x08
/*----------------Define functions for bit/byte operations-------------------*/
#define bitRead(value, bit) (((value) >> (bit)) & 0x01)
@ -78,30 +78,30 @@ using namespace std;
//-----------------------------------------------------------Structure Defines--------------------------------------------------//
struct pccc_header //Structure for the Header Information for EthernetIP
{
unsigned char *Data;
unsigned char *Data_Size;
unsigned char *Data;
unsigned char *Data_Size;
unsigned char *HD_length = 5;//[5] -> Typical Header Length for Command; Response Header Length is 4
unsigned char *HD_CMD_Code;//[1] -> Command Code
unsigned char *HD_CMD_Code;//[1] -> Command Code
unsigned char *HD_Status;//[1] -> Status Code
unsigned char *HD_TransactionNum;//[2] -> Transaction Number
unsigned char *HD_Data_Function_Code;//[1] -> Function code MSB
unsigned char *HD_Ext_Status; //Ext Status -> only appended if Status = 0x0f[1]
unsigned char *HD_Data_Function_Code;//[1] -> Function code MSB
unsigned char *HD_Ext_Status; //Ext Status -> only appended if Status = 0x0f[1]
unsigned char resp_cod_hex = 0x4f; //Response Hex Value
unsigned char *RP_CMD_Code = &resp_cod_hex;//[1] -> Reply Command Code = 0x4f
unsigned char resp_cod_hex = 0x4f; //Response Hex Value
unsigned char *RP_CMD_Code = &resp_cod_hex;//[1] -> Reply Command Code = 0x4f
};
struct protected_logical_read_command //Struct for Reply and Command values of Read
{
unsigned char *CMD_Byte_Size;//[1]
unsigned char *RP_EXT_Status;//[1]* -> Ext Status -> only appended if Status = 0x0f[1]
{
unsigned char *CMD_Byte_Size;//[1]
unsigned char *RP_EXT_Status;//[1]* -> Ext Status -> only appended if Status = 0x0f[1]
};
struct protected_logical_write_command
{
unsigned char *CMD_Byte_Size;//[1]*
unsigned char *RP_EXT_Status;//[1]* -> Ext Status -> only appended if Status = 0x0f[1]
{
unsigned char *CMD_Byte_Size;//[1]*
unsigned char *RP_EXT_Status;//[1]* -> Ext Status -> only appended if Status = 0x0f[1]
};
//--------------------------------------------------------------------------------------------------------------------------------------//
@ -127,148 +127,148 @@ int an_word_pccc(unsigned char byte1, unsigned char byte2);
//This function takes in the data from enip.cpp and places the data in the appropriate structure variables
uint16_t processPCCCMessage(unsigned char *buffer, int buffer_size)
{
/* Variables */
int new_pccc_length; //New PCCC Length
pccc_header header;
header.Data = buffer;
header.Data_Size = buffer_size;
/*Determine the new pccc length*/
new_pccc_length = ParsePCCCData(buffer,buffer_size);
return new_pccc_length; //Return the length to enip.cpp
/* Variables */
int new_pccc_length; //New PCCC Length
pccc_header header;
header.Data = buffer;
header.Data_Size = buffer_size;
/*Determine the new pccc length*/
new_pccc_length = ParsePCCCData(buffer,buffer_size);
return new_pccc_length; //Return the length to enip.cpp
}
uint16_t ParsePCCCData(unsigned char *buffer, int buffer_size)
{
/*Variables*/
int new_pccc_length; //Variable for new PCCC length
pccc_header header;
header.HD_CMD_Code = &buffer[0];//[1] -> Command Code
header.HD_Status = &buffer[1];////[1] -> Status Code
header.HD_TransactionNum = &buffer[2];//[2] -> Transaction Number
header.HD_Data_Function_Code = &buffer[4];//[1] -> Data Function Code
/*Determine what command is being requested*/
new_pccc_length = Command_Protocol(header,buffer,buffer_size);
return new_pccc_length; //Return the new pccc length
{
/*Variables*/
int new_pccc_length; //Variable for new PCCC length
pccc_header header;
header.HD_CMD_Code = &buffer[0];//[1] -> Command Code
header.HD_Status = &buffer[1];////[1] -> Status Code
header.HD_TransactionNum = &buffer[2];//[2] -> Transaction Number
header.HD_Data_Function_Code = &buffer[4];//[1] -> Data Function Code
/*Determine what command is being requested*/
new_pccc_length = Command_Protocol(header,buffer,buffer_size);
return new_pccc_length; //Return the new pccc length
}
/* Determine the Command that is being requested to execute */
uint16_t Command_Protocol(pccc_header header, unsigned char *buffer, int buffer_size)
{
uint16_t var_pccc_length;
/*If Statement to determine the command code from the Command Packet*/
if(((unsigned int)*header.HD_CMD_Code == 0x0f) && ((unsigned int)*header.HD_Data_Function_Code == 0xA2))//Protected Logical Read
{
var_pccc_length = Protected_Logical_Read_Reply(header,buffer,buffer_size);
return var_pccc_length;
}
else if(((unsigned int)*header.HD_CMD_Code == 0x0f) && ( ((unsigned int)*header.HD_Data_Function_Code == 0xAA) || ((unsigned int)*header.HD_Data_Function_Code == 0xAB)))//Protected Logical Write
{
var_pccc_length = Protected_Logical_Write_Reply(header,buffer,buffer_size);
return var_pccc_length;
}
else
{
/*initialize logging system*/
unsigned char log_msg[1000];
unsigned char *p = log_msg;
spdlog::info("PCCC: Unsupported Command/Data Function Code!");
return -1;
}//return length as -1 to signify that the CMD Code/Function Code was not recognize
uint16_t var_pccc_length;
/*If Statement to determine the command code from the Command Packet*/
if(((unsigned int)*header.HD_CMD_Code == 0x0f) && ((unsigned int)*header.HD_Data_Function_Code == 0xA2))//Protected Logical Read
{
var_pccc_length = Protected_Logical_Read_Reply(header,buffer,buffer_size);
return var_pccc_length;
}
else if(((unsigned int)*header.HD_CMD_Code == 0x0f) && ( ((unsigned int)*header.HD_Data_Function_Code == 0xAA) || ((unsigned int)*header.HD_Data_Function_Code == 0xAB)))//Protected Logical Write
{
var_pccc_length = Protected_Logical_Write_Reply(header,buffer,buffer_size);
return var_pccc_length;
}
else
{
/*initialize logging system*/
unsigned char log_msg[1000];
unsigned char *p = log_msg;
spdlog::info("PCCC: Unsupported Command/Data Function Code!");
return -1;
}//return length as -1 to signify that the CMD Code/Function Code was not recognize
}
uint16_t Protected_Logical_Read_Reply(pccc_header header, unsigned char *buffer, int buffer_size)
{
/*Variables*/
protected_logical_read_command protected_LR;
protected_LR.CMD_Byte_Size = &buffer[5];//Byte Size of data to be read
/*Determining Data Length*/
unsigned int len_resp = 4;
len_resp = len_resp + (unsigned int)*protected_LR.CMD_Byte_Size;
/*check if the message is long enough- Left in for future error handling setup*/
/*if (buffer_size < 8)
{
//PCCC Error Handling; Make sure that the buffer size is at least 8
}*/
/*Variables*/
protected_logical_read_command protected_LR;
protected_LR.CMD_Byte_Size = &buffer[5];//Byte Size of data to be read
/*Determining Data Length*/
unsigned int len_resp = 4;
len_resp = len_resp + (unsigned int)*protected_LR.CMD_Byte_Size;
/*check if the message is long enough- Left in for future error handling setup*/
/*if (buffer_size < 8)
{
//PCCC Error Handling; Make sure that the buffer size is at least 8
}*/
//****************** Read Coils **********************//
if(buffer[6] == PCCC_FN_OUTPUT && buffer[7] == PCCC_OUTPUT_LOGICAL_SLOT) // Done/Tested
{
Pccc_ReadCoils(buffer, buffer_size);
}
//*************** Read Discrete Inputs ***************//
else if(buffer[6] == PCCC_FN_INPUT && buffer[7] == PCCC_INPUT_LOGICAL_SLOT)// Done/Tested
{
Pccc_ReadDiscreteInputs(buffer, buffer_size);
}
//****************** Read Coils **********************//
if(buffer[6] == PCCC_FN_OUTPUT && buffer[7] == PCCC_OUTPUT_LOGICAL_SLOT) // Done/Tested
{
Pccc_ReadCoils(buffer, buffer_size);
}
//*************** Read Discrete Inputs ***************//
else if(buffer[6] == PCCC_FN_INPUT && buffer[7] == PCCC_INPUT_LOGICAL_SLOT)// Done/Tested
{
Pccc_ReadDiscreteInputs(buffer, buffer_size);
}
//****************** Read Holding Registers[PURE, 16Bit Mem, 32bit MEM] ******************//
else if((buffer[6] == PCCC_FN_INT || buffer[6] == PCCC_FN_FLOAT) && (buffer[7] == PCCC_INTEGER || buffer[7] == PCCC_FLOATING_POINT))//Done/Tested
{
Pccc_ReadHoldingRegisters(buffer, buffer_size);
}
else
{
unsigned char log_msg[1000];
unsigned char *p = log_msg;
spdlog::info("PCCC: Error occurred while processing Protected Logical Read");
return -1;
}//return length as -1 to signify that the CMD Code/Function Code was not recognize
/*Creating the reply packet and memcpy the data into the buffer*/
memmove(&buffer[0], (unsigned int)header.RP_CMD_Code, 1); //0x4f Response Code
//****************** Read Holding Registers[PURE, 16Bit Mem, 32bit MEM] ******************//
else if((buffer[6] == PCCC_FN_INT || buffer[6] == PCCC_FN_FLOAT) && (buffer[7] == PCCC_INTEGER || buffer[7] == PCCC_FLOATING_POINT))//Done/Tested
{
Pccc_ReadHoldingRegisters(buffer, buffer_size);
}
else
{
unsigned char log_msg[1000];
unsigned char *p = log_msg;
spdlog::info("PCCC: Error occurred while processing Protected Logical Read");
return -1;
}//return length as -1 to signify that the CMD Code/Function Code was not recognize
/*Creating the reply packet and memcpy the data into the buffer*/
memmove(&buffer[0], (unsigned int)header.RP_CMD_Code, 1); //0x4f Response Code
memmove(&buffer[1], (unsigned int)header.HD_Status, 1); //Same from COMMAND REQUEST
memmove(&buffer[2], (unsigned int)header.HD_TransactionNum, 2);//Same from COMMAND REQUEST
return len_resp; //Return the Resonse Packet Length for PCCC
return len_resp; //Return the Resonse Packet Length for PCCC
}
uint16_t Protected_Logical_Write_Reply(pccc_header header,unsigned char *buffer, int buffer_size) // Connected
{
/*Variables*/
protected_logical_write_command protected_LW;
protected_LW.CMD_Byte_Size = &buffer[5];//Byte Size of data to be read
/*Determining link of new PCCC Packet*/
uint16_t len_resp = header.HD_length - 1;
/*Creating the reply packet and memcpy the data into the buffer*/
memmove(&buffer[0], (unsigned int)header.RP_CMD_Code, 1);
{
/*Variables*/
protected_logical_write_command protected_LW;
protected_LW.CMD_Byte_Size = &buffer[5];//Byte Size of data to be read
/*Determining link of new PCCC Packet*/
uint16_t len_resp = header.HD_length - 1;
/*Creating the reply packet and memcpy the data into the buffer*/
memmove(&buffer[0], (unsigned int)header.RP_CMD_Code, 1);
memmove(&buffer[1], (unsigned int)header.HD_Status, 1);
memmove(&buffer[2], (unsigned int)header.HD_TransactionNum, 2);
/*check if the message is long enough- Left in for future error handling setup*/
/*if (buffer_size < 8)
{
//PCCC Error Handling; Make sure that the buffer size is at least 8
}*/
//****************** Write Coil **********************//
if(buffer[6] == PCCC_FN_OUTPUT && buffer[7] == PCCC_OUTPUT_LOGICAL_SLOT)// Done/Tested
{
Pccc_WriteCoil(buffer, buffer_size);
}
/*check if the message is long enough- Left in for future error handling setup*/
/*if (buffer_size < 8)
{
//PCCC Error Handling; Make sure that the buffer size is at least 8
}*/
//****************** Write Coil **********************//
if(buffer[6] == PCCC_FN_OUTPUT && buffer[7] == PCCC_OUTPUT_LOGICAL_SLOT)// Done/Tested
{
Pccc_WriteCoil(buffer, buffer_size);
}
//****************** Write Register ******************//
else if((buffer[6] == PCCC_FN_FLOAT || buffer[6] == PCCC_FN_INT) && (buffer[7] == PCCC_INTEGER || buffer[7] == PCCC_FLOATING_POINT))//Done/Tested
{
Pccc_WriteRegister(buffer, buffer_size);
}
//****************** Write Register ******************//
else if((buffer[6] == PCCC_FN_FLOAT || buffer[6] == PCCC_FN_INT) && (buffer[7] == PCCC_INTEGER || buffer[7] == PCCC_FLOATING_POINT))//Done/Tested
{
Pccc_WriteRegister(buffer, buffer_size);
}
//****************** Function Code Error ******************/
/*Left in for future error handling setup*/
else
{
//PCCC Error Handling; Make sure that the buffer size is at least 8. If none of the defined File Type and File Numbers match, error unrecognized File Type and File Number.
}
return len_resp;
//****************** Function Code Error ******************/
/*Left in for future error handling setup*/
else
{
//PCCC Error Handling; Make sure that the buffer size is at least 8. If none of the defined File Type and File Numbers match, error unrecognized File Type and File Number.
}
return len_resp;
}
//-----------------------------------------------------------------------------
@ -276,20 +276,20 @@ uint16_t Protected_Logical_Write_Reply(pccc_header header,unsigned char *buffer,
//-----------------------------------------------------------------------------
int word_pccc(unsigned char byte1, unsigned char byte2)
{
int returnValue;
returnValue = (int)(byte1) | (int)byte2;
int returnValue;
returnValue = (int)(byte1) | (int)byte2;
return returnValue;
return returnValue;
}
//-----------------------------------------------------------------------------
// Concatenate two bytes into an int
//-----------------------------------------------------------------------------
int an_word_pccc(unsigned char byte1, unsigned char byte2)
{
int returnValue;
returnValue = (int)(byte1) | (int)(byte2 << 8);
int returnValue;
returnValue = (int)(byte1) | (int)(byte2 << 8);
return returnValue;
return returnValue;
}
//-----------------------------------------------------------------------------
@ -297,49 +297,49 @@ int an_word_pccc(unsigned char byte1, unsigned char byte2)
//-----------------------------------------------------------------------------
void Pccc_ReadCoils(unsigned char *buffer, int buffer_size) //Working QX Read
{
int Start, ByteDataLength, Mask;
/*check if the message is long enough- Left in for future error handling setup*/
/*if (buffer_size < 10)
{
//PCCC Error Handling (Fill in?); This Request must have at least 10 bytes. If it doesn't, its a corrupted message
}*/
Start = word_pccc(buffer[8],buffer[9]); //Start based on the Element and Subelemnt values in the Command Packet
Mask = log2( word_pccc(buffer[10],buffer[11]) ); //Save the byte size or byte data length to the variable from the command packet
ByteDataLength = buffer[5];
int Start, ByteDataLength, Mask;
/*check if the message is long enough- Left in for future error handling setup*/
/*if (buffer_size < 10)
{
//PCCC Error Handling (Fill in?); This Request must have at least 10 bytes. If it doesn't, its a corrupted message
}*/
Start = word_pccc(buffer[8],buffer[9]); //Start based on the Element and Subelemnt values in the Command Packet
Mask = log2( word_pccc(buffer[10],buffer[11]) ); //Save the byte size or byte data length to the variable from the command packet
ByteDataLength = buffer[5];
std::lock_guard<std::mutex> guard(bufferLock);
/*----Reading the values from the PLC bool_output buffer and writing to the PCCC buffer based on position----*/
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[4+i], j, *bool_output[position/8][position%8]);
}
else
{
bitWrite(buffer[4+i],j,0);
}
}
else
{
//PCCC Error Handling (Fill in?); If the position is greater than the MAX COILS, ERROR Overflow?
}
}
}
/*Left in for future error handling setup*/
/*if (pccc_error != ERR_NONE)
{
//PCCC Error Handling (Fill in?); Deetermine if there was an error:
}*/
std::lock_guard<std::mutex> guard(bufferLock);
/*----Reading the values from the PLC bool_output buffer and writing to the PCCC buffer based on position----*/
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[4+i], j, *bool_output[position/8][position%8]);
}
else
{
bitWrite(buffer[4+i],j,0);
}
}
else
{
//PCCC Error Handling (Fill in?); If the position is greater than the MAX COILS, ERROR Overflow?
}
}
}
/*Left in for future error handling setup*/
/*if (pccc_error != ERR_NONE)
{
//PCCC Error Handling (Fill in?); Deetermine if there was an error:
}*/
}
//-----------------------------------------------------------------------------
@ -347,130 +347,130 @@ void Pccc_ReadCoils(unsigned char *buffer, int buffer_size) //Working QX Read
//-----------------------------------------------------------------------------
void Pccc_ReadDiscreteInputs(unsigned char *buffer, int buffer_size) //Working IX Read Only
{
int Start, ByteDataLength;
/*This Request must have at least 10 bytes. If it doesn't, its a corrupted messageLeft in for future error handling setup*/
/*if (buffer_size < 10)
{
//PCCC Error Handling (Fill in?); This Request must have at least 10 bytes. If it doesn't, its a corrupted message
}*/
Start = word_pccc(buffer[8],buffer[9]);//Start based on the Element and Subelemnt values in the Command Packet
ByteDataLength = buffer[5];//Save the byte size or byte data length to the variable from the command packet
std::lock_guard<std::mutex> guard(bufferLock);
/*--------Reading the values from the PLC bool_input buffer and writing to the PCCC buffer based on position--------*/
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[4+i], j, *bool_input[position/8][position%8]);
}
else
{
bitWrite(buffer[4+i],j,0);
}
}
else
{
//PCCC Error Handling (Fill in?); If the position is greater than the MAX, ERROR Overflow?
}
}
}
/*Left in for future error handling setup*/
/*if (mb_error != ERR_NONE)
{
//PCCC Error Handling (Fill in?); Deetermine if there was an error:
}*/
int Start, ByteDataLength;
/*This Request must have at least 10 bytes. If it doesn't, its a corrupted messageLeft in for future error handling setup*/
/*if (buffer_size < 10)
{
//PCCC Error Handling (Fill in?); This Request must have at least 10 bytes. If it doesn't, its a corrupted message
}*/
Start = word_pccc(buffer[8],buffer[9]);//Start based on the Element and Subelemnt values in the Command Packet
ByteDataLength = buffer[5];//Save the byte size or byte data length to the variable from the command packet
std::lock_guard<std::mutex> guard(bufferLock);
/*--------Reading the values from the PLC bool_input buffer and writing to the PCCC buffer based on position--------*/
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[4+i], j, *bool_input[position/8][position%8]);
}
else
{
bitWrite(buffer[4+i],j,0);
}
}
else
{
//PCCC Error Handling (Fill in?); If the position is greater than the MAX, ERROR Overflow?
}
}
}
/*Left in for future error handling setup*/
/*if (mb_error != ERR_NONE)
{
//PCCC Error Handling (Fill in?); Deetermine if there was an error:
}*/
}
//-----------------------------------------------------------------------------
// Implementation of PCCC Read Holding Registers
//-----------------------------------------------------------------------------
void Pccc_ReadHoldingRegisters(unsigned char *buffer, int buffer_size) // QW Read
{
int Start, an_Start, WordDataLength, ByteDataLength;
{
int Start, an_Start, WordDataLength, ByteDataLength;
/*this request must have at least 10 bytes. If it doesn't, it's a corrupted message - Left in for future error handling setup*/
/*if (buffer_size < 10)
{
//PCCC Error Handling (Fill in?); This Request must have at least 10 bytes. If it doesn't, its a corrupted message
}*/
/*this request must have at least 10 bytes. If it doesn't, it's a corrupted message - Left in for future error handling setup*/
/*if (buffer_size < 10)
{
//PCCC Error Handling (Fill in?); This Request must have at least 10 bytes. If it doesn't, its a corrupted message
}*/
Start = word_pccc(buffer[8],buffer[9]);//Start based on the Element and Subelemnt values in the Command Packet
ByteDataLength = buffer[5];//Save the byte size or byte data length to the variable from the command packet
WordDataLength = ByteDataLength / 2;//Calculate the word data length based on the byte data length
unsigned int Temp_FileT = buffer[7];//Value will be changed potentially during this process, save the File Type Value from command packet
unsigned int Temp_FileN = buffer[6];//Value will be changed potentially during this process, save the File Number Value from command packet
Start = word_pccc(buffer[8],buffer[9]);//Start based on the Element and Subelemnt values in the Command Packet
ByteDataLength = buffer[5];//Save the byte size or byte data length to the variable from the command packet
WordDataLength = ByteDataLength / 2;//Calculate the word data length based on the byte data length
unsigned int Temp_FileT = buffer[7];//Value will be changed potentially during this process, save the File Type Value from command packet
unsigned int Temp_FileN = buffer[6];//Value will be changed potentially during this process, save the File Number Value from command packet
/*asked for too many registers - Left in for future error handling setup*/
/*if (ByteDataLength > 255)
{
//PCCC Error Handling (Fill in?); This Request must have at greater than 255 bytes. If it does, its a corrupted message
//return;
}*/
/*asked for too many registers - Left in for future error handling setup*/
/*if (ByteDataLength > 255)
{
//PCCC Error Handling (Fill in?); This Request must have at greater than 255 bytes. If it does, its a corrupted message
//return;
}*/
std::lock_guard<std::mutex> guard(bufferLock);
/*--------Reading the values from the PLC int_output, int_memory, and dint_memory buffer and writing to the PCCC buffer based on position--------*/
for(int i = 0; i < WordDataLength; i++)
{
int position = Start + i;
//int an_position = an_Start + i;
if ((position <= MIN_16B_RANGE) && (Temp_FileN == PCCC_FN_INT && Temp_FileT == PCCC_INTEGER))
{
if (int_output[position] != NULL)
{
buffer[ 4 + position * 2] = lowByte(*int_output[position]);
buffer[5 + position * 2] = highByte(*int_output[position]);
}
else
{
buffer[ 4 + position * 2] = 0;
buffer[5 + position * 2] = 0;
}
}
//accessing memory
//16-bit registers
else if ((position >= MIN_16B_RANGE && position <= MAX_16B_RANGE) && (Temp_FileN == PCCC_FN_INT && Temp_FileT == PCCC_INTEGER))
{
if (int_memory[position - MIN_16B_RANGE] != NULL)
{
buffer[ 4 + position * 2] = lowByte(*int_memory[position - MIN_16B_RANGE]);
buffer[5 + position * 2] = highByte(*int_memory[position - MIN_16B_RANGE]);
}
else
{
buffer[ 4 + position * 2] = 0;
buffer[5 + position * 2] = 0;
}
}
//32-bit registers
else if (Temp_FileN == PCCC_FN_FLOAT && Temp_FileT == PCCC_FLOATING_POINT && (position % 2 == 0))
{
position = position/2;
uint32_t tempValue = *dint_memory[position];
buffer[4+(4*position)] = tempValue;
buffer[5+(4*position)] = tempValue >> 8;
buffer[6+(4*position)] = tempValue >> 16;
buffer[7+(4*position)] = tempValue >> 24;
}
/*Left in for future error handling setup-Invalid Address*/
else
{
//PCCC Error Handling (Fill in?); If none of the above are recognized, error
}
std::lock_guard<std::mutex> guard(bufferLock);
/*--------Reading the values from the PLC int_output, int_memory, and dint_memory buffer and writing to the PCCC buffer based on position--------*/
for(int i = 0; i < WordDataLength; i++)
{
int position = Start + i;
//int an_position = an_Start + i;
if ((position <= MIN_16B_RANGE) && (Temp_FileN == PCCC_FN_INT && Temp_FileT == PCCC_INTEGER))
{
if (int_output[position] != NULL)
{
buffer[ 4 + position * 2] = lowByte(*int_output[position]);
buffer[5 + position * 2] = highByte(*int_output[position]);
}
else
{
buffer[ 4 + position * 2] = 0;
buffer[5 + position * 2] = 0;
}
}
//accessing memory
//16-bit registers
else if ((position >= MIN_16B_RANGE && position <= MAX_16B_RANGE) && (Temp_FileN == PCCC_FN_INT && Temp_FileT == PCCC_INTEGER))
{
if (int_memory[position - MIN_16B_RANGE] != NULL)
{
buffer[ 4 + position * 2] = lowByte(*int_memory[position - MIN_16B_RANGE]);
buffer[5 + position * 2] = highByte(*int_memory[position - MIN_16B_RANGE]);
}
else
{
buffer[ 4 + position * 2] = 0;
buffer[5 + position * 2] = 0;
}
}
//32-bit registers
else if (Temp_FileN == PCCC_FN_FLOAT && Temp_FileT == PCCC_FLOATING_POINT && (position % 2 == 0))
{
position = position/2;
uint32_t tempValue = *dint_memory[position];
buffer[4+(4*position)] = tempValue;
buffer[5+(4*position)] = tempValue >> 8;
buffer[6+(4*position)] = tempValue >> 16;
buffer[7+(4*position)] = tempValue >> 24;
}
/*Left in for future error handling setup-Invalid Address*/
else
{
//PCCC Error Handling (Fill in?); If none of the above are recognized, error
}
}
}
}
//-----------------------------------------------------------------------------
@ -478,45 +478,45 @@ void Pccc_ReadHoldingRegisters(unsigned char *buffer, int buffer_size) // QW Rea
//-----------------------------------------------------------------------------
void Pccc_WriteCoil(unsigned char *buffer, int buffer_size) //QX Write NEEDS WRITE MULTIPLE
{
int Start, Mask;
int mask_offset = 0;
/*Left in for future error handling setup*/
/*if(buffer_size < 10)
{
//ModbusError(buffer, ERR_ILLEGAL_DATA_ADDRESS);
//return;
}
*/
/*For the Write Mask, there has to be a maskoffset due to an extra two bytes */
if((unsigned int)buffer[4] == 0xAB)
{
mask_offset = buffer[5]; //Byte Size
}
Start = word_pccc(buffer[8],buffer[9]);//Start based on the Element and Subelemnt values in the Command Packet
Mask = log2( word_pccc(buffer[10],buffer[11]) );// Maskoffset based on the mask value in Masked Protected Write Command Packet
/*--------Determines if the values inside the PCCC data has a 1 or 0 in it. Writes that value to the bool_output based on the contents of the data in PCCC Buffer-------*/
if(Start < MAX_COILS)
{
unsigned char value;
if(word_pccc(buffer[10 + mask_offset],buffer[11 + mask_offset]) > 0)
{
value = 1;
}
else
{
value = 0;
}
std::lock_guard<std::mutex> guard(bufferLock);
if(bool_output[Start][Mask] != NULL)
{
*bool_output[Start][Mask] = value;
}
}
int Start, Mask;
int mask_offset = 0;
/*Left in for future error handling setup*/
/*if(buffer_size < 10)
{
//ModbusError(buffer, ERR_ILLEGAL_DATA_ADDRESS);
//return;
}
*/
/*For the Write Mask, there has to be a maskoffset due to an extra two bytes */
if((unsigned int)buffer[4] == 0xAB)
{
mask_offset = buffer[5]; //Byte Size
}
Start = word_pccc(buffer[8],buffer[9]);//Start based on the Element and Subelemnt values in the Command Packet
Mask = log2( word_pccc(buffer[10],buffer[11]) );// Maskoffset based on the mask value in Masked Protected Write Command Packet
/*--------Determines if the values inside the PCCC data has a 1 or 0 in it. Writes that value to the bool_output based on the contents of the data in PCCC Buffer-------*/
if(Start < MAX_COILS)
{
unsigned char value;
if(word_pccc(buffer[10 + mask_offset],buffer[11 + mask_offset]) > 0)
{
value = 1;
}
else
{
value = 0;
}
std::lock_guard<std::mutex> guard(bufferLock);
if(bool_output[Start][Mask] != NULL)
{
*bool_output[Start][Mask] = value;
}
}
}
@ -525,47 +525,47 @@ void Pccc_ReadHoldingRegisters(unsigned char *buffer, int buffer_size) // QW Rea
//-----------------------------------------------------------------------------
void Pccc_WriteRegister(unsigned char *buffer, int buffer_size) // QW Write
{
int Start, WordDataLength, ByteDataLength;
int Start, WordDataLength, ByteDataLength;
Start = word_pccc(buffer[8],buffer[9]);//Start based on the Element and Subelemnt values in the Command Packet
int an_Start = an_word_pccc(buffer[8],buffer[9]);//Different Start method for INTs based on the Element and Subelemnt values in the Command Packet
ByteDataLength = buffer[5];//Save the byte size or byte data length to the variable from the command packet
WordDataLength = ByteDataLength / 2;//Calculate the word data length based on the byte data length
unsigned int Temp_FileT = buffer[7];//Value will be changed potentially during this process, save the File Type Value from command packet
unsigned int Temp_FileN = buffer[6];//Value will be changed potentially during this process, save the File Number Value from command packet
Start = word_pccc(buffer[8],buffer[9]);//Start based on the Element and Subelemnt values in the Command Packet
int an_Start = an_word_pccc(buffer[8],buffer[9]);//Different Start method for INTs based on the Element and Subelemnt values in the Command Packet
ByteDataLength = buffer[5];//Save the byte size or byte data length to the variable from the command packet
WordDataLength = ByteDataLength / 2;//Calculate the word data length based on the byte data length
unsigned int Temp_FileT = buffer[7];//Value will be changed potentially during this process, save the File Type Value from command packet
unsigned int Temp_FileN = buffer[6];//Value will be changed potentially during this process, save the File Number Value from command packet
std::lock_guard<std::mutex> guard(bufferLock);
/*--------Determines if the values inside the PCCC data has data. Writes that value to the appropriate PLC Buffer based on the contents of the data in PCCC Buffer-------*/
for(int i = 0; i < WordDataLength; i++)
{
int position = Start + i;
//analog outputs
if ((position <= MIN_16B_RANGE) && (Temp_FileN == PCCC_FN_INT && (Temp_FileT == PCCC_INTEGER)))
{
if (int_output[position] != NULL) *int_output[position] = an_word_pccc(buffer[10 + i], buffer[11 + i]);//look at this closer
}
//accessing memory
//16-bit registers
else if ((position >= MIN_16B_RANGE && position <= MAX_16B_RANGE) && (Temp_FileN == PCCC_FN_OUTPUT && (Temp_FileT == PCCC_INTEGER)))
{
if (int_memory[position - MIN_16B_RANGE] != NULL) *int_memory[position - MIN_16B_RANGE] = an_word_pccc(buffer[10 + i], buffer[11 + i]);//look at this closer
}
//32-bit registers
if (Temp_FileN == PCCC_FN_FLOAT && (Temp_FileT == PCCC_FLOATING_POINT))
{
if (dint_memory[position] != NULL)
{
uint32_t tempValue = buffer[10 + i] | buffer[11 + i] << 8 | buffer[12 + i] << 16 | buffer[13 + i] <<24;//look at this closer
*dint_memory[position] = tempValue;
i += 4;
}
else
{
pccc_holding_regs[position] = an_word_pccc(buffer[10 + i], buffer[11 + i]);//look at this closer might need to copy from temp
}
}
}
}
std::lock_guard<std::mutex> guard(bufferLock);
/*--------Determines if the values inside the PCCC data has data. Writes that value to the appropriate PLC Buffer based on the contents of the data in PCCC Buffer-------*/
for(int i = 0; i < WordDataLength; i++)
{
int position = Start + i;
//analog outputs
if ((position <= MIN_16B_RANGE) && (Temp_FileN == PCCC_FN_INT && (Temp_FileT == PCCC_INTEGER)))
{
if (int_output[position] != NULL) *int_output[position] = an_word_pccc(buffer[10 + i], buffer[11 + i]);//look at this closer
}
//accessing memory
//16-bit registers
else if ((position >= MIN_16B_RANGE && position <= MAX_16B_RANGE) && (Temp_FileN == PCCC_FN_OUTPUT && (Temp_FileT == PCCC_INTEGER)))
{
if (int_memory[position - MIN_16B_RANGE] != NULL) *int_memory[position - MIN_16B_RANGE] = an_word_pccc(buffer[10 + i], buffer[11 + i]);//look at this closer
}
//32-bit registers
if (Temp_FileN == PCCC_FN_FLOAT && (Temp_FileT == PCCC_FLOATING_POINT))
{
if (dint_memory[position] != NULL)
{
uint32_t tempValue = buffer[10 + i] | buffer[11 + i] << 8 | buffer[12 + i] << 16 | buffer[13 + i] <<24;//look at this closer
*dint_memory[position] = tempValue;
i += 4;
}
else
{
pccc_holding_regs[position] = an_word_pccc(buffer[10 + i], buffer[11 + i]);//look at this closer might need to copy from temp
}
}
}
}

View File

@ -8,7 +8,7 @@
// 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.
@ -19,21 +19,23 @@
// Thiago Alves, Jun 2019
//-----------------------------------------------------------------------------
#include <ini.h>
#include <spdlog/spdlog.h>
#include <cstdio>
#include <cstdint>
#include <algorithm>
#include <chrono>
#include <istream>
#include <fstream>
#include <memory>
#include <mutex>
#include <thread>
#include <type_traits>
#include <ini.h>
#include <spdlog/spdlog.h>
#include "glue.h"
#include "ini_util.h"
#include "ladder.h"
#include "pstorage.h"
#include "lib/iec_types_all.h"
/** \addtogroup openplc_runtime
@ -158,9 +160,9 @@ size_t pstorage_copy_glue(const GlueVariablesBinding& bindings, char* buffer) {
/// This is populated with values from the config file.
struct PstorageConfig {
PstorageConfig() :
poll_interval(std::chrono::seconds(10))
poll_interval(chrono::seconds(10))
{}
std::chrono::seconds poll_interval;
chrono::seconds poll_interval;
};
int pstorage_cfg_handler(void* user_data, const char* section,
@ -172,9 +174,9 @@ int pstorage_cfg_handler(void* user_data, const char* section,
auto config = reinterpret_cast<PstorageConfig*>(user_data);
if (strcmp(name, "poll_interval") == 0) {
// We do not allow a poll period of less than 1 second as that
// We do not allow a poll period of less than 1 second as that
// might cause lock contention problems.
config->poll_interval = std::chrono::seconds(max(1, atoi(value)));
config->poll_interval = chrono::seconds(max(1, atoi(value)));
} else if (strcmp(name, "enabled") == 0) {
// Nothing to do here - we already know this is enabled
} else {
@ -185,21 +187,21 @@ int pstorage_cfg_handler(void* user_data, const char* section,
return 0;
}
int8_t pstorage_run(std::unique_ptr<std::istream, std::function<void(std::istream*)>>& cfg_stream,
int8_t pstorage_run(oplc::config_stream& cfg_stream,
const char* cfg_overrides,
const GlueVariablesBinding& bindings,
volatile bool& run,
function<std::ostream*(void)> stream_fn)
{
function<ostream*(void)> stream_fn) {
PstorageConfig config;
ini_parse_stream(istream_fgets, cfg_stream.get(), pstorage_cfg_handler, &config);
ini_parse_stream(oplc::istream_fgets, cfg_stream.get(),
pstorage_cfg_handler, &config);
// We are done with the file, so release the unique ptr. Normally this
// will close the reference to the file
cfg_stream.reset(nullptr);
if (strlen(cfg_overrides) > 0) {
config.poll_interval = std::chrono::seconds(max(1, atoi(cfg_overrides)));
config.poll_interval = chrono::seconds(max(1, atoi(cfg_overrides)));
}
const char endianness_header[2] = { IS_BIG_ENDIAN, '\n'};
@ -270,8 +272,7 @@ inline int8_t read_and_check(istream& input_stream, const char header[],
}
int8_t pstorage_read(istream& input_stream,
const GlueVariablesBinding& bindings)
{
const GlueVariablesBinding& bindings) {
// Read the file header - we define the file header as a constant that
// must be present as the header. We don't allow UTF BOMs here.
char header_check[FILE_HEADER_SIZE];
@ -314,7 +315,7 @@ int8_t pstorage_read(istream& input_stream,
switch (glue.size) {
case IECLST_BIT:
num_bytes = 1;
break;
break;
case IECLST_BYTE:
num_bytes = 1;
break;
@ -383,17 +384,13 @@ void pstorage_service_finalize(const GlueVariablesBinding& binding) {
void pstorage_service_run(const GlueVariablesBinding& binding,
volatile bool& run, const char* config) {
// We don't allow a poll duration of less than one second otherwise
// that can have detrimental effects on performance
auto create_stream = []() { return new ofstream("persistent.file", ios::binary); };
unique_ptr<istream, function<void(istream*)>> cfg_stream(new ifstream("../etc/config.ini"), [](istream* s)
{
reinterpret_cast<ifstream*>(s)->close();
delete s;
});
auto create_stream = []() {
return new ofstream("persistent.file", ios::binary);
};
auto cfg_stream = oplc::open_config();
pstorage_run(cfg_stream, config, binding, run, create_stream);
}

View File

@ -12,13 +12,11 @@
// See the License for the specific language governing permissionsand
// limitations under the License.
#ifndef CORE_PSTORAGE_H_
#define CORE_PSTORAGE_H_
#ifndef RUNTIME_CORE_PSTORAGE_H_
#define RUNTIME_CORE_PSTORAGE_H_
#include <cstdint>
#include <chrono>
#include <functional>
#include <memory>
#include "ini_util.h"
/** \addtogroup openplc_runtime
* @{
@ -77,7 +75,7 @@ std::int8_t pstorage_read(std::istream& input_stream,
/// @param run A flag that indicates if we should terminate the process.
/// @return Zero on success, otherwise non-zero. This function may fail
/// part way through. Failure does not mean no variables have been set.
std::int8_t pstorage_run(std::unique_ptr<std::istream, std::function<void(std::istream*)>>& cfg_stream,
std::int8_t pstorage_run(oplc::config_stream& cfg_stream,
const char* custom_config,
const GlueVariablesBinding& bindings,
volatile bool& run,
@ -85,4 +83,4 @@ std::int8_t pstorage_run(std::unique_ptr<std::istream, std::function<void(std::i
/** @}*/
#endif // CORE_PSTORAGE_H_
#endif // RUNTIME_CORE_PSTORAGE_H_

View File

@ -12,8 +12,8 @@
// See the License for the specific language governing permissionsand
// limitations under the License.
#include <algorithm>
#include <spdlog/spdlog.h>
#include <algorithm>
#include "service_definition.h"
#include "../glue.h"
@ -62,12 +62,14 @@ ServiceDefinition::ServiceDefinition(const char* name,
{}
void ServiceDefinition::initialize() {
GlueVariablesBinding bindings(&bufferLock, OPLCGLUE_GLUE_SIZE, oplc_glue_vars, OPLCGLUE_MD5_DIGEST);
GlueVariablesBinding bindings(&bufferLock, OPLCGLUE_GLUE_SIZE,
oplc_glue_vars, OPLCGLUE_MD5_DIGEST);
this->init_fn(bindings);
}
void ServiceDefinition::finalize() {
GlueVariablesBinding bindings(&bufferLock, OPLCGLUE_GLUE_SIZE, oplc_glue_vars, OPLCGLUE_MD5_DIGEST);
GlueVariablesBinding bindings(&bufferLock, OPLCGLUE_GLUE_SIZE,
oplc_glue_vars, OPLCGLUE_MD5_DIGEST);
this->finalize_fn(bindings);
}
@ -81,12 +83,14 @@ void ServiceDefinition::start(const char* config) {
size_t config_len = strlen(config);
if (config_len > MAX_INTERACTIVE_CONFIG_SIZE - 1) {
spdlog::warn("{} cannot be started because config is longer than {}.", this->name, MAX_INTERACTIVE_CONFIG_SIZE);
spdlog::warn("{} cannot be started because config is longer than {}.",
this->name, MAX_INTERACTIVE_CONFIG_SIZE);
return;
}
// Copy the configuration information into our configuration buffer
strncpy(this->config_buffer, config, min(config_len, MAX_INTERACTIVE_CONFIG_SIZE));
strncpy(this->config_buffer, config,
min(config_len, MAX_INTERACTIVE_CONFIG_SIZE));
spdlog::info("Starting service {}", this->name);
@ -117,4 +121,6 @@ void* ServiceDefinition::run_service(void* user_data) {
service->start_fn(bindings, service->running, service->config_buffer);
service->running = false;
return nullptr;
}

View File

@ -12,12 +12,12 @@
// See the License for the specific language governing permissionsand
// limitations under the License.
#ifndef CORE_SERVICE_SERVICE_DEFINITION_H_
#define CORE_SERVICE_SERVICE_DEFINITION_H_
#ifndef RUNTIME_CORE_SERVICE_SERVICE_DEFINITION_H_
#define RUNTIME_CORE_SERVICE_SERVICE_DEFINITION_H_
#include <pthread.h>
#include <cstdint>
#include <functional>
#include <pthread.h>
/** \addtogroup openplc_runtime
* @{
@ -46,7 +46,7 @@ typedef std::function<void(const GlueVariablesBinding& binding, volatile bool& r
/// or from start to stop. That's only because we haven't had such a need
/// yet. If that comes up, then we'll add that.
class ServiceDefinition final {
public:
public:
/// Initialize a new instance of a service definition that can be started
/// and stopped but does not participate in initialize or finalize.
/// @param name The unique name of this service.
@ -91,11 +91,12 @@ class ServiceDefinition final {
/// service.
const char* config() const { return this->config_buffer; }
private:
private:
// Hide the copy constructor
ServiceDefinition(ServiceDefinition &);
static void* run_service(void* user_data);
private:
private:
/// The type name of the service.
const char* name;
/// The function to initialize the service.
@ -115,4 +116,4 @@ class ServiceDefinition final {
/** @}*/
#endif // CORE_SERVICE_SERVICE_DEFINITION_H_
#endif // RUNTIME_CORE_SERVICE_SERVICE_DEFINITION_H_

View File

@ -22,6 +22,8 @@
#include "../modbusslave/slave.h"
#include "../dnp3s/dnp3.h"
using namespace std;
ServiceInitFunction pstorage_init_fn(pstorage_service_init);
ServiceStartFunction pstorage_start_service_fn(pstorage_service_run);
ServiceStartFunction dnp3s_start_service_fn(dnp3s_service_run);
@ -38,27 +40,27 @@ ServiceDefinition* services[] = {
};
ServiceDefinition* services_find(const char* name) {
ServiceDefinition** item = std::find_if(std::begin(services), std::end(services), [name] (ServiceDefinition* def) {
ServiceDefinition** item = find_if(begin(services), end(services), [name] (ServiceDefinition* def) {
return strcmp(def->id(), name) == 0;
});
return (item != std::end(services)) ? *item : nullptr;
return (item != end(services)) ? *item : nullptr;
}
void services_stop() {
std::for_each(std::begin(services), std::end(services), [] (ServiceDefinition* def){
for_each(begin(services), end(services), [] (ServiceDefinition* def) {
def->stop();
});
}
void services_init() {
std::for_each(std::begin(services), std::end(services), [] (ServiceDefinition* def){
for_each(begin(services), end(services), [] (ServiceDefinition* def) {
def->initialize();
});
}
void services_finalize() {
std::for_each(std::begin(services), std::end(services), [] (ServiceDefinition* def){
for_each(begin(services), end(services), [] (ServiceDefinition* def) {
def->finalize();
});
}
}

View File

@ -12,8 +12,8 @@
// See the License for the specific language governing permissionsand
// limitations under the License.
#ifndef CORE_SERVICE_SERVICE_REGISTRY_H_
#define CORE_SERVICE_SERVICE_REGISTRY_H_
#ifndef RUNTIME_CORE_SERVICE_SERVICE_REGISTRY_H_
#define RUNTIME_CORE_SERVICE_SERVICE_REGISTRY_H_
/** \addtogroup openplc_runtime
* @{
@ -37,4 +37,4 @@ void services_finalize();
/** @}*/
#endif // CORE_SERVICE_SERVICE_DEFINITION_H_
#endif // RUNTIME_CORE_SERVICE_SERVICE_DEFINITION_H_