/
tisit
/
OpenPLC_v3
Обзор
Документация
Войти
/
tisit
/
OpenPLC_v3
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
webserver/core/dnp3.cpp
465 строк
16 KB
Rainer Kordmaa
-w was hiding 125 warnings, more than half of them associated with log() in main.cpp
06 мар 2024, 23:50
06 мар 2024, 23:50
3f63596
Код
Авторство
О чём код?
//2017 Trevor Aron // //File contains code for DNP3 // #include <asiodnp3/DNP3Manager.h> #include <asiodnp3/PrintingSOEHandler.h> #include <asiodnp3/PrintingChannelListener.h> #include <asiodnp3/ConsoleLogger.h> #include <asiodnp3/UpdateBuilder.h> #include <asiopal/UTCTimeSource.h> #include <opendnp3/outstation/SimpleCommandHandler.h> #include <opendnp3/LogLevels.h> //#include <opendnp3/outstation/Database.h> #include <string> #include <thread> #include <iostream> #include <stdio.h> #include <time.h> #include <signal.h> #include <algorithm> #include <functional> #include <cctype> #include <locale> #include <fstream> #include "ladder.h" //some modbus defines #define MAX_DISCRETE_INPUT 8192 #define MAX_COILS 8192 #define MAX_HOLD_REGS 8192 #define MAX_INP_REGS 1024 #define MIN_16B_RANGE 1024 #define MAX_16B_RANGE 2047 #define MIN_32B_RANGE 2048 #define MAX_32B_RANGE 4095 #define MIN_64B_RANGE 4096 #define MAX_64B_RANGE 8191 #define OPLC_CYCLE 50000000 // Initial offset parameters (yurgen1975) int offset_di = 0; int offset_do = 0; int offset_ai = 0; int offset_ao = 0; using namespace std; using namespace opendnp3; using namespace openpal; using namespace asiopal; using namespace asiodnp3; IEC_BOOL dnp3_discrete_input[MAX_DISCRETE_INPUT]; IEC_BOOL dnp3_coils[MAX_COILS]; IEC_UINT dnp3_input_regs[MAX_INP_REGS]; IEC_UINT dnp3_holding_regs[MAX_HOLD_REGS]; // trim string from left static inline std::string <rim(std::string &s) { s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun<int, int>(std::isspace)))); return s; } // trim string from right static inline std::string &rtrim(std::string &s) { s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end()); return s; } // trim string from left and right static inline std::string &trim(std::string &s) { return ltrim(rtrim(s)); } //----------------------------------------------------------------------------- // Class to handle commands from the master //----------------------------------------------------------------------------- class CommandCallback: public ICommandHandler { public: //CROB - changed to support offsets (yurgen1975) virtual CommandStatus Select(const ControlRelayOutputBlock& command, uint16_t index) { index = index + offset_di; return CommandStatus::SUCCESS; } virtual CommandStatus Operate(const ControlRelayOutputBlock& command, uint16_t index, OperateType opType) { index = index + offset_di; auto code = command.functionCode; CommandStatus return_val; if(code == ControlCode::LATCH_ON || code == ControlCode::LATCH_OFF) { return_val = CommandStatus::SUCCESS; IEC_BOOL crob_val = (code == ControlCode::LATCH_ON); pthread_mutex_lock(&bufferLock); if(bool_output[index/8][index%8] != NULL) { *bool_output[index/8][index%8] = crob_val; } pthread_mutex_unlock(&bufferLock); } else { return_val = CommandStatus::NOT_SUPPORTED; } return return_val; } //Analog Out - changed to support offsets (yurgen1975) virtual CommandStatus Select(const AnalogOutputInt16& command, uint16_t index) { index = index + offset_ao; return CommandStatus::SUCCESS; } virtual CommandStatus Operate(const AnalogOutputInt16& command, uint16_t index, OperateType opType) { index = index + offset_ao; auto ao_val = command.value; pthread_mutex_lock(&bufferLock); if(index < MIN_16B_RANGE && int_output[index] != NULL) { *int_output[index] = ao_val; } else if(index < MAX_16B_RANGE && int_memory[index - MIN_16B_RANGE] != NULL) { *int_memory[index - MIN_16B_RANGE] = ao_val; } else if(index > MAX_16B_RANGE) { return CommandStatus::OUT_OF_RANGE; } pthread_mutex_unlock(&bufferLock); return CommandStatus::SUCCESS; } //AnalogOut 32 (Int) virtual CommandStatus Select(const AnalogOutputInt32& command, uint16_t index) { return CommandStatus::SUCCESS; } virtual CommandStatus Operate(const AnalogOutputInt32& command, uint16_t index, OperateType opType) { auto ao_val = command.value; if(index < MIN_32B_RANGE || index >= MAX_32B_RANGE) return CommandStatus::OUT_OF_RANGE; pthread_mutex_lock(&bufferLock); if(dint_memory[index - MIN_32B_RANGE] != NULL) { *dint_memory[index - MIN_32B_RANGE] = ao_val; } pthread_mutex_unlock(&bufferLock); return CommandStatus::SUCCESS; } //AnalogOut 32 (Float) virtual CommandStatus Select(const AnalogOutputFloat32& command, uint16_t index) { return CommandStatus::SUCCESS; } virtual CommandStatus Operate(const AnalogOutputFloat32& command, uint16_t index, OperateType opType) { auto ao_val = command.value; if(index < MIN_32B_RANGE || index >= MAX_32B_RANGE) return CommandStatus::OUT_OF_RANGE; pthread_mutex_lock(&bufferLock); if(dint_memory[index - MIN_32B_RANGE] != NULL) { *dint_memory[index - MIN_32B_RANGE] = ao_val; } pthread_mutex_unlock(&bufferLock); return CommandStatus::SUCCESS; } //AnalogOut 64 virtual CommandStatus Select(const AnalogOutputDouble64& command, uint16_t index) { return CommandStatus::SUCCESS; } virtual CommandStatus Operate(const AnalogOutputDouble64& command, uint16_t index, OperateType opType) { auto ao_val = command.value; if(index < MIN_64B_RANGE || index >= MAX_64B_RANGE) return CommandStatus::OUT_OF_RANGE; pthread_mutex_lock(&bufferLock); if(lint_memory[index - MIN_64B_RANGE] != NULL) { *lint_memory[index - MIN_64B_RANGE] = ao_val; } pthread_mutex_unlock(&bufferLock); return CommandStatus::SUCCESS; } protected: void Start() final {} void End() final {} }; //------------------------------------------------------------------ // Function to update DNP3 values every time they may have changed // Updated by Yurgen1975 to support slave devices: DI/DO address 800 and AI/AO address 100 //------------------------------------------------------------------ void update_vals(std::shared_ptr<IOutstation> outstation){ UpdateBuilder builder; // Update Discrete input (Binary input) - changed to support offsets (yurgen1975) for(int i = offset_di; i < MAX_DISCRETE_INPUT; i++) { builder.Update(Binary((bool)(*bool_input[i/8][i%8])), i-offset_di); } // Update Coils (Binary Output) - changed to support offsets (yurgen1975) for(int i = offset_do; i < MAX_COILS; i++) { builder.Update(BinaryOutputStatus((bool)(*bool_output[i/8][i%8])), i-offset_do); } // Update Input Registers (Analog Input) - changed to support offsets (yurgen1975) for (int i = offset_ai; i < MAX_INP_REGS; i++) { builder.Update(Analog((int)(*int_input[i])), i-offset_ai); } // Update Holding Registers (Analog Output) - changed to support offsets (yurgen1975) for (int i = offset_ao; i < MIN_16B_RANGE; i++) { builder.Update(AnalogOutputStatus((int)(*int_output[i])), i-offset_ao); } // Update Holding registers for memory for (int i = MIN_16B_RANGE; i < MAX_16B_RANGE; i++) { if(int_memory[i - MIN_16B_RANGE] != NULL) builder.Update( AnalogOutputStatus((int)(*int_memory[i - MIN_16B_RANGE])), i ); } // Update Holding registers for 32 b memory for (int i = MIN_32B_RANGE; i < MAX_32B_RANGE; i++) { if(dint_memory[i - MIN_32B_RANGE] != NULL) builder.Update( AnalogOutputStatus((int)(*dint_memory[i - MIN_32B_RANGE])), i ); } // Update Holding registers for 64 b memory for (int i = MIN_64B_RANGE; (i < MAX_64B_RANGE && i - MIN_64B_RANGE < sizeof(lint_memory) / sizeof(lint_memory[0])); i++) { if(lint_memory[i - MIN_64B_RANGE] != NULL) builder.Update( AnalogOutputStatus((int)(*lint_memory[i - MIN_64B_RANGE])), i ); } outstation->Apply(builder.Build()); } //---------------------------------------------------------------------- // Need to parse 'database_size' first //---------------------------------------------------------------------- OutstationStackConfig create_config() { string line; ifstream cfgfile("dnp3.cfg"); bool found = false; if(cfgfile.is_open()) { while (getline(cfgfile, line)) { if (line[0] == '#') continue; try { istringstream iss(line); string token; getline(iss, token, '='); token = trim(token); if (token == "database_size") { getline(iss, token, '='); return OutstationStackConfig( DatabaseSizes::AllTypes(atoi(token.c_str())) ); } else continue; } catch(...) { cout << "Malformatted Line: " << line << endl; exit(1); } } } return OutstationStackConfig(DatabaseSizes::AllTypes(10)); } //---------------------------------------------------------------------- // parse dnp3.cfg and set dnp3 settings //---------------------------------------------------------------------- OutstationStackConfig parseDNP3Config() { string line; ifstream cfgfile("dnp3.cfg"); OutstationStackConfig config = create_config(); if(cfgfile.is_open()) { while (getline(cfgfile, line)) { if (line[0] == '#') continue; try { istringstream iss(line); string token; getline(iss, token, '='); token = trim(token); if (token == "local_address") { getline(iss, token, '='); config.link.LocalAddr = atoi(token.c_str()); } else if (token == "remote_address") { getline(iss, token, '='); config.link.RemoteAddr = atoi(token.c_str()); } else if (token == "keep_alive_timeout") { getline(iss, token, '='); if(token == "MAX") { config.link.KeepAliveTimeout = openpal::TimeDuration::Max(); } else { config.link.KeepAliveTimeout = openpal::TimeDuration::Seconds(atoi(token.c_str())); } } else if (token == "enable_unsolicited") { getline(iss, token, '='); if(token == "True") config.outstation.params.allowUnsolicited = true; else config.outstation.params.allowUnsolicited = false; } else if (token == "select_timeout") { getline(iss, token, '='); config.outstation.params.selectTimeout = openpal::TimeDuration::Seconds(atoi(token.c_str())); } else if (token == "max_controls_per_request") { getline(iss, token, '='); config.outstation.params.maxControlsPerRequest = atoi(token.c_str()); } else if (token == "max_rx_frag_size") { getline(iss, token, '='); config.outstation.params.maxRxFragSize = atoi(token.c_str()); } else if (token == "max_tx_frag_size") { getline(iss, token, '='); config.outstation.params.maxTxFragSize = atoi(token.c_str()); } else if (token == "event_buffer_size") { getline(iss, token, '='); config.outstation.eventBufferConfig = EventBufferConfig::AllTypes(atoi(token.c_str())); // get offsets from dnp.cfg (yurgen1975) } else if (token == "offset_di") { getline(iss, token, '='); offset_di = atoi(token.c_str()); } else if (token == "offset_do") { getline(iss, token, '='); offset_do = atoi(token.c_str()); } else if (token == "offset_ai") { getline(iss, token, '='); offset_ai = atoi(token.c_str()); } else if (token == "offset_ao") { getline(iss, token, '='); offset_ao = atoi(token.c_str()); // ------------------------------------------------------------------- } else if (token == "sol_confirm_timeout") { getline(iss, token, '='); config.outstation.params.solConfirmTimeout = openpal::TimeDuration::Milliseconds( atoi(token.c_str()) ); } else if (token == "unsol_confirm_timeout") { getline(iss, token, '='); config.outstation.params.unsolConfirmTimeout = openpal::TimeDuration::Milliseconds( atoi(token.c_str()) ); } else if (token == "unsol_retry_timeout") { getline(iss, token, '='); config.outstation.params.unsolRetryTimeout = openpal::TimeDuration::Milliseconds( atoi(token.c_str()) ); } } catch(...) { cout << "Malformatted Line: " << line << endl; exit(1); } } } return config; } /*class ILogHandler { public: virtual void Log( const LogEntry& entry ) { printf("I have logged something\n"); } }; */ void ConsoleLogger::Log(const openpal::LogEntry& entry) { char log_msg[1000]; sprintf(log_msg, "DNP3 ID %s: %s\n", entry.loggerid, entry.message); log(log_msg); } //------------------------------------------------------------------ //Function to begin DNP3 server functions //------------------------------------------------------------------ void dnp3StartServer(int port) { const uint32_t FILTERS = levels::NORMAL; // Allocate a single thread to the pool since this is a single outstation // Log messages to the console DNP3Manager manager(1, ConsoleLogger::Create()); // Create a listener server auto channel = manager.AddTCPServer("DNP3_Server", FILTERS, ChannelRetry::Default(), "0.0.0.0", port, PrintingChannelListener::Create()); // Create a new outstation with a log level, command handler, and // config info this returns a thread-safe interface used for // updating the outstation's database. std::shared_ptr<ICommandHandler> cc = std::make_shared<CommandCallback>(); auto outstation = channel->AddOutstation( "outstation", cc, DefaultOutstationApplication::Create(), parseDNP3Config() ); // Enable the outstation and start communications outstation->Enable(); printf("DNP3 Enabled \n"); mapUnusedIO(); // Continuously update struct timespec timer_start; clock_gettime(CLOCK_MONOTONIC, &timer_start); while(run_dnp3) { pthread_mutex_lock(&bufferLock); update_vals(outstation); pthread_mutex_unlock(&bufferLock); sleep_until(&timer_start, OPLC_CYCLE); } printf("Shutting down DNP3 server\n"); channel->Shutdown(); printf("DNP3 Server deactivated\n"); }