@@ -0,0 +1,21 @@ | |||
#include "CustomStringUtilities.h" | |||
#include <iostream> | |||
const char* CustomStringUtilities::typeOfWhitespacesReturn = " \t\n\r\f\v"; | |||
const char* CustomStringUtilities::typeOfWhitespaces = " \t\r\f\v"; | |||
void CustomStringUtilities::removeAllWhitespaces(std::string& str, bool trimReturn) { | |||
const char* whitespaces = trimReturn ? typeOfWhitespacesReturn : typeOfWhitespaces; | |||
size_t pos, offset = 0; | |||
while ((pos = str.find_first_of(whitespaces, offset)) != std::string::npos) { | |||
str.erase(pos, 1); | |||
offset = pos; | |||
} | |||
} | |||
void CustomStringUtilities::trim(std::string& str) { | |||
str.erase(str.find_last_not_of(typeOfWhitespaces) + 1); | |||
str.erase(0, str.find_first_not_of(typeOfWhitespaces)); | |||
} | |||
@@ -0,0 +1,14 @@ | |||
#pragma once | |||
#include <string> | |||
class CustomStringUtilities | |||
{ | |||
private: | |||
static const char* typeOfWhitespaces; | |||
static const char* typeOfWhitespacesReturn; | |||
public: | |||
//remove whitespaces from the passed string | |||
static void removeAllWhitespaces(std::string& str, bool trimReturn = true); | |||
static void trim(std::string&); | |||
}; | |||
@@ -0,0 +1,163 @@ | |||
#include "DataAcquisition.h" | |||
#include "easylogging++.h" | |||
#include <iomanip> | |||
#include <condition_variable> | |||
#include <mutex> | |||
std::atomic_bool modbusThreadRunning = false; | |||
std::atomic_bool modbusThreadCancelled = false; | |||
//Modbus worker thread, lock and notification variable | |||
std::condition_variable cvar_modbus_queue; | |||
std::mutex mux_modbus_queue; | |||
DataAcquisition::DataAcquisition() : dataModel(DataModel::Instance()) | |||
{ | |||
} | |||
DataAcquisition::~DataAcquisition() | |||
{ | |||
LOG(INFO) << "Wait until modbus worker queue finishes..."; | |||
modbusThreadCancelled = true; | |||
cvar_modbus_queue.notify_one(); | |||
if(modbusThread.size() != 0 && modbusThread[0].joinable()){ | |||
modbusThread[0].join(); | |||
modbusThread.clear(); | |||
} | |||
} | |||
//Modbus thread | |||
//Execute the enqueued modbus register requests | |||
void modbusWorkerThread(DataAcquisition* this_p) { | |||
LOG(INFO) << "Modbus Worker Thread started"; | |||
auto& queue = this_p->getModbusQueue(); | |||
auto& publishers = this_p->getPublishers(); | |||
bool first = true; | |||
try { | |||
//vector of connections which failed to open | |||
std::vector<u_int> failedConnections; | |||
while (1) { | |||
if (modbusThreadCancelled) | |||
return; | |||
//If this is the first execution or the thread has | |||
//been notified after being in sleep mode | |||
if (queue.size() == 0 || first){ | |||
first = false; | |||
failedConnections.clear(); | |||
//No work to do, wait for something to be enqueued | |||
std::unique_lock<std::mutex> threadLock(mux_modbus_queue); | |||
//Wait till notification (happens when some registers are enqueued | |||
cvar_modbus_queue.wait(threadLock); | |||
if (modbusThreadCancelled) | |||
return; | |||
//open all of the registered connections | |||
for(unsigned int i = 0; i < publishers.size(); i++){ | |||
if(!publishers[i]->open()){ | |||
//write id to list if connection failed | |||
failedConnections.push_back(publishers[i]->getID()); | |||
} | |||
} | |||
} | |||
if (modbusThreadCancelled) | |||
return; | |||
//Get next parameter from the queue | |||
ParameterSpecification modbusParameter = queue.pop_front(); | |||
//Look if the connection is in the list of the failed connections, if so , skip this parameter | |||
if(std::find(failedConnections.begin(), failedConnections.end(), modbusParameter.connection->getID()) != failedConnections.end()){ | |||
continue; | |||
} | |||
//Skip parameter if not a reading parameter or connection is not open | |||
if(!modbusParameter.isReadable()) | |||
continue; | |||
switch (modbusParameter.length) { | |||
case 1: | |||
modbusParameter.connection->readBit(modbusParameter); | |||
break; | |||
case 8: | |||
modbusParameter.connection->readByte(modbusParameter); | |||
break; | |||
case 16: | |||
modbusParameter.connection->readRegister(modbusParameter); | |||
break; | |||
case 32: | |||
modbusParameter.connection->readDRegister(modbusParameter); | |||
break; | |||
case 64: | |||
modbusParameter.connection->readQRegister(modbusParameter); | |||
break; | |||
default: | |||
modbusParameter.connection->readBits(modbusParameter); | |||
} | |||
if(modbusParameter.error){ | |||
//Rading of Modbus Parameter was not successful | |||
LOG(WARNING) << std::dec | |||
<< "[Modbus " << modbusParameter.connection->getID() << ": " << modbusParameter.connection->getConnectionType() << "] " | |||
<< "Failed reading parameter "<< (int)modbusParameter.description | |||
<< " at 0x" | |||
<< std::hex << std::setfill('0') << std::setw(4) | |||
<< modbusParameter.address; | |||
} | |||
else{ | |||
//LOG(INFO) << std::dec | |||
// << "[Modbus " << modbusParameter.connection->getConnectionType() << " "<< | |||
// modbusParameter.connection->getID() << "]" << " Readed param " << (int)modbusParameter.description | |||
// << " value: " << value; | |||
DataModel::Instance()->saveModbusParameter(std::move(modbusParameter)); | |||
} | |||
} | |||
} | |||
catch (std::exception& e) { | |||
LOG(FATAL) << "Error in modbus access, shutting down modbus thread: " << e.what(); | |||
} | |||
modbusThreadRunning = false; | |||
} | |||
void DataAcquisition::startModbusThread() { | |||
if (modbusThreadRunning == false) | |||
{ | |||
modbusThreadRunning = true; | |||
modbusThread.push_back(std::thread(modbusWorkerThread, this)); | |||
} | |||
} | |||
//Registers publisher and moves ownership to DataAcquisition class | |||
void DataAcquisition::registerPublisher(std::unique_ptr<PublisherInterface> publisher) { | |||
publishers.push_back(std::move(publisher)); | |||
} | |||
void DataAcquisition::enqueuePublisherRegister(){ | |||
for (size_t i = 0; i < publishers.size(); i++) { | |||
//Collects the alert modbus registers and enqueue them | |||
publishers[i]->enqueueReadingRegisters(modbusAccessQueue, Category::Alert); | |||
} | |||
for (size_t i = 0; i < publishers.size(); i++) { | |||
//Collects the condition modbus registers and enqueue them | |||
publishers[i]->enqueueReadingRegisters(modbusAccessQueue, Category::Condition); | |||
} | |||
cvar_modbus_queue.notify_one(); | |||
} | |||
@@ -0,0 +1,38 @@ | |||
#pragma once | |||
#include "DataModel.h" | |||
#include "PublisherInterface.h" | |||
#include <memory> | |||
#include <vector> | |||
#include <thread> | |||
#include "ts_queue.h" | |||
#include "modbus_interface_lib.h" | |||
class DataAcquisition | |||
{ | |||
private: | |||
std::unique_ptr<DataModel>& dataModel; | |||
//Modbus Connections for POC and Bender, can be serial or IP | |||
std::vector<std::unique_ptr<PublisherInterface>> publishers; | |||
//Modbus thread | |||
std::vector<std::thread> modbusThread; | |||
ts_queue<ParameterSpecification> modbusAccessQueue; | |||
public: | |||
DataAcquisition(); | |||
~DataAcquisition(); | |||
void startModbusThread(); | |||
ts_queue<ParameterSpecification>& getModbusQueue() { return modbusAccessQueue; } | |||
//void registerPublisher(Publisher* publisher); | |||
void registerPublisher(std::unique_ptr<PublisherInterface> publisher); | |||
std::vector<std::unique_ptr<PublisherInterface>>& getPublishers() { return publishers; } | |||
void enqueuePublisherRegister(); | |||
}; |
@@ -0,0 +1,364 @@ | |||
#include "DataModel.h" | |||
#include <easylogging++.h> | |||
#include <fstream> | |||
#include <filesystem> | |||
#include "SystemConfig.h" | |||
#include <charconv> | |||
#include <mutex> | |||
#include "PublisherPowercenter.h" | |||
#include "PublisherBenderRcm.h" | |||
std::chrono::milliseconds timeoutMS = std::chrono::milliseconds(10'000); | |||
long DataModel::permanentParamHistory = 0; | |||
int DataModel::narrowBlock = 0; | |||
//Initialization of static member | |||
std::unique_ptr<DataModel> DataModel::instance = nullptr; | |||
//Read file locker | |||
std::timed_mutex accessFilesMTX; | |||
constexpr unsigned long long DataModel::timepoint_to_sec_long(const std::chrono::system_clock::time_point& t) { | |||
return duration_to_sec_long(t.time_since_epoch()); | |||
} | |||
const ts_map<ModbusRegister, SavedData> &DataModel::getPublisherData() const | |||
{ | |||
return temporaryStorage; | |||
} | |||
CappedStorage* DataModel::getPermanentData(ModbusRegister reg){ | |||
auto it = permanentStorage.find(reg); | |||
if(it != permanentStorage.end()){ | |||
return &(it->second); | |||
} | |||
else | |||
return nullptr; | |||
} | |||
constexpr unsigned long long DataModel::duration_to_sec_long(const std::chrono::system_clock::duration& t) { | |||
return std::chrono::duration_cast<std::chrono::seconds>(t).count(); | |||
} | |||
inline void DataModel::Create() { | |||
instance = std::make_unique<DataModel>(); | |||
permanentParamHistory = SystemConfig::getIntConfigParameter("permanent_param_history"); | |||
narrowBlock = SystemConfig::getIntConfigParameter("narrow_block"); | |||
} | |||
DataModel::~DataModel() | |||
{ | |||
LOG(INFO) << "Save locally stored permanent data to permanent storage..."; | |||
//Save permanent data to storage | |||
long long time = std::chrono::system_clock::now().time_since_epoch().count(); | |||
std::fstream file; | |||
std::stringstream ss; | |||
ss << dataDir << "data_" << time; | |||
std::string filename = ss.str(); | |||
file.open(filename, std::ios::out); | |||
if (file.is_open()) { | |||
for(auto &storage : permanentStorage){ | |||
if(storage.second.size() == 0) | |||
continue; | |||
file << (int)storage.first << ":" << std::endl; | |||
file << storage.second << std::endl; | |||
} | |||
file.close(); | |||
} | |||
else | |||
LOG(ERROR) << "Could not access file to store permanent data..."; | |||
} | |||
std::unique_ptr<DataModel>& DataModel::Instance() { | |||
if (!instance) | |||
Create(); | |||
return instance; | |||
} | |||
void DataModel::Destroy() { | |||
if (instance) | |||
instance.reset(); | |||
} | |||
void DataModel::makePermanent(ModbusRegister reg, bool biased) | |||
{ | |||
permanentStorage.emplace(reg, CappedStorage(biased)); | |||
} | |||
void DataModel::saveModbusParameter(ParameterSpecification param) | |||
{ | |||
if(param.cat == Category::Alert){ | |||
short bitmask = 0; | |||
std::memcpy(&bitmask, param.readedBytes.data(), param.length); | |||
//Found an alert here | |||
if(bitmask != 0) | |||
LOG(WARNING) << "Received an alert from Modbus " << param.connection->getConnectionType() << ", ID " << param.connection->getID(); | |||
alerts.push(param); | |||
return; | |||
} | |||
//Temporary storage of last value | |||
temporaryStorage.emplaceOrOverwrite(param.description, SavedData(param.cat, param.readedBytes)); | |||
//If parameter is listet to be stored permanently | |||
auto res = permanentStorage.find(param.description); | |||
if(res != permanentStorage.end()){ | |||
checkForFlushData(); | |||
res->second.store(param.readedBytes); | |||
} | |||
} | |||
void DataModel::checkForFlushData() { | |||
while(1){ | |||
auto permStorageIter = permanentStorage.begin(); | |||
auto storageIter = permanentStorage.find(permStorageIter->first); | |||
auto& X = storageIter->second.getX(); | |||
auto& y = storageIter->second.getY(); | |||
//Rescale Matrix if data exceeds time limit | |||
storageIter->second.lock(); | |||
if(X.size() > 1 && (X(0,1) <= (X(X.rows()-1, 1) - DataModel::permanentParamHistory))){ | |||
if(X.size() <= narrowBlock) | |||
return; | |||
LOG(INFO) << "Shrink permanent storage of parameter " << std::dec << (int)permStorageIter->first; | |||
std::stringstream ss; | |||
ss << (int)storageIter->first << "(X);"; | |||
Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> temp(X.rows()-narrowBlock, X.cols()); | |||
temp = X.block(narrowBlock, 0, X.rows() - narrowBlock, X.cols()); | |||
//backup capped data to flush to file | |||
for(int i = 0; i < narrowBlock-1; i++){ | |||
ss << std::fixed << std::setprecision(0) << X(i, X.cols()-1) << ";"; | |||
} | |||
X.resize(X.rows() - narrowBlock, X.cols()); | |||
X = std::move(temp); | |||
ss << std::fixed << std::setprecision(0) << std::endl << (int)storageIter->first << "(y);"; | |||
temp.resize(y.rows()-narrowBlock, 1); | |||
temp = y.block(narrowBlock, 0, y.rows() - narrowBlock, 1); | |||
//backup capped data to flush to file | |||
for(int i = 0; i < narrowBlock-1; i++){ | |||
ss << std::fixed << std::setprecision(5) << y(i) << ";"; | |||
} | |||
ss << std::endl; | |||
y.resize(y.rows() - narrowBlock, 1); | |||
y = std::move(temp); | |||
flush(ss); | |||
} | |||
storageIter->second.unlock(); | |||
if((++permStorageIter) == permanentStorage.end()) | |||
break; | |||
} | |||
} | |||
bool DataModel::flush(std::stringstream& ss) | |||
{ | |||
//Data file lock condition | |||
if (!accessFilesMTX.try_lock_for(timeoutMS)) { | |||
LOG(ERROR) << "Timeout after waiting " << (long long)timeoutMS.count() << " for reading data files"; | |||
return false; | |||
} | |||
LOG(INFO) << "Flush data to local file"; | |||
std::fstream file; | |||
auto now = std::chrono::system_clock::now(); | |||
std::stringstream filename; | |||
filename << dataDir << "data_" << now.time_since_epoch().count(); | |||
file.open(filename.str(), std::ios_base::out); | |||
if (file.is_open()) { | |||
file << ss.str(); | |||
file.close(); | |||
} | |||
else | |||
LOG(ERROR) << "Could not open file for writing"; | |||
accessFilesMTX.unlock(); | |||
return true; | |||
} | |||
//reads in the log file to the supplied stringstream | |||
//define fromTime to get only logs older than fromTime (seconds after epoch) | |||
uintmax_t DataModel::readLogFile(std::stringstream& ss, long long fromTime) { | |||
el::Loggers::flushAll(); | |||
uintmax_t sizeRead = 0; | |||
std::filesystem::path p{ logDir }; | |||
std::ifstream file; | |||
std::string lineBuffer; | |||
std::filesystem::directory_iterator iterator(p); | |||
for (auto& currentFile : std::filesystem::directory_iterator(p)) | |||
if (currentFile.is_regular_file()) { | |||
if (logFileName.compare(currentFile.path().filename().string()) == 0) { | |||
file.open(currentFile.path()); | |||
if (file.is_open()) { | |||
sizeRead += currentFile.file_size(); | |||
bool foundTimePoint = false; | |||
while (std::getline(file, lineBuffer)) { | |||
if (lineBuffer.size() == 0) | |||
continue; | |||
if (fromTime != 0 && foundTimePoint == false) { | |||
std::stringstream temp; | |||
temp << lineBuffer; | |||
std::tm tm{}; | |||
temp >> std::get_time(&tm, dateFormatLogger.c_str()); | |||
auto timePoint = std::chrono::system_clock::from_time_t(std::mktime(&tm)); | |||
if (timePoint.time_since_epoch().count() >= fromTime) { | |||
foundTimePoint = true; | |||
} | |||
} | |||
else | |||
ss << lineBuffer << std::endl; | |||
} | |||
file.close(); | |||
LOG(INFO) << "Readed log file"; | |||
} | |||
else | |||
LOG(WARNING) << "Couldn't open LOG file for writing"; | |||
break; | |||
} | |||
} | |||
//If size read is 0, no tile at specified index found or file was empty | |||
return sizeRead; | |||
} | |||
uintmax_t DataModel::readAllDataFiles(std::stringstream& ss) { | |||
uintmax_t sizeRead = 0; | |||
if (!accessFilesMTX.try_lock_for(timeoutMS)) { | |||
LOG(ERROR) << "Timeout after waiting " << (long long)timeoutMS.count() << " for reading data files - blocked by another thread"; | |||
return 0; | |||
} | |||
std::filesystem::path p{ dataDir }; | |||
std::ifstream file; | |||
std::string lineBuffer; | |||
std::filesystem::directory_iterator iterator(p); | |||
for (auto& currentFile : std::filesystem::directory_iterator(p)){ | |||
if (currentFile.is_regular_file() && std::regex_match(currentFile.path().filename().string(), regexPatternFile)) { | |||
//look for a valid file with the specified index | |||
file.open(currentFile.path()); | |||
if (file.is_open()) { | |||
sizeRead += currentFile.file_size(); | |||
while (std::getline(file, lineBuffer)) { | |||
ss << lineBuffer << std::endl; | |||
} | |||
file.close(); | |||
std::filesystem::remove(currentFile.path()); | |||
} | |||
} | |||
} | |||
accessFilesMTX.unlock(); | |||
//If size read is 0, no tile at specified index found or file was empty | |||
return sizeRead; | |||
} | |||
std::stringstream& DataModel::readPermanentData(std::stringstream& buffer, bool retain){ | |||
for(auto &e: permanentStorage){ | |||
buffer << (int)e.first << ":" << std::endl; | |||
buffer << e.second; | |||
} | |||
if(!retain) | |||
permanentStorage.clear(); | |||
return buffer; | |||
} | |||
ts_queue<ParameterSpecification>& DataModel::getAlerts() | |||
{ | |||
return alerts; | |||
} | |||
std::stringstream& DataModel::readTemporaryData(std::stringstream& buffer){ | |||
for(auto &e: temporaryStorage){ | |||
buffer << (int)e.first << ":"; | |||
buffer << e.second; | |||
} | |||
return buffer; | |||
} | |||
//olderThan: files which are older than this specified time are deleted | |||
unsigned long DataModel::removeStoredData(seconds olderThan) { | |||
using namespace std::filesystem; | |||
auto timeNow = duration_cast<seconds>(system_clock::now().time_since_epoch()); | |||
u_int filesDeleted = 0; | |||
for (auto& file : directory_iterator(path("data/"))) { | |||
if (file.is_regular_file()) { | |||
if (std::regex_match(file.path().stem().string(), regexPatternFile)) { | |||
std::string str = file.path().stem().string(); | |||
str = str.substr(str.find_first_of("0123456789", 0)); | |||
//time of file in seconds after 01/01/1970 | |||
long long timeOfFile = 0; | |||
//if (auto [p, ec] = std::from_chars(str.data(), str.data() + str.length(), timeOfFile); ec == std::errc()) | |||
timeOfFile = std::stoll(str, 0); | |||
if ((timeOfFile + olderThan.count()) < timeNow.count()) | |||
if(remove(file)) | |||
filesDeleted++; | |||
} | |||
} | |||
} | |||
LOG(INFO) << "Deleted data files (" << filesDeleted << ") that were older than " << (long long)olderThan.count() <<"seconds on local storage"; | |||
return filesDeleted; | |||
} | |||
//remove every file that are stored locally | |||
unsigned long DataModel::removeStoredData() { | |||
using namespace std::filesystem; | |||
create_directory("data/"); | |||
unsigned long filesDeleted = 0; | |||
try{ | |||
for (auto& file : directory_iterator(path("data/"))) { | |||
if (file.is_regular_file()) { | |||
if (std::regex_match(file.path().stem().string(), regexPatternFile)) { | |||
std::string str = file.path().stem().string(); | |||
str = str.substr(str.find_first_of("0123456789", 0)); | |||
filesDeleted++; | |||
} | |||
} | |||
} | |||
} | |||
catch(std::exception& e){ | |||
LOG(ERROR) << "Can't access data directory"; | |||
} | |||
LOG(INFO) << "Deleted all data files (" << filesDeleted << ") on local storage"; | |||
return filesDeleted; | |||
} | |||
std::ostream& operator<<(std::ostream& os, const PublisherType type) { | |||
switch (type) { | |||
case PublisherType::RCMS_BENDER: | |||
os << "Bender RCMS"; | |||
break; | |||
case PublisherType::POWERCENTER: | |||
os << "Siemens Powercenter"; | |||
break; | |||
default: | |||
os << "<Unknown Publisher Type>"; | |||
break; | |||
} | |||
return os; | |||
} | |||
std::ostream& operator<<(std::ostream& os, const SavedData& savedData){ | |||
float value = 0; | |||
std::memcpy(&value, savedData.data.data(), 4); | |||
os << value << std::endl; | |||
return os; | |||
} |
@@ -0,0 +1,129 @@ | |||
#pragma once | |||
#include <memory> | |||
#include <vector> | |||
#include "PublisherData.h" | |||
#include <map> | |||
#include <chrono> | |||
#include <optional> | |||
#include <regex> | |||
#include <atomic> | |||
#include "ModbusDataPOC.h" | |||
#include "ModbusDataBender.h" | |||
#include "ts_map.h" | |||
#include "cappedstorage.h" | |||
const Eigen::MatrixXd dummyMat(0,0); | |||
const Eigen::VectorXd dummyVec(0); | |||
struct SavedData{ | |||
Category cat; | |||
std::vector<uint16_t> data; | |||
SavedData(const Category c, const std::vector<uint16_t> d){ | |||
cat = c; | |||
data = d; | |||
} | |||
friend std::ostream& operator<<(std::ostream& os, const SavedData& savedData); | |||
}; | |||
class PublisherInterface; | |||
constexpr auto READ_BUFFER_SIZE = 4096; | |||
typedef std::vector<std::vector<float>> float_matrix; | |||
//Type for one dimensional permanent double values | |||
typedef ts_map<ModbusRegister, CappedStorage> permanent_data; | |||
using namespace std::chrono; | |||
class DataModel | |||
{ | |||
public: | |||
//Sigleton Methods | |||
static std::unique_ptr<DataModel>& Instance(); | |||
static void Destroy(); | |||
static void Create(); | |||
~DataModel(); | |||
//save Data inside application | |||
void savePublishedData(PublisherData&&); | |||
void makePermanent(ModbusRegister reg, bool biased); | |||
void saveModbusParameter(ParameterSpecification param); | |||
//getter | |||
std::vector<u_int> getSavedPublishedDataIndexes(const u_int id, const seconds newerThan); | |||
//---local file storage methods--- | |||
//Stores in-programm data to local file system | |||
bool flush(std::stringstream &ss); | |||
uintmax_t readLogFile(std::stringstream& ss, long long fromTime); | |||
//Reads in from all files their content to ss, or a certain file, specified by its index | |||
//TODO: define readSince to read al data after that time point | |||
//size_t readSingleFile(std::stringstream& ss, const size_t index); | |||
uintmax_t readFromAllFiles(std::stringstream& ss, std::chrono::system_clock::time_point readSince = std::chrono::system_clock::from_time_t(0)); | |||
uintmax_t readAllDataFiles(std::stringstream& ss); | |||
unsigned long removeStoredData(seconds olderThan); | |||
unsigned long removeStoredData(); | |||
//saves collected data after a certain amount of time points | |||
void checkForFlushData(); | |||
unsigned long long getStartTime(const unsigned int id); | |||
unsigned long long getStartTime(); | |||
const char* getDataDir() { return dataDir; } | |||
const std::regex& getRegexPatternFile() { return regexPatternFile; } | |||
constexpr unsigned long long duration_to_sec_long(const std::chrono::system_clock::duration& t); | |||
constexpr unsigned long long timepoint_to_sec_long(const std::chrono::system_clock::time_point& t); | |||
const ts_map<ModbusRegister, SavedData> &getPublisherData() const; | |||
CappedStorage* getPermanentData(ModbusRegister reg); | |||
std::stringstream &readTemporaryData(std::stringstream &buffer); | |||
std::stringstream &readPermanentData(std::stringstream &buffer, bool retain); | |||
ts_queue<ParameterSpecification> &getAlerts(); | |||
private: | |||
//Register to enqueue data wich are stored permanently | |||
permanent_data permanentStorage; | |||
//Temporary storage of the last readed parameter | |||
ts_map<ModbusRegister, SavedData> temporaryStorage; | |||
//Temporary saved alerts | |||
ts_queue<ParameterSpecification> alerts; | |||
static long permanentParamHistory; | |||
static int narrowBlock; | |||
const std::regex regexPatternFile = std::regex("data_\\w+"); | |||
const std::string logFileName = "data_analysis.log"; | |||
const std::string dateFormatLogger = "%Y-%m-%d %H:%M:%S"; | |||
u_int nrOfDataPoints = 0; | |||
//static instance which can be accessed by calling DataModel::Instance() | |||
static std::unique_ptr<DataModel> instance; | |||
//local directory path to the data storage | |||
const char* dataDir = "data/"; | |||
const char* logDir = "log/"; | |||
//Data from publishers, one elemt in outer vector for one id | |||
//Each id consists of a vector of PublisherData (use map to make the id become a unique key) | |||
//ostream operators (read and write dataModel data from streams) | |||
friend std::ostream& operator<<(std::ostream& os, DataModel& dataModel); | |||
friend std::istream& operator>>(std::istream& is, DataModel& dataModel); | |||
}; |
@@ -0,0 +1,37 @@ | |||
#include "Evaluator.h" | |||
#include <easylogging++.h> | |||
#include "SystemConfig.h" | |||
Evaluator::Evaluator() | |||
{ | |||
//initialize Linear Regression Analysis on Residual Current Register | |||
CappedStorage* storageP = DataModel::Instance()->getPermanentData(ModbusRegister::BENDER_Residual_current); | |||
if(storageP != nullptr){ | |||
MLAlgorithms.push_back(std::make_unique<MLLinReg>(storageP)); | |||
} | |||
else | |||
LOG(ERROR) << "Tried to invoke ML-Algorithm on non permanent or non existing parameter storage"; | |||
} | |||
inline std::ostream& operator<<(std::ostream& os, std::vector<float>& vec) { | |||
std::for_each(vec.begin(), vec.end(), [&os](float a) {os << a << " "; }); | |||
return os; | |||
} | |||
//update all registered models | |||
std::vector<MLAlert> Evaluator::evaluate() | |||
{ | |||
std::vector<MLAlert> alerts; | |||
for(auto &a: MLAlgorithms){ | |||
MLAlert alert = a->updateModel(); | |||
if(alert.type != CustomAlertTypes::NO_ALERT) | |||
alerts.push_back(alert); | |||
} | |||
for(auto &a: alerts) | |||
LOG(WARNING) << a.message; | |||
return alerts; | |||
} | |||
@@ -0,0 +1,25 @@ | |||
#pragma once | |||
#include <memory> | |||
#include "MLAnalyzer.h" | |||
#include "MLLinReg.h" | |||
#include "MLAlert.h" | |||
#include <vector> | |||
#include <memory> | |||
class Evaluator | |||
{ | |||
private: | |||
//Enqueued Algorithms to perform data analysis | |||
std::vector<std::unique_ptr<MLAnalyzer>> MLAlgorithms; | |||
public: | |||
Evaluator(); | |||
//Gets called if new data is available | |||
std::vector<MLAlert> evaluate(); | |||
}; | |||
@@ -0,0 +1,17 @@ | |||
#ifndef MLALERT_H | |||
#define MLALERT_H | |||
#include <string> | |||
enum CustomAlertTypes{ | |||
NO_ALERT, | |||
CRIT_RCM_TENDENCY, | |||
}; | |||
struct MLAlert{ | |||
CustomAlertTypes type; | |||
std::string message; | |||
MLAlert(CustomAlertTypes type, std::string message): type(type), message(message){} | |||
}; | |||
#endif // MLALERT_H |
@@ -0,0 +1,42 @@ | |||
#include "MLAnalyzer.h" | |||
#include <sstream> | |||
#include "DataModel.h" | |||
#include "SystemConfig.h" | |||
MLAnalyzer::MLAnalyzer(CappedStorage *storage) : | |||
n(storage->getN()), m(storage->getM()), data(storage), updateRate(SystemConfig::getIntConfigParameter("update_model_rate")) | |||
{ | |||
} | |||
void MLAnalyzer::addData(Eigen::VectorXd Px, double Py) | |||
{ | |||
Eigen::Matrix<double, 1, 1> temp; | |||
temp << Py; | |||
addData(Px, temp); | |||
} | |||
void MLAnalyzer::addData(Eigen::MatrixXd Px, Eigen::VectorXd Py) | |||
{ | |||
//if(Px.cols() != n || (Px.rows() != Py.rows())){ | |||
// std::stringstream ss; | |||
// ss << "Invalid Matrix dimensions: "; | |||
// throw ss.str(); | |||
//} | |||
// | |||
//X.conservativeResize(X.rows()+Px.rows(), Eigen::NoChange_t()); | |||
//y.conservativeResize(y.rows()+Py.rows(), Eigen::NoChange_t()); | |||
// | |||
//X.block(m, 0, Px.rows(), 1) = Eigen::MatrixXd::Ones(Px.rows(), 1); | |||
//X.block(m, 1, Px.rows(), Px.cols()) = Px; | |||
// | |||
//m += X.rows(); | |||
// | |||
//if(++currentBatch >= trainAfterNPoints){ | |||
// updateModel(); | |||
// currentBatch = 0; | |||
//} | |||
} |
@@ -0,0 +1,37 @@ | |||
#ifndef MLANALYZER_H | |||
#define MLANALYZER_H | |||
#include <chrono> | |||
#include <vector> | |||
#include <eigen3/Eigen/Dense> | |||
#include "DataModel.h" | |||
#include "MLAlert.h" | |||
//Interface for Machine Learning algorithms | |||
class MLAnalyzer | |||
{ | |||
protected: | |||
std::chrono::milliseconds updateTime; | |||
const long long n; | |||
long long m; | |||
unsigned short currentBatch = 0; | |||
const unsigned short trainAfterNPoints = 10; | |||
//Data references | |||
CappedStorage* data; | |||
const int updateRate; | |||
int updateCounter = 0; | |||
public: | |||
MLAnalyzer(CappedStorage *storage); | |||
void addData(Eigen::VectorXd Px, double Py); | |||
void addData(Eigen::MatrixXd Px, Eigen::VectorXd Py); | |||
virtual MLAlert updateModel() = 0; | |||
}; | |||
#endif // MLANALYZER_H |
@@ -0,0 +1,74 @@ | |||
#include "MLLinReg.h" | |||
#include "SystemConfig.h" | |||
#include <easylogging++.h> | |||
#include <fstream> | |||
#include <iomanip> | |||
MLLinReg::MLLinReg(CappedStorage *storage) : MLAnalyzer(storage) | |||
{ | |||
criticalResidualCurrent = SystemConfig::getFloatConfigParameter("crit_residual_current"); | |||
criticalTimeRangeSec = SystemConfig::getIntConfigParameter("crit_residual_timerange") * SECONDS_IN_WEEK; | |||
Theta.resize(2, 1); | |||
} | |||
MLAlert MLLinReg::updateModel() | |||
{ | |||
if(t0 == 0 && data->getX().rows() != 0) {LOG(INFO) << std::setprecision(12) << std::fixed << "t_0 = " << (data->getX())(0, 1); t0 = (data->getX())(0, 1);} | |||
if(++updateCounter < updateRate) | |||
return MLAlert(CustomAlertTypes::NO_ALERT, ""); | |||
updateCounter = 0; | |||
LOG(INFO) << "Update Linear Regression Model"; | |||
normalEquation(); | |||
bool error = checkCritical(); | |||
if(error){ | |||
//Return an alert object if tendency is critical | |||
std::stringstream ans; | |||
ans << "Received critical tendency in the next " << std::round(criticalTimeRangeSec/SECONDS_IN_WEEK) << " seconds"; | |||
//LOG(INFO) << "Theta values: " << Theta(0) << " " << Theta(1); | |||
//if(first){ | |||
// std::ofstream file; | |||
// std::stringstream ss; | |||
// file.open("debug_lin_reg.txt", std::ios::out); | |||
// if(file.is_open()){ | |||
// file << std::setprecision(12) << std::fixed << "Theta: " << Theta(0) << " " << Theta(1) << std::endl; | |||
// file << std::setprecision(12) << std::fixed << "X: " << data->getX()(data->getX().rows()-1, 1) << std::endl; | |||
// file << std::setprecision(12) << std::fixed << "y: " << data->getY()(data->getY().rows()-1) << std::endl; | |||
// file << std::setprecision(12) << std::fixed << "t0: " << t0 << std::endl; | |||
// file << std::setprecision(12) << std::fixed << std::endl; | |||
// file.close(); | |||
// } | |||
// first = false; | |||
//} | |||
return MLAlert(CustomAlertTypes::CRIT_RCM_TENDENCY, ans.str()); | |||
} | |||
else | |||
return MLAlert(CustomAlertTypes::NO_ALERT, ""); | |||
} | |||
bool MLLinReg::checkCritical(){ | |||
//Check, if the critical value is reached within the specified time range | |||
if(data->getM() == 0) | |||
return false; | |||
//Offset, to avoid checking with little data and receive faulty results | |||
if(data->getM() < 100) | |||
return false; | |||
return (Theta(0) + Theta(1) * ((data->getX())((data->getX()).rows()-1, 1) + criticalTimeRangeSec)) | |||
>= criticalResidualCurrent; | |||
} | |||
//Numerical approach to solve univariate Linear Regression Model | |||
void MLLinReg::normalEquation() { | |||
Theta = Eigen::MatrixXd::Zero(2, 1); | |||
Theta = (data->getX().transpose() * data->getX()).inverse() * data->getX().transpose() * data->getY(); | |||
if(data->getX().rows() > 500){ | |||
LOG(INFO) << Theta(0) << " " << Theta(1); | |||
LOG(INFO) << data->getX().rows(); | |||
} | |||
} |
@@ -0,0 +1,23 @@ | |||
#ifndef MLLINREG_H | |||
#define MLLINREG_H | |||
#include "MLAnalyzer.h" | |||
constexpr int SECONDS_IN_WEEK = 7*24*60*60; | |||
class MLLinReg: public MLAnalyzer | |||
{ | |||
private: | |||
Eigen::MatrixXd Theta; | |||
void normalEquation(); | |||
bool first = true; | |||
long t0 = 0; | |||
float criticalResidualCurrent; | |||
int criticalTimeRangeSec; | |||
public: | |||
MLLinReg(CappedStorage*); | |||
MLAlert updateModel() override; | |||
bool checkCritical(); | |||
}; | |||
#endif // MLLINREG_H |
@@ -0,0 +1,10 @@ | |||
#include "ModbusDataBender.h" | |||
ModbusDataBender::ModbusDataBender(const unsigned int id) : ModbusDataInterface(id) | |||
{ | |||
//Store Modbus reading registers | |||
//Identification | |||
//Dummy parameter to simulate continous reading | |||
modbusParamFP64.emplace_back(ModbusRegister::BENDER_Residual_current, 100, Access::R, Category::Condition); | |||
} |
@@ -0,0 +1,11 @@ | |||
#pragma once | |||
#include "ModbusDataInterface.h" | |||
class ModbusDataBender : public ModbusDataInterface | |||
{ | |||
public: | |||
ModbusDataBender(const unsigned int id); | |||
}; | |||
@@ -0,0 +1,20 @@ | |||
#include "ModbusDataInterface.h" | |||
#include <algorithm> | |||
/* | |||
Collects all ModbusRegister which fulfill the specified category | |||
cat: specified category to look for | |||
queue: reference to a thread safe queue, which contains the readable registers | |||
return: the queue with the enqueued values | |||
*/ | |||
ts_queue<ParameterSpecification>& ModbusDataInterface::modbusRegisterCat(const Category cat, ts_queue<ParameterSpecification>& queue, std::unique_ptr<ModbusInterface>& connection) | |||
{ | |||
std::for_each(modbusParamFP32.begin(), modbusParamFP32.end(), [&queue, cat, &connection](const auto& p) { if (cat == p.getCategory()) queue.push(p.getSpecification(connection)); }); | |||
std::for_each(modbusParamFP64.begin(), modbusParamFP64.end(), [&queue, cat, &connection](const auto& p) { if (cat == p.getCategory()) queue.push(p.getSpecification(connection)); }); | |||
std::for_each(modbusParamU16.begin(), modbusParamU16.end(), [&queue, cat, &connection](const auto& p) { if (cat == p.getCategory()) queue.push(p.getSpecification(connection)); }); | |||
std::for_each(modbusParamU32.begin(), modbusParamU32.end(), [&queue, cat, &connection](const auto& p) { if (cat == p.getCategory()) queue.push(p.getSpecification(connection)); }); | |||
std::for_each(modbusParamS16.begin(), modbusParamS16.end(), [&queue, cat, &connection](const auto& p) { if (cat == p.getCategory()) queue.push(p.getSpecification(connection)); }); | |||
std::for_each(modbusParamString.begin(), modbusParamString.end(), [&queue, cat, &connection](const auto& p) { if (cat == p.getCategory()) queue.push(p.getSpecification(connection)); }); | |||
return queue; | |||
} |
@@ -0,0 +1,34 @@ | |||
#pragma once | |||
#include <vector> | |||
#include <string> | |||
#include <any> | |||
#include <sstream> | |||
#include <optional> | |||
#include "ts_queue.h" | |||
#include "ParameterDouble.h" | |||
#include "ParameterFloat.h" | |||
#include "ParameterUInt16.h" | |||
#include "ParameterUInt32.h" | |||
#include "ParameterCharP.h" | |||
#include "ParameterS16.h" | |||
class ModbusInterface; | |||
class ModbusDataInterface | |||
{ | |||
private: | |||
const unsigned int id; | |||
protected: | |||
std::vector<ParameterFloat> modbusParamFP32; | |||
std::vector<ParameterDouble> modbusParamFP64; | |||
std::vector<ParameterUInt16> modbusParamU16; | |||
std::vector<ParameterUInt32> modbusParamU32; | |||
std::vector<ParameterS16> modbusParamS16; | |||
std::vector<ParameterCharP> modbusParamString; | |||
public: | |||
ModbusDataInterface(const unsigned int id) : id(id) { } | |||
ts_queue<ParameterSpecification>& modbusRegisterCat(const Category cat, ts_queue<ParameterSpecification>& queue, std::unique_ptr<ModbusInterface> &connection); | |||
void readAll(); | |||
}; |
@@ -0,0 +1,247 @@ | |||
#include "ModbusDataPOC.h" | |||
std::string ModbusDataPOC::getStatusMessage(unsigned short statusID) | |||
{ | |||
switch (statusID) | |||
{ | |||
case 1: | |||
return "Breaker off (without realeasing)"; | |||
case 2: | |||
return "Breaker normal (on)"; | |||
case 3: | |||
return "Breaker released (off)"; | |||
case 4: | |||
return "Breaker released (Lever on and blocked)"; | |||
default: | |||
return "Status unknown"; | |||
} | |||
} | |||
ModbusDataPOC::ModbusDataPOC(const unsigned int id) : ModbusDataInterface(id){ | |||
//Store Modbus reading registers | |||
//Identification | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Ident_Manufacturer, 0x0002, Access::R); | |||
modbusParamString.emplace_back(ModbusRegister::POC_Ident_Ordernumber, 0x0003, 20, Access::R); | |||
modbusParamString.emplace_back(ModbusRegister::POC_Ident_Seriesnumber, 0x000D, 16, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Ident_Hardware_Version, 0x0015, Access::R); | |||
modbusParamString.emplace_back(ModbusRegister::POC_Ident_Software_Version, 0x0016, 4, Access::R); | |||
modbusParamString.emplace_back(ModbusRegister::POC_Ident_Plant_identification_code, 0x001D, 32, Access::RW); | |||
modbusParamString.emplace_back(ModbusRegister::POC_Ident_Installation_site, 0x002D, 22, Access::RW); | |||
modbusParamString.emplace_back(ModbusRegister::POC_Ident_Installation_date, 0x0038, 16, Access::RW); | |||
modbusParamString.emplace_back(ModbusRegister::POC_Ident_Firmware_Applicationcontroller, 0x0045, 10, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Ident_Market, 0x005E, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Ident_Main_device_rated_current, 0x005F, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Ident_Tripping_curve_characteristic, 0x0060, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Ident_Installation_place_fuse, 0x0062, Access::RW); | |||
modbusParamString.emplace_back(ModbusRegister::POC_Ident_MLFB_fuse, 0x0063, 20, Access::RW); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Ident_Hardware_Electronics, 0x006D, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Ident_rated_current_melting_part, 0x006E, Access::RW); | |||
//IP | |||
modbusParamString.emplace_back(ModbusRegister::POC_IP_Ethernet_MAC, 0x0200, 6, Access::R); | |||
modbusParamU32.emplace_back(ModbusRegister::POC_IP_Status_DHCP, 0x0203, Access::RW); | |||
modbusParamU32.emplace_back(ModbusRegister::POC_IP_SNTP_server_ip, 0x0205, Access::RW); | |||
modbusParamU32.emplace_back(ModbusRegister::POC_IP_SNTP_client_mode, 0x0207, Access::RW); | |||
modbusParamU32.emplace_back(ModbusRegister::POC_IP_Status_firewall, 0x0209, Access::RW); | |||
modbusParamU32.emplace_back(ModbusRegister::POC_IP_port_number, 0x020D, Access::RW); | |||
modbusParamU32.emplace_back(ModbusRegister::POC_IP_Static_IP, 0x020F, Access::RW); | |||
modbusParamU32.emplace_back(ModbusRegister::POC_IP_Subnetmask, 0x0211, Access::RW); | |||
modbusParamU32.emplace_back(ModbusRegister::POC_IP_Gateway, 0x029D, Access::RW); | |||
modbusParamU32.emplace_back(ModbusRegister::POC_IP_Current_IP, 0x029F, Access::R); | |||
modbusParamU32.emplace_back(ModbusRegister::POC_IP_Current_Subnet, 0x02A1, Access::R); | |||
modbusParamU32.emplace_back(ModbusRegister::POC_IP_Current_Gateway, 0x02A3, Access::R); | |||
//Bluetooth | |||
modbusParamU16.emplace_back(ModbusRegister::POC_BT_Status, 0x0301, Access::R); | |||
modbusParamS16.emplace_back(ModbusRegister::POC_BT_send_power, 0x0302, Access::RW); | |||
modbusParamString.emplace_back(ModbusRegister::POC_BT_device_address, 0x0303, 6, Access::R); | |||
modbusParamU32.emplace_back(ModbusRegister::POC_BT_passkey, 0x0306, Access::RW); | |||
//Radio | |||
modbusParamU32.emplace_back(ModbusRegister::POC_Radio_Date_time_utc, 0x02A3, Access::RW); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Paring_status_1, 0x0402, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Paring_status_2, 0x0403, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Paring_status_3, 0x0404, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Paring_status_4, 0x0405, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Paring_status_5, 0x0406, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Paring_status_6, 0x0407, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Paring_status_7, 0x0408, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Paring_status_8, 0x0409, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Paring_status_9, 0x040A, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Paring_status_10, 0x040B, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Paring_status_11, 0x040C, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Paring_status_12, 0x040D, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Paring_status_13, 0x040E, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Paring_status_14, 0x040F, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Paring_status_15, 0x0410, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Paring_status_16, 0x0411, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Paring_status_17, 0x0412, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Paring_status_18, 0x0413, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Paring_status_19, 0x0414, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Paring_status_20, 0x0415, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Paring_status_21, 0x0416, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Paring_status_22, 0x0417, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Paring_status_23, 0x0418, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Paring_status_24, 0x0419, Access::R); | |||
modbusParamS16.emplace_back(ModbusRegister::POC_Radio_transmit_power, 0x041A, Access::RW); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Device_status_1, 0x041B, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Device_status_2, 0x041C, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Device_status_3, 0x041D, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Device_status_4, 0x041E, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Device_status_5, 0x041F, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Device_status_6, 0x0420, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Device_status_7, 0x0421, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Device_status_8, 0x0422, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Device_status_9, 0x0423, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Device_status_10, 0x0424, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Device_status_11, 0x0425, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Device_status_12, 0x0426, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Device_status_13, 0x0427, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Device_status_14, 0x0428, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Device_status_15, 0x0429, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Device_status_16, 0x042A, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Device_status_17, 0x042B, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Device_status_18, 0x042C, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Device_status_19, 0x042D, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Device_status_20, 0x042E, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Device_status_21, 0x042F, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Device_status_22, 0x0430, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Device_status_23, 0x0431, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Device_status_24, 0x0432, Access::R); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Radio_Time_sync_to_POC, 0x0433, Access::R); | |||
//Radio Communication | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Mac_address_device_1, 0x0609, 8 , Access::RW); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Mac_address_device_2, 0x0616, 8 , Access::RW); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Mac_address_device_3, 0x0623, 8 , Access::RW); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Mac_address_device_4, 0x0630, 8 , Access::RW); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Mac_address_device_5, 0x063D, 8 , Access::RW); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Mac_address_device_6, 0x064A, 8 , Access::RW); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Mac_address_device_7, 0x0657, 8 , Access::RW); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Mac_address_device_8, 0x0664, 8 , Access::RW); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Mac_address_device_9, 0x0671, 8 , Access::RW); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Mac_address_device_10, 0x067E, 8 , Access::RW); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Mac_address_device_11, 0x068B, 8 , Access::RW); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Mac_address_device_12, 0x0698, 8 , Access::RW); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Mac_address_device_13, 0x06A5, 8 , Access::RW); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Mac_address_device_14, 0x06B2, 8 , Access::RW); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Mac_address_device_15, 0x06BF, 8 , Access::RW); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Mac_address_device_16, 0x06CC, 8 , Access::RW); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Mac_address_device_17, 0x06D9, 8 , Access::RW); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Mac_address_device_18, 0x06E6, 8 , Access::RW); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Mac_address_device_19, 0x06F3, 8 , Access::RW); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Mac_address_device_20, 0x0700, 8 , Access::RW); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Mac_address_device_21, 0x070D, 8 , Access::RW); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Mac_address_device_22, 0x071A, 8 , Access::RW); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Mac_address_device_23, 0x0727, 8 , Access::RW); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Mac_address_device_24, 0x0734, 8 , Access::RW); | |||
//Measurement and Condition | |||
modbusParamFP32.emplace_back(ModbusRegister::POC_Cond_Temp, 0x0C00 ,Access::R, Category::Condition); | |||
modbusParamFP32.emplace_back(ModbusRegister::POC_Cond_Temp_avg, 0x0C02 ,Access::R, Category::Condition); | |||
modbusParamFP32.emplace_back(ModbusRegister::POC_Cond_I_Phase, 0x0C04 ,Access::R, Category::Condition); | |||
modbusParamFP32.emplace_back(ModbusRegister::POC_Cond_I_Phase_avg, 0x0C06 ,Access::R, Category::Condition); | |||
modbusParamFP32.emplace_back(ModbusRegister::POC_Cond_I_Phase_max, 0x0C08 ,Access::R, Category::Condition); | |||
modbusParamFP32.emplace_back(ModbusRegister::POC_Cond_U_L_N, 0x0C0A ,Access::R, Category::Condition); | |||
modbusParamFP32.emplace_back(ModbusRegister::POC_Cond_Freq, 0x0C0C ,Access::R, Category::Condition); | |||
modbusParamFP32.emplace_back(ModbusRegister::POC_Cond_P, 0x0C0E ,Access::R, Category::Condition); | |||
modbusParamFP32.emplace_back(ModbusRegister::POC_Cond_S, 0x0C10 ,Access::R, Category::Condition); | |||
modbusParamFP32.emplace_back(ModbusRegister::POC_Cond_Q, 0x0C12 ,Access::R, Category::Condition); | |||
modbusParamFP32.emplace_back(ModbusRegister::POC_Cond_Cos_phi, 0x0C14 ,Access::R, Category::Condition); | |||
modbusParamFP64.emplace_back(ModbusRegister::POC_Cond_P_in, 0x0C16 ,Access::R, Category::Condition); | |||
modbusParamFP64.emplace_back(ModbusRegister::POC_Cond_P_ab, 0x0C1A ,Access::R, Category::Condition); | |||
modbusParamFP64.emplace_back(ModbusRegister::POC_Cond_S_in, 0x0C1E ,Access::R, Category::Condition); | |||
modbusParamFP64.emplace_back(ModbusRegister::POC_Cond_S_ab, 0x0C22 ,Access::R, Category::Condition); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Cond_Status, 0x0C26 ,Access::R, Category::Condition); | |||
//Measurement settings | |||
modbusParamU32.emplace_back(ModbusRegister::POC_Measurement_Time_period_temperature, 0x0E02 ,Access::RW ); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Measurement_Activate_temp_alert, 0x0E04 ,Access::RW ); | |||
modbusParamFP32.emplace_back(ModbusRegister::POC_Measurement_Limit_temperature_alert, 0x0E05 ,Access::RW ); | |||
modbusParamFP32.emplace_back(ModbusRegister::POC_Measurement_Hysteresis_temperature_alert, 0x0E07 ,Access::RW ); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Measurement_I_Averaging_interval_s, 0x0E0D ,Access::RW ); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Measurement_I_alert1_over_on_off, 0x0E0E ,Access::RW ); | |||
modbusParamFP32.emplace_back(ModbusRegister::POC_Measurement_I_alert1_over_limit_percentage, 0x0E0F ,Access::RW ); | |||
modbusParamFP32.emplace_back(ModbusRegister::POC_Measurement_I_alert1_over_hysteresis, 0x0E11 ,Access::RW ); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Measurement_I_alert2_over_on_off, 0x0E17 ,Access::RW ); | |||
modbusParamFP32.emplace_back(ModbusRegister::POC_Measurement_I_alert2_over_limit_percentage, 0x0E18 ,Access::RW ); | |||
modbusParamFP32.emplace_back(ModbusRegister::POC_Measurement_I_alert2_over_hysteresis, 0x0E1A ,Access::RW ); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Measurement_I_alert1_under_on_off, 0x0E20 ,Access::RW ); | |||
modbusParamFP32.emplace_back(ModbusRegister::POC_Measurement_I_alert1_under_limit_percentage, 0x0E21 ,Access::RW ); | |||
modbusParamFP32.emplace_back(ModbusRegister::POC_Measurement_I_alert1_under_hysteresis, 0x0E23 ,Access::RW ); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Measurement_I_alert2_under_on_off, 0x0E29 ,Access::RW ); | |||
modbusParamFP32.emplace_back(ModbusRegister::POC_Measurement_I_alert2_under_limit_percentage, 0x0E2A ,Access::RW ); | |||
modbusParamFP32.emplace_back(ModbusRegister::POC_Measurement_I_alert2_under_hysteresis, 0x0E2C ,Access::RW ); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Measurement_U_alert1_over_on_off, 0x0E32 ,Access::RW ); | |||
modbusParamFP32.emplace_back(ModbusRegister::POC_Measurement_U_alert1_over_limit_percentage, 0x0E33 ,Access::RW ); | |||
modbusParamFP32.emplace_back(ModbusRegister::POC_Measurement_U_alert1_over_hysteresis, 0x0E35 ,Access::RW ); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Measurement_U_alert2_over_on_off, 0x0E3B ,Access::RW ); | |||
modbusParamFP32.emplace_back(ModbusRegister::POC_Measurement_U_alert2_over_limit_percentage, 0x0E3C ,Access::RW ); | |||
modbusParamFP32.emplace_back(ModbusRegister::POC_Measurement_U_alert2_over_hysteresis, 0x0E3E ,Access::RW ); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Measurement_U_alert1_under_on_off, 0x0E44 ,Access::RW ); | |||
modbusParamFP32.emplace_back(ModbusRegister::POC_Measurement_U_alert1_under_limit_percentage, 0x0E45 ,Access::RW ); | |||
modbusParamFP32.emplace_back(ModbusRegister::POC_Measurement_U_alert1_under_hysteresis, 0x0E47 ,Access::RW ); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Measurement_U_alert2_under_on_off, 0x0E4D ,Access::RW ); | |||
modbusParamFP32.emplace_back(ModbusRegister::POC_Measurement_U_alert2_under_limit_percentage, 0x0E4E ,Access::RW ); | |||
modbusParamFP32.emplace_back(ModbusRegister::POC_Measurement_U_alert2_under_hysteresis, 0x0E50 ,Access::RW ); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Measurement_Energy_flow_direction, 0x0E5A ,Access::RW ); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Measurement_Alert_on_off_AFDD_threshold_shortfall, 0x0E5C ,Access::RW ); | |||
//Diagnosis | |||
modbusParamU32.emplace_back(ModbusRegister::POC_Diag_Alert, 0x0A00 ,Access::R , Category::Alert); | |||
modbusParamFP64.emplace_back(ModbusRegister::POC_Diag_H_run_with_I, 0x0A02 ,Access::R , Category::Diagnosis); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Diag_Alert_on_off_run_total_with_I, 0x0A06 ,Access::RW , Category::Diagnosis); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Diag_Min_load_current, 0x0A07 ,Access::RW , Category::Diagnosis); | |||
modbusParamFP64.emplace_back(ModbusRegister::POC_Diag_Limit_run_hours_with_I_alert, 0x0A08 ,Access::RW , Category::Diagnosis); | |||
modbusParamFP64.emplace_back(ModbusRegister::POC_Diag_H_run_total, 0x0A12 ,Access::R , Category::Diagnosis); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Diag_Alert_on_off_run_total, 0x0A16 ,Access::RW , Category::Diagnosis); | |||
modbusParamFP64.emplace_back(ModbusRegister::POC_Diag_Limit_run_hours_total_alert, 0x0A17 ,Access::RW , Category::Diagnosis); | |||
modbusParamFP32.emplace_back(ModbusRegister::POC_Diag_Nr_of_mechanical_switchings, 0x0A21 ,Access::R , Category::Diagnosis); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Diag_Alert_on_off_nr_mechanical_switchings, 0x0A23 ,Access::RW , Category::Diagnosis); | |||
modbusParamFP32.emplace_back(ModbusRegister::POC_Diag_Limit_mechanical_switchings, 0x0A24 ,Access::RW , Category::Diagnosis); | |||
modbusParamFP32.emplace_back(ModbusRegister::POC_Diag_Nr_of_triggered_switches, 0x0A2A ,Access::R , Category::Diagnosis); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Diag_Alert_on_off_nr_triggered_switchings, 0x0A2C ,Access::RW , Category::Diagnosis); | |||
modbusParamFP32.emplace_back(ModbusRegister::POC_Diag_Limit_triggered_switchings, 0x0A2D ,Access::RW , Category::Diagnosis); | |||
modbusParamS16.emplace_back(ModbusRegister::POC_Diag_RSSI_BLE, 0x0A3D ,Access::R , Category::Diagnosis); | |||
modbusParamS16.emplace_back(ModbusRegister::POC_Diag_RSSI_radio, 0x0A3E ,Access::R , Category::Diagnosis); | |||
modbusParamFP32.emplace_back(ModbusRegister::POC_Diag_Nr_of_short_circuit_triggers, 0x0A40 ,Access::R , Category::Diagnosis); | |||
modbusParamU16.emplace_back(ModbusRegister::POC_Diag_Alert_on_off_nr_short_circuit_triggers, 0x0A42 ,Access::RW , Category::Diagnosis); | |||
modbusParamFP32.emplace_back(ModbusRegister::POC_Diag_Nr_of_short_circuit_triggers, 0x0A43 ,Access::RW , Category::Diagnosis); | |||
modbusParamString.emplace_back(ModbusRegister::POC_Diag_Time_and_sync_status, 0x0A45, 8 ,Access::R , Category::Diagnosis); | |||
//Radio Communication | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Installation_Code_1, 0x0600, 18, Access::W); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Installation_Code_2, 0x060D, 18, Access::W); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Installation_Code_3, 0x061A, 18, Access::W); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Installation_Code_4, 0x0627, 18, Access::W); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Installation_Code_5, 0x0634, 18, Access::W); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Installation_Code_6, 0x0641, 18, Access::W); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Installation_Code_7, 0x064E, 18, Access::W); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Installation_Code_8, 0x065B, 18, Access::W); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Installation_Code_9, 0x0668, 18, Access::W); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Installation_Code_10, 0x0675, 18, Access::W); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Installation_Code_11, 0x0682, 18, Access::W); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Installation_Code_12, 0x068F, 18, Access::W); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Installation_Code_13, 0x069C, 18, Access::W); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Installation_Code_14, 0x06A9, 18, Access::W); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Installation_Code_15, 0x06B6, 18, Access::W); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Installation_Code_16, 0x06C3, 18, Access::W); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Installation_Code_17, 0x06D0, 18, Access::W); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Installation_Code_18, 0x06DD, 18, Access::W); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Installation_Code_19, 0x06EA, 18, Access::W); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Installation_Code_20, 0x06F7, 18, Access::W); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Installation_Code_21, 0x0704, 18, Access::W); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Installation_Code_22, 0x0711, 18, Access::W); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Installation_Code_23, 0x071E, 18, Access::W); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Installation_Code_24, 0x072B, 18, Access::W); | |||
modbusParamString.emplace_back(ModbusRegister::POC_RadioCom_Paring_Device, 0x0738, 18, Access::W); | |||
} | |||
@@ -0,0 +1,20 @@ | |||
#pragma once | |||
#include <vector> | |||
#include <string> | |||
#include <memory> | |||
#include <optional> | |||
#include <any> | |||
#include <map> | |||
#include "ModbusDataInterface.h" | |||
class ModbusDataPOC : public ModbusDataInterface | |||
{ | |||
private: | |||
public: | |||
ModbusDataPOC(const unsigned int id); | |||
static std::string getStatusMessage(unsigned short statusID); | |||
}; |
@@ -0,0 +1,213 @@ | |||
#include "ModbusInterface.h" | |||
#include "ModbusInterface.h" | |||
#include <iostream> | |||
#include <easylogging++.h> | |||
void ModbusInterface::assignSlave() { | |||
my_modbus->addSlave(slaveAddressID); | |||
my_slave = my_modbus->slavePtr(slaveAddressID); | |||
} | |||
ModbusInterface::~ModbusInterface() { | |||
if(my_modbus != nullptr){ | |||
my_modbus->close(); | |||
delete my_modbus; | |||
} | |||
} | |||
bool ModbusInterface::openConnection() { | |||
modbus_init(); | |||
//if(!(my_modbus->isOpen())) | |||
open = my_modbus->open(); | |||
if(!open){ | |||
LOG(ERROR) << "Couldn't open Modbus " << getConnectionType() << | |||
" connection at " << device; | |||
LOG(ERROR) << my_modbus->lastError(); | |||
} | |||
return open; | |||
} | |||
void ModbusInterface::disconnect() { | |||
if(my_modbus != nullptr) | |||
my_modbus->close(); | |||
} | |||
bool ModbusInterface::readBit(ParameterSpecification ¶m) { | |||
if(!param.connection->getIsOpen()){ | |||
param.error = true; | |||
return false; | |||
} | |||
bool buf; | |||
if (my_slave->readCoil(param.address, buf) != 1) { | |||
param.error = true; | |||
return 0; | |||
} | |||
else{ | |||
param.readedBytes.push_back(buf); | |||
param.error = false; | |||
return buf; | |||
} | |||
} | |||
bool ModbusInterface::readBits(ParameterSpecification ¶m) { | |||
if(!param.connection->getIsOpen()){ | |||
param.error = true; | |||
return false; | |||
} | |||
bool bufBool[param.length*8]; | |||
int ans = my_slave->readCoils(param.address, bufBool, param.length*8); | |||
if (ans != param.length*8) { | |||
param.error = true; | |||
return 0; | |||
} | |||
else{ | |||
//Big Endian decryption | |||
for(unsigned int i = 0; i < param.length; i++){ | |||
uint8_t byte = 0; | |||
for(unsigned int j = 0; j < 8; j++){ | |||
byte += bufBool[i*8 +j] << j; | |||
} | |||
param.readedBytes.push_back(byte); | |||
} | |||
param.error = false; | |||
return true; | |||
} | |||
} | |||
uint8_t ModbusInterface::readByte(ParameterSpecification ¶m) { | |||
if(!param.connection->getIsOpen()){ | |||
param.error = true; | |||
return false; | |||
} | |||
uint16_t buf; | |||
if (my_slave->readRegister(param.address, buf) != 1) { | |||
param.error = true; | |||
return 0; | |||
} | |||
else{ | |||
param.readedBytes.push_back(buf & 0x00FF); | |||
param.error = false; | |||
return static_cast<uint8_t>(buf & 0x00FF); | |||
} | |||
} | |||
uint16_t ModbusInterface::readRegister(ParameterSpecification ¶m) { | |||
if(!param.connection->getIsOpen()){ | |||
param.error = true; | |||
return false; | |||
} | |||
uint16_t buf; | |||
if (my_slave->readRegister(param.address, buf) != 1) { | |||
param.error = true; | |||
return 0; | |||
} | |||
else{ | |||
param.readedBytes.push_back(buf); | |||
param.error = false; | |||
return buf; | |||
} | |||
} | |||
uint32_t ModbusInterface::readDRegister(ParameterSpecification ¶m) { | |||
if(!param.connection->getIsOpen()){ | |||
param.error = true; | |||
return false; | |||
} | |||
uint16_t buf[2]; | |||
if (my_slave->readRegisters(param.address, buf, 2) != 2) { | |||
param.error = true; | |||
return 0; | |||
} | |||
else{ | |||
param.readedBytes.push_back(*buf); | |||
param.readedBytes.push_back(*(buf+1)); | |||
param.error = false; | |||
return _MODBUS_GET_INT32_FROM_INT16(buf, 0); | |||
} | |||
} | |||
uint64_t ModbusInterface::readQRegister(ParameterSpecification ¶m) { | |||
if(!param.connection->getIsOpen()){ | |||
param.error = true; | |||
return false; | |||
} | |||
uint16_t buf[4]; | |||
int ans = my_slave->readRegisters(param.address, buf, 4); | |||
if (ans != 4) { | |||
param.error = true; | |||
return 0; | |||
} | |||
else{ | |||
param.readedBytes.push_back(*buf); | |||
param.readedBytes.push_back(*(buf+1)); | |||
param.readedBytes.push_back(*(buf+2)); | |||
param.readedBytes.push_back(*(buf+3)); | |||
param.error = false; | |||
return _MODBUS_GET_INT64_FROM_INT16(buf, 0); | |||
} | |||
} | |||
bool ModbusInterface::writeBit(const uint16_t address, bool bit) | |||
{ | |||
if(my_slave->writeCoil(address, bit) != 1){ | |||
return false; | |||
} | |||
else | |||
return true; | |||
} | |||
bool ModbusInterface::writeByte(const uint16_t address, uint8_t byte) | |||
{ | |||
bool buf[8]; | |||
for(int i = 0; i < 8; i++){ | |||
buf[i] = byte & 0x01; | |||
byte = byte > 1; | |||
} | |||
if(my_slave->writeCoils(address, buf, 8) != 1){ | |||
return false; | |||
} | |||
else | |||
return true; | |||
} | |||
bool ModbusInterface::writeRegister(const uint16_t address, uint16_t word) | |||
{ | |||
if(my_slave->writeRegister(address, word) != 1){ | |||
return false; | |||
} | |||
else | |||
return true; | |||
} | |||
bool ModbusInterface::writeDRegister(const uint16_t address, uint32_t dword) | |||
{ | |||
uint16_t data[2]; | |||
data[0] = (dword & 0x0000FFFF); | |||
data[0] = (dword & 0xFFFF0000)>>16; | |||
if(my_slave->writeRegisters(address, data, 2) != 1){ | |||
return false; | |||
} | |||
else | |||
return true; | |||
} | |||
bool ModbusInterface::writeQRegister(const uint16_t address, uint64_t qword) | |||
{ | |||
uint16_t data[4]; | |||
data[0] = (qword & 0x0000'0000'0000'FFFF); | |||
data[1] = (qword & 0x0000'0000'FFFF'0000) >> 16; | |||
data[2] = (qword & 0x0000'FFFF'0000'0000) >> 32; | |||
data[3] = (qword & 0xFFFF'0000'0000'0000) >> 48; | |||
if(my_slave->writeRegisters(address, data, 4) != 1){ | |||
return false; | |||
} | |||
else | |||
return true; | |||
} |
@@ -0,0 +1,54 @@ | |||
#pragma once | |||
#include <string> | |||
#include <modbuspp.h> | |||
#include "ParameterInterface.h" | |||
class ModbusInterface { | |||
private: | |||
unsigned int slaveAddressID; | |||
bool open; | |||
protected: | |||
Modbus::Master* my_modbus = nullptr; | |||
Modbus::Slave* my_slave = nullptr; | |||
//IP (TCP) or serial port (RTU) | |||
std::string device; | |||
void assignSlave(); | |||
public: | |||
ModbusInterface(u_int _id, std::string _device) : slaveAddressID(_id), device(_device) { }; | |||
virtual ~ModbusInterface(); | |||
virtual void modbus_init() = 0; | |||
virtual std::string getConnectionType() = 0; | |||
bool openConnection(); | |||
void disconnect(); | |||
bool getIsOpen() const { return open; } | |||
unsigned int getID(){ return slaveAddressID; } | |||
//Read and write functions | |||
bool readBit(ParameterSpecification ¶m); | |||
bool readBits(ParameterSpecification ¶m); | |||
uint8_t readByte(ParameterSpecification ¶m); | |||
uint16_t readRegister(ParameterSpecification ¶m); | |||
uint32_t readDRegister(ParameterSpecification ¶m); | |||
uint64_t readQRegister(ParameterSpecification ¶m); | |||
bool writeBit(const uint16_t address, bool bit); | |||
bool writeByte(const uint16_t address, uint8_t byte); | |||
bool writeRegister(const uint16_t address, uint16_t word); | |||
bool writeDRegister(const uint16_t address, uint32_t dword); | |||
bool writeQRegister(const uint16_t address, uint64_t qword); | |||
}; | |||
#define _MODBUS_GET_INT64_FROM_INT16(tab_int16, index) \ | |||
(((int64_t)tab_int16[(index) ] << 48) + \ | |||
((int64_t)tab_int16[(index) + 1] << 32) + \ | |||
((int64_t)tab_int16[(index) + 2] << 16) + \ | |||
(int64_t)tab_int16[(index) + 3]) | |||
#define _MODBUS_GET_INT32_FROM_INT16(tab_int16, index) ((tab_int16[(index)] << 16) + tab_int16[(index) + 1]) | |||
#define _MODBUS_GET_INT16_FROM_INT8(tab_int8, index) ((tab_int8[(index)] << 8) + tab_int8[(index) + 1]) |
@@ -0,0 +1,283 @@ | |||
#pragma once | |||
enum class Category { | |||
//Category for collective reading accesses | |||
NONE, | |||
Condition, | |||
Diagnosis, | |||
Alert | |||
}; | |||
enum class ModbusRegister { | |||
//---------------------Powercenter---------------------------- | |||
//Information for identification | |||
POC_Ident_Manufacturer, | |||
POC_Ident_Ordernumber, | |||
POC_Ident_Seriesnumber, | |||
POC_Ident_Hardware_Version, | |||
POC_Ident_Software_Version, | |||
POC_Ident_Plant_identification_code, | |||
POC_Ident_Installation_site, | |||
POC_Ident_Installation_date, | |||
POC_Ident_Firmware_Applicationcontroller, | |||
POC_Ident_Market, | |||
POC_Ident_Main_device_rated_current, | |||
POC_Ident_Tripping_curve_characteristic, | |||
POC_Ident_Installation_place_fuse, | |||
POC_Ident_MLFB_fuse, | |||
POC_Ident_Hardware_Electronics, | |||
POC_Ident_rated_current_melting_part, | |||
//IP Information | |||
POC_IP_Ethernet_MAC, | |||
POC_IP_Status_DHCP, | |||
POC_IP_SNTP_server_ip, | |||
POC_IP_SNTP_client_mode, | |||
POC_IP_Status_firewall, | |||
POC_IP_port_number, | |||
POC_IP_Static_IP, | |||
POC_IP_Subnetmask, | |||
POC_IP_Gateway, | |||
POC_IP_Current_IP, | |||
POC_IP_Current_Subnet, | |||
POC_IP_Current_Gateway, | |||
//Bluetooth Information | |||
POC_BT_Status, | |||
POC_BT_send_power, | |||
POC_BT_device_address, | |||
POC_BT_passkey, | |||
//Radio Information | |||
POC_Radio_Date_time_utc, | |||
POC_Radio_Paring_status_1, | |||
POC_Radio_Paring_status_2, | |||
POC_Radio_Paring_status_3, | |||
POC_Radio_Paring_status_4, | |||
POC_Radio_Paring_status_5, | |||
POC_Radio_Paring_status_6, | |||
POC_Radio_Paring_status_7, | |||
POC_Radio_Paring_status_8, | |||
POC_Radio_Paring_status_9, | |||
POC_Radio_Paring_status_10, | |||
POC_Radio_Paring_status_11, | |||
POC_Radio_Paring_status_12, | |||
POC_Radio_Paring_status_13, | |||
POC_Radio_Paring_status_14, | |||
POC_Radio_Paring_status_15, | |||
POC_Radio_Paring_status_16, | |||
POC_Radio_Paring_status_17, | |||
POC_Radio_Paring_status_18, | |||
POC_Radio_Paring_status_19, | |||
POC_Radio_Paring_status_20, | |||
POC_Radio_Paring_status_21, | |||
POC_Radio_Paring_status_22, | |||
POC_Radio_Paring_status_23, | |||
POC_Radio_Paring_status_24, | |||
POC_Radio_transmit_power, | |||
POC_Radio_Device_status_1, | |||
POC_Radio_Device_status_2, | |||
POC_Radio_Device_status_3, | |||
POC_Radio_Device_status_4, | |||
POC_Radio_Device_status_5, | |||
POC_Radio_Device_status_6, | |||
POC_Radio_Device_status_7, | |||
POC_Radio_Device_status_8, | |||
POC_Radio_Device_status_9, | |||
POC_Radio_Device_status_10, | |||
POC_Radio_Device_status_11, | |||
POC_Radio_Device_status_12, | |||
POC_Radio_Device_status_13, | |||
POC_Radio_Device_status_14, | |||
POC_Radio_Device_status_15, | |||
POC_Radio_Device_status_16, | |||
POC_Radio_Device_status_17, | |||
POC_Radio_Device_status_18, | |||
POC_Radio_Device_status_19, | |||
POC_Radio_Device_status_20, | |||
POC_Radio_Device_status_21, | |||
POC_Radio_Device_status_22, | |||
POC_Radio_Device_status_23, | |||
POC_Radio_Device_status_24, | |||
POC_Radio_Time_sync_to_POC, | |||
//Radio Communication Information | |||
POC_RadioCom_Mac_address_device_1, | |||
POC_RadioCom_Mac_address_device_2, | |||
POC_RadioCom_Mac_address_device_3, | |||
POC_RadioCom_Mac_address_device_4, | |||
POC_RadioCom_Mac_address_device_5, | |||
POC_RadioCom_Mac_address_device_6, | |||
POC_RadioCom_Mac_address_device_7, | |||
POC_RadioCom_Mac_address_device_8, | |||
POC_RadioCom_Mac_address_device_9, | |||
POC_RadioCom_Mac_address_device_10, | |||
POC_RadioCom_Mac_address_device_11, | |||
POC_RadioCom_Mac_address_device_12, | |||
POC_RadioCom_Mac_address_device_13, | |||
POC_RadioCom_Mac_address_device_14, | |||
POC_RadioCom_Mac_address_device_15, | |||
POC_RadioCom_Mac_address_device_16, | |||
POC_RadioCom_Mac_address_device_17, | |||
POC_RadioCom_Mac_address_device_18, | |||
POC_RadioCom_Mac_address_device_19, | |||
POC_RadioCom_Mac_address_device_20, | |||
POC_RadioCom_Mac_address_device_21, | |||
POC_RadioCom_Mac_address_device_22, | |||
POC_RadioCom_Mac_address_device_23, | |||
POC_RadioCom_Mac_address_device_24, | |||
//Measurement Settings | |||
POC_Measurement_Time_period_temperature, | |||
POC_Measurement_Activate_temp_alert, | |||
POC_Measurement_Limit_temperature_alert, | |||
POC_Measurement_Hysteresis_temperature_alert, | |||
POC_Measurement_I_Averaging_interval_s, | |||
//Current Alerts | |||
POC_Measurement_I_alert1_over_on_off, | |||
POC_Measurement_I_alert1_over_limit_percentage, | |||
POC_Measurement_I_alert1_over_hysteresis, | |||
POC_Measurement_I_alert2_over_on_off, | |||
POC_Measurement_I_alert2_over_limit_percentage, | |||
POC_Measurement_I_alert2_over_hysteresis, | |||
POC_Measurement_I_alert1_under_on_off, | |||
POC_Measurement_I_alert1_under_limit_percentage, | |||
POC_Measurement_I_alert1_under_hysteresis, | |||
POC_Measurement_I_alert2_under_on_off, | |||
POC_Measurement_I_alert2_under_limit_percentage, | |||
POC_Measurement_I_alert2_under_hysteresis, | |||
//Voltage Alerts | |||
POC_Measurement_U_alert1_over_on_off, | |||
POC_Measurement_U_alert1_over_limit_percentage, | |||
POC_Measurement_U_alert1_over_hysteresis, | |||
POC_Measurement_U_alert2_over_on_off, | |||
POC_Measurement_U_alert2_over_limit_percentage, | |||
POC_Measurement_U_alert2_over_hysteresis, | |||
POC_Measurement_U_alert1_under_on_off, | |||
POC_Measurement_U_alert1_under_limit_percentage, | |||
POC_Measurement_U_alert1_under_hysteresis, | |||
POC_Measurement_U_alert2_under_on_off, | |||
POC_Measurement_U_alert2_under_limit_percentage, | |||
POC_Measurement_U_alert2_under_hysteresis, | |||
POC_Measurement_Energy_flow_direction, | |||
POC_Measurement_Alert_on_off_AFDD_threshold_shortfall, | |||
//Switch gears condition | |||
POC_Cond_Temp, //°C | |||
POC_Cond_Temp_avg, //°C | |||
POC_Cond_I_Phase, //A | |||
POC_Cond_I_Phase_avg, //A | |||
POC_Cond_I_Phase_max, //A | |||
POC_Cond_U_L_N, //V | |||
POC_Cond_Freq, //Hz | |||
POC_Cond_P, //W | |||
POC_Cond_S, //W | |||
POC_Cond_Q, //Var | |||
POC_Cond_Cos_phi, | |||
POC_Cond_P_in, //Wh | |||
POC_Cond_P_ab, //Wh | |||
POC_Cond_S_in, //Varh | |||
POC_Cond_S_ab, //Varh | |||
POC_Cond_Status, | |||
//Powercenter Diagnosis | |||
POC_Diag_Alert, | |||
//Hours run with current flow | |||
POC_Diag_H_run_with_I, //s | |||
POC_Diag_Alert_on_off_run_total_with_I, | |||
POC_Diag_Min_load_current, | |||
POC_Diag_Limit_run_hours_with_I_alert, | |||
POC_Diag_H_run_total, //s | |||
POC_Diag_Alert_on_off_run_total, | |||
POC_Diag_Limit_run_hours_total_alert, | |||
POC_Diag_Nr_of_mechanical_switchings, | |||
POC_Diag_Alert_on_off_nr_mechanical_switchings, | |||
POC_Diag_Limit_mechanical_switchings, | |||
POC_Diag_Nr_of_triggered_switches, | |||
POC_Diag_Alert_on_off_nr_triggered_switchings, | |||
POC_Diag_Limit_triggered_switchings, | |||
//Received Signal Strength Indicator in dbm | |||
POC_Diag_RSSI_BLE, | |||
POC_Diag_RSSI_radio, | |||
POC_Diag_Nr_of_short_circuit_triggers, | |||
POC_Diag_Alert_on_off_nr_short_circuit_triggers, | |||
POC_Diag_Limit_shoer_circuit_triggers, | |||
POC_Diag_Time_and_sync_status, | |||
//Radio communication settings | |||
POC_RadioCom_Installation_Code_1, | |||
POC_RadioCom_Installation_Code_2, | |||
POC_RadioCom_Installation_Code_3, | |||
POC_RadioCom_Installation_Code_4, | |||
POC_RadioCom_Installation_Code_5, | |||
POC_RadioCom_Installation_Code_6, | |||
POC_RadioCom_Installation_Code_7, | |||
POC_RadioCom_Installation_Code_8, | |||
POC_RadioCom_Installation_Code_9, | |||
POC_RadioCom_Installation_Code_10, | |||
POC_RadioCom_Installation_Code_11, | |||
POC_RadioCom_Installation_Code_12, | |||
POC_RadioCom_Installation_Code_13, | |||
POC_RadioCom_Installation_Code_14, | |||
POC_RadioCom_Installation_Code_15, | |||
POC_RadioCom_Installation_Code_16, | |||
POC_RadioCom_Installation_Code_17, | |||
POC_RadioCom_Installation_Code_18, | |||
POC_RadioCom_Installation_Code_19, | |||
POC_RadioCom_Installation_Code_20, | |||
POC_RadioCom_Installation_Code_21, | |||
POC_RadioCom_Installation_Code_22, | |||
POC_RadioCom_Installation_Code_23, | |||
POC_RadioCom_Installation_Code_24, | |||
POC_RadioCom_Paring_Device, | |||
POC_RadioCom_Paring_device_1, | |||
POC_RadioCom_Paring_device_2, | |||
POC_RadioCom_Paring_device_3, | |||
POC_RadioCom_Paring_device_4, | |||
POC_RadioCom_Paring_device_5, | |||
POC_RadioCom_Paring_device_6, | |||
POC_RadioCom_Paring_device_7, | |||
POC_RadioCom_Paring_device_8, | |||
POC_RadioCom_Paring_device_9, | |||
POC_RadioCom_Paring_device_10, | |||
POC_RadioCom_Paring_device_11, | |||
POC_RadioCom_Paring_device_12, | |||
POC_RadioCom_Paring_device_13, | |||
POC_RadioCom_Paring_device_14, | |||
POC_RadioCom_Paring_device_15, | |||
POC_RadioCom_Paring_device_16, | |||
POC_RadioCom_Paring_device_17, | |||
POC_RadioCom_Paring_device_18, | |||
POC_RadioCom_Paring_device_19, | |||
POC_RadioCom_Paring_device_20, | |||
POC_RadioCom_Paring_device_21, | |||
POC_RadioCom_Paring_device_22, | |||
POC_RadioCom_Paring_device_23, | |||
POC_RadioCom_Paring_device_24, | |||
//---------------------Bender---------------------------- | |||
//Test variable to be read from virtual RCM device | |||
BENDER_Residual_current, | |||
}; | |||
namespace CMD { | |||
enum class Identification { | |||
Trigger_flash_light, | |||
}; | |||
enum class IP { | |||
Apply_ethernet_configuration_changes, | |||
}; | |||
enum class Bluetooth { | |||
Switch_on_off_BT_Reset_passkey, | |||
}; | |||
enum class MeasurementSettings { | |||
Reset_energy_counter, | |||
Reset_extrem_values, | |||
}; | |||
} |
@@ -0,0 +1,31 @@ | |||
#include "ModbusRtu.h" | |||
#include <sstream> | |||
inline std::string ModbusRTU::getSettingsString() const | |||
{ | |||
return std::to_string(baud) + pairity + std::to_string(stopBit); | |||
} | |||
ModbusRTU::~ModbusRTU() | |||
{ | |||
//if(my_slave != nullptr){ | |||
// delete my_slave; | |||
//} | |||
//if(my_modbus != nullptr){ | |||
// my_modbus->close(); | |||
// delete my_modbus; | |||
//} | |||
} | |||
ModbusRTU::ModbusRTU(const u_int id, const std::string _device, const unsigned int _baud, const char _pairity, const unsigned int _stopBit) | |||
: ModbusInterface(id, _device), baud(_baud), pairity(_pairity), stopBit(_stopBit) | |||
{ | |||
} | |||
void ModbusRTU::modbus_init() { | |||
if(my_modbus == nullptr){ | |||
my_modbus = new Modbus::Master(Modbus::Rtu, device, getSettingsString()); | |||
assignSlave(); | |||
} | |||
} |
@@ -0,0 +1,26 @@ | |||
#pragma once | |||
#include <modbuspp.h> | |||
#include "ModbusInterface.h" | |||
class ModbusRTU : | |||
public ModbusInterface | |||
{ | |||
private: | |||
unsigned int baud; | |||
char pairity; | |||
unsigned int stopBit; | |||
//Create settings string | |||
std::string getSettingsString() const; | |||
public: | |||
ModbusRTU(const u_int id, const std::string _device, const u_int _baud, const char _pairity, const u_int _stopBit); | |||
virtual ~ModbusRTU(); | |||
virtual void modbus_init() override; | |||
std::string getConnectionType()override { return "Rtu"; } | |||
}; | |||
@@ -0,0 +1,25 @@ | |||
#include "ModbusTcp.h" | |||
ModbusTCP::ModbusTCP(const u_int id, const std::string _ip, const unsigned int _port) | |||
: ModbusInterface(id, _ip), port(std::to_string(_port)) | |||
{ | |||
} | |||
ModbusTCP::~ModbusTCP() | |||
{ | |||
//if(my_slave != nullptr){ | |||
// delete my_slave; | |||
//} | |||
//if(my_modbus != nullptr){ | |||
// my_modbus->close(); | |||
// delete my_modbus; | |||
//} | |||
} | |||
void ModbusTCP::modbus_init() { | |||
if (my_modbus == nullptr){ | |||
my_modbus = new Modbus::Master(Modbus::Tcp, device, port); | |||
assignSlave(); | |||
} | |||
} |
@@ -0,0 +1,20 @@ | |||
#pragma once | |||
#include <modbuspp.h> | |||
#include "ModbusInterface.h" | |||
class ModbusTCP : | |||
public ModbusInterface | |||
{ | |||
private: | |||
std::string port = "502"; | |||
public: | |||
ModbusTCP(const u_int id, const std::string _ip, const u_int _port); | |||
virtual ~ModbusTCP(); | |||
virtual void modbus_init() override; | |||
std::string getConnectionType()override { return "Tcp"; } | |||
}; | |||
@@ -0,0 +1,128 @@ | |||
#include "NetServer.h" | |||
#include <easylogging++.h> | |||
#include "DataModel.h" | |||
void NetServer::Stop() | |||
{ | |||
net::ServerInterface<net::MessageTypes>::Stop(); | |||
LOG(INFO) << "[SERVER] Stopped Server"; | |||
serverRunning = false; | |||
} | |||
bool NetServer::Start() | |||
{ | |||
if(serverRunning){ | |||
LOG(WARNING) << "[SERVER] tried to start ip tcp server, but it is already running"; | |||
return true; | |||
} | |||
bool res = net::ServerInterface<net::MessageTypes>::Start(); | |||
if(res){ | |||
LOG(INFO) << "[SERVER] Started Server"; | |||
serverRunning = true; | |||
return res; | |||
} | |||
LOG(ERROR) << "[SERVER] Couldn't start Server"; | |||
serverRunning = false; | |||
return res; | |||
} | |||
bool NetServer::OnClientConnect(std::shared_ptr<net::Connection<net::MessageTypes>> client) | |||
{ | |||
//Decide whether to accept or deny connection (always accept in this case) | |||
net::Message<net::MessageTypes> msg; | |||
msg.header.id = net::MessageTypes::ServerAccept; | |||
client->Send(msg); | |||
return true; | |||
} | |||
// Called when a client appears to have disconnected | |||
void NetServer::OnClientDisconnect(std::shared_ptr<net::Connection<net::MessageTypes>> client) | |||
{ | |||
LOG(INFO) << "Removing client [" << client->GetID() << "]"; | |||
} | |||
// Called when a message arrives | |||
void NetServer::OnMessage(std::shared_ptr<net::Connection<net::MessageTypes>> client, net::Message<net::MessageTypes>& msgRcv) | |||
{ | |||
net::Message<net::MessageTypes> msgSend; | |||
std::stringstream body; | |||
switch (msgRcv.header.id) | |||
{ | |||
case net::MessageTypes::ServerData: | |||
LOG(INFO) << "[Client " << client->GetID() << "]: Request Server Data"; | |||
msgSend.header.id = net::MessageTypes::ServerData; | |||
getBodyData(body); | |||
msgSend << body.str(); | |||
client->Send(msgSend); | |||
break; | |||
case net::MessageTypes::ServerCondition: | |||
LOG(INFO) << "[Client " << client->GetID() << "]: Request temporary Condition Data"; | |||
msgSend.header.id = net::MessageTypes::ServerCondition; | |||
getBodyCondition(body); | |||
msgSend << body.str(); | |||
client->Send(msgSend); | |||
break; | |||
case net::MessageTypes::ServerAlert: | |||
LOG(INFO) << "[Client " << client->GetID() << "]: Request Alert Data"; | |||
msgSend.header.id = net::MessageTypes::ServerAlert; | |||
client->Send(msgSend); | |||
break; | |||
case net::MessageTypes::ServerLog: | |||
LOG(INFO) << "[Client " << client->GetID() << "]: Request Log Files"; | |||
msgSend.header.id = net::MessageTypes::ServerLog; | |||
getBodyLog(body, msgRcv); | |||
msgSend << body.str(); | |||
client->Send(msgSend); | |||
break; | |||
case net::MessageTypes::ServerCloseConnection: | |||
LOG(INFO) << "[Client " << client->GetID() << "]: Request disconnection"; | |||
client->Disconnect(); | |||
break; | |||
default: | |||
LOG(ERROR) << "[Client " << client->GetID() << "]: Invaild reuqest code"; | |||
break; | |||
} | |||
} | |||
void NetServer::notifyAllConnections(net::Message<net::MessageTypes> &message) | |||
{ | |||
LOG(INFO) << "Sent broadcast to "; | |||
unsigned int counter = 0; | |||
//Send message to all listed active connections | |||
for(auto &connection: m_deqConnections){ | |||
connection->Send(message); | |||
counter++; | |||
} | |||
LOG(INFO) << "Sent broadcast to " << counter << " devices"; | |||
} | |||
std::stringstream& NetServer::getBodyData(std::stringstream& buffer) | |||
{ | |||
DataModel::Instance()->readAllDataFiles(buffer); | |||
DataModel::Instance()->readPermanentData(buffer, false); | |||
return buffer; | |||
} | |||
std::stringstream& NetServer::getBodyLog(std::stringstream& buffer, net::Message<net::MessageTypes> msgRcv) | |||
{ | |||
unsigned long long fromTime; | |||
if (msgRcv.header.size == 0) | |||
fromTime = 0; | |||
else | |||
msgRcv >> fromTime; | |||
DataModel::Instance()->readLogFile(buffer, fromTime); | |||
return buffer; | |||
} | |||
std::stringstream& NetServer::getBodyCondition(std::stringstream& buffer) | |||
{ | |||
DataModel::Instance()->readTemporaryData(buffer); | |||
return buffer; | |||
} | |||
@@ -0,0 +1,54 @@ | |||
#pragma once | |||
#include <sstream> | |||
#include <asio.hpp> | |||
#include "net_server_client.h" | |||
namespace net { | |||
enum class MessageTypes : uint32_t | |||
{ | |||
ServerAccept, | |||
ServerDeny, | |||
ServerData, | |||
ServerLog, | |||
ServerCondition, | |||
ServerAlert, | |||
ServerCloseConnection, | |||
}; | |||
} | |||
class NetServer : public net::ServerInterface<net::MessageTypes> | |||
{ | |||
public: | |||
NetServer(uint16_t nPort) : net::ServerInterface<net::MessageTypes>(nPort) {} | |||
NetServer() = delete; | |||
NetServer(NetServer&) = delete; | |||
void Stop() override; | |||
bool Start() override; | |||
bool isRunning(){return serverRunning;} | |||
void notifyAllConnections(net::Message<net::MessageTypes>& message); | |||
bool serverRunning = false; | |||
protected: | |||
virtual bool OnClientConnect(std::shared_ptr<net::Connection<net::MessageTypes>> client) override; | |||
// Called when a client appears to have disconnected | |||
virtual void OnClientDisconnect(std::shared_ptr<net::Connection<net::MessageTypes>> client) override; | |||
// Called when a message arrives | |||
virtual void OnMessage(std::shared_ptr<net::Connection<net::MessageTypes>> client, net::Message<net::MessageTypes>& msg) override; | |||
private: | |||
//Functions to collect the data for response message body | |||
//Collects Data message body | |||
std::stringstream& getBodyData(std::stringstream& buffer); | |||
//Collects Lof files message body | |||
std::stringstream& getBodyLog(std::stringstream& buffer, net::Message<net::MessageTypes> msgRcv); | |||
//Collects Electrical Condition values message body | |||
std::stringstream& getBodyCondition(std::stringstream& buffer); | |||
}; |
@@ -0,0 +1 @@ | |||
#include "ParameterCharP.h" |
@@ -0,0 +1,15 @@ | |||
#pragma once | |||
#include "ParameterInterface.h" | |||
class ParameterCharP : public ParameterInterface<char*> | |||
{ | |||
private: | |||
char* param = nullptr; | |||
const uint8_t length; | |||
public: | |||
ParameterCharP(const ModbusRegister description, const uint16_t address, const uint8_t _length, const Access access) : ParameterInterface(description, address, access), length(_length) {}; | |||
ParameterCharP(const ModbusRegister description, const unsigned short address, const uint8_t _length, const Access access, const Category cat) : ParameterInterface(description, address, access, cat), length(_length) {}; | |||
virtual ~ParameterCharP(){} | |||
uint8_t getSize() const override { return length; } | |||
}; |
@@ -0,0 +1,2 @@ | |||
#include "ParameterDouble.h" | |||
@@ -0,0 +1,16 @@ | |||
#pragma once | |||
#include "ParameterInterface.h" | |||
typedef double FP64; | |||
class ParameterDouble : public ParameterInterface<FP64> | |||
{ | |||
private: | |||
FP64 param = 0.0; | |||
public: | |||
ParameterDouble(const ModbusRegister description, uint16_t address, Access access) : ParameterInterface(description, address, access) {}; | |||
ParameterDouble(const ModbusRegister description, const unsigned short address, const Access access, const Category cat) : ParameterInterface(description, address, access, cat) {}; | |||
virtual ~ParameterDouble(){} | |||
uint8_t getSize() const override { return 64; } | |||
}; |
@@ -0,0 +1,2 @@ | |||
#include "ParameterFloat.h" | |||
@@ -0,0 +1,17 @@ | |||
#pragma once | |||
#include "ParameterInterface.h" | |||
typedef float FP32; | |||
class ParameterFloat : public ParameterInterface<FP32> | |||
{ | |||
private: | |||
public: | |||
ParameterFloat(const ModbusRegister description, uint16_t address, Access access) : ParameterInterface(description, address, access) {}; | |||
ParameterFloat(const ModbusRegister description, const unsigned short address, const Access access, const Category cat) : ParameterInterface(description, address, access, cat) {}; | |||
virtual ~ParameterFloat(){} | |||
uint8_t getSize() const override { return 32; } | |||
}; | |||
@@ -0,0 +1,58 @@ | |||
#pragma once | |||
#include "ModbusRegister.h" | |||
#include <optional> | |||
#include <vector> | |||
#include <memory> | |||
class ModbusInterface; | |||
enum class Access { | |||
//Access Permission of a given MODBUS Parameter | |||
R, | |||
W, | |||
RW, | |||
CMD | |||
}; | |||
struct ParameterSpecification { | |||
ModbusRegister description; | |||
Category cat; | |||
uint16_t address; | |||
uint8_t length; | |||
Access access; | |||
std::vector<uint16_t> readedBytes; | |||
bool error = true; | |||
std::unique_ptr<ModbusInterface>& connection; | |||
ParameterSpecification(ModbusRegister d, Category c, uint16_t ad, uint8_t l, std::unique_ptr<ModbusInterface>& i, Access ac) : | |||
description(d), cat(c), address(ad), length(l), access(ac), connection(i) {} | |||
bool isReadable(){ return access == Access::R || access == Access::RW;} | |||
bool isWritable(){ return access == Access::W || access == Access::RW || access == Access::CMD;} | |||
}; | |||
template<typename S> | |||
class ParameterInterface | |||
{ | |||
protected: | |||
S currentParam{}; | |||
const ModbusRegister valueDescription; | |||
const uint16_t modbusAddress; | |||
const Access access; | |||
const Category cat = Category::NONE; | |||
public: | |||
ParameterInterface(const ModbusRegister description, const unsigned short address, const Access access) : valueDescription(description), modbusAddress(address), access(access) {}; | |||
ParameterInterface(const ModbusRegister description, const unsigned short address, const Access access, const Category _cat) : valueDescription(description), modbusAddress(address), access(access), cat(_cat) {}; | |||
virtual ~ParameterInterface(){} | |||
bool isReadable() { return (access == Access::R || access == Access::RW); } | |||
S getParam()const { return currentParam; }; | |||
uint16_t getModbusAddress() { return modbusAddress; } | |||
ModbusRegister getValueDescription() { return valueDescription; }; | |||
Category getCategory()const {return cat;} | |||
virtual uint8_t getSize() const = 0; | |||
ParameterSpecification getSpecification(std::unique_ptr<ModbusInterface>& connection) const { return ParameterSpecification(valueDescription, cat, modbusAddress, getSize(), connection, access); } | |||
}; |
@@ -0,0 +1,2 @@ | |||
#include "ParameterS16.h" | |||
@@ -0,0 +1,15 @@ | |||
#pragma once | |||
#include "ParameterInterface.h" | |||
class ParameterS16 : public ParameterInterface<int16_t> | |||
{ | |||
private: | |||
int16_t param = 0; | |||
public: | |||
ParameterS16(const ModbusRegister description, uint16_t address, Access access) : ParameterInterface(description, address, access) {}; | |||
ParameterS16(const ModbusRegister description, const unsigned short address, const Access access, const Category cat) : ParameterInterface(description, address, access, cat) {}; | |||
virtual ~ParameterS16(){} | |||
uint8_t getSize() const override { return 16; } | |||
}; |
@@ -0,0 +1,2 @@ | |||
#include "ParameterUInt16.h" | |||
@@ -0,0 +1,15 @@ | |||
#pragma once | |||
#include "ParameterInterface.h" | |||
class ParameterUInt16 : public ParameterInterface<uint16_t> | |||
{ | |||
private: | |||
uint16_t param = 0; | |||
public: | |||
ParameterUInt16(const ModbusRegister description, uint16_t address, Access access) : ParameterInterface(description, address, access) {}; | |||
ParameterUInt16(const ModbusRegister description, const unsigned short address, const Access access, const Category cat) : ParameterInterface(description, address, access, cat) {}; | |||
virtual ~ParameterUInt16(){} | |||
uint8_t getSize() const override { return 16; } | |||
}; |
@@ -0,0 +1,2 @@ | |||
#include "ParameterUInt32.h" | |||
@@ -0,0 +1,14 @@ | |||
#pragma once | |||
#include "ParameterInterface.h" | |||
class ParameterUInt32 : public ParameterInterface<uint32_t> | |||
{ | |||
private: | |||
uint32_t param = 0; | |||
public: | |||
ParameterUInt32(const ModbusRegister description, uint16_t address, Access access) : ParameterInterface(description, address, access) {}; | |||
ParameterUInt32(const ModbusRegister description, const unsigned short address, const Access access, const Category cat) : ParameterInterface(description, address, access, cat) {}; | |||
virtual ~ParameterUInt32(){} | |||
uint8_t getSize() const override { return 32; } | |||
}; |
@@ -0,0 +1,20 @@ | |||
#include "PublisherBenderRcm.h" | |||
#include "SystemConfig.h" | |||
PublisherBenderRcm::PublisherBenderRcm() : PublisherInterface(PublisherType::RCMS_BENDER) | |||
{ | |||
connection = std::make_unique<ModbusRTU>(SystemConfig::getIntConfigParameter("modbus_rtu_slave_address"), | |||
SystemConfig::getStringConfigParameter("modbus_rtu_device"), | |||
SystemConfig::getIntConfigParameter("modbus_rtu_baud"), | |||
SystemConfig::getStringConfigParameter("modbus_rtu_pairity").at(0), | |||
SystemConfig::getIntConfigParameter("modbus_rtu_stop_bits")); | |||
modbusData = std::make_unique<ModbusDataBender>(this->id); | |||
} | |||
std::string PublisherBenderRcm::getName() const | |||
{ | |||
std::stringstream name; | |||
name << "RCM - Bender (ID: " << id << ")"; | |||
return name.str(); | |||
} |
@@ -0,0 +1,18 @@ | |||
#pragma once | |||
#include <memory> | |||
#include "PublisherType.h" | |||
#include "ModbusDataBender.h" | |||
#include "PublisherInterface.h" | |||
#include "modbus_interface_lib.h" | |||
class PublisherBenderRcm : | |||
public PublisherInterface | |||
{ | |||
private: | |||
public: | |||
PublisherBenderRcm(); | |||
std::string getName()const override; | |||
}; | |||
@@ -0,0 +1,30 @@ | |||
#include "PublisherData.h" | |||
PublisherData::PublisherData(const int _publisherID, PublisherType pubType, float _value) : | |||
publisherID(_publisherID), publisherType(pubType), values(std::vector<float>()) | |||
{ | |||
values.push_back(_value); | |||
} | |||
PublisherData::PublisherData(const int _publisherID, PublisherType pubType, std::vector<float> _values) : | |||
publisherID(_publisherID), publisherType(pubType), values(_values) | |||
{ | |||
} | |||
PublisherData::PublisherData() : | |||
publisherID(0), publisherType(PublisherType::NA), values({ 0.0 }) | |||
{ | |||
} | |||
std::ostream& operator<<(std::ostream& os, const PublisherData& data){ | |||
for (u_int i = 0; i < data.values.size()-1; i++) { | |||
os << data.values[i] << ","; | |||
} | |||
os << data.values[data.values.size()-1] << ";"; | |||
return os; | |||
} |
@@ -0,0 +1,42 @@ | |||
#pragma once | |||
#include <chrono> | |||
#include <vector> | |||
#include <iostream> | |||
#include "PublisherType.h" | |||
using namespace std::chrono; | |||
typedef unsigned int u_int; | |||
class PublisherData | |||
{ | |||
private: | |||
u_int publisherID; | |||
const PublisherType publisherType; | |||
system_clock::time_point time_generated; | |||
std::vector<float> values; | |||
public: | |||
//constructors | |||
PublisherData(const int _publisherID, PublisherType pubType, float value); | |||
PublisherData(const int _publisherID, PublisherType pubType, std::vector<float> _values); | |||
PublisherData(); | |||
//void updateTime() { this->time_generated = system_clock::now(); } | |||
inline void addData(const float data) { values.push_back(data); } | |||
u_int getID() { return publisherID; } | |||
std::vector<float>& getValues() { return values; }; | |||
//getter/setter | |||
PublisherType getType()const { return publisherType; } | |||
system_clock::time_point getTimeGenerated()const { return time_generated; } | |||
void setTimeGenerated(const std::chrono::system_clock::time_point t) { time_generated = t; } | |||
bool hasData()const { return values.size() == 0 ? false : true; } | |||
//operator | |||
friend std::ostream& operator<<(std::ostream&, const PublisherData&); | |||
friend std::istream& operator>>(std::istream&, PublisherData&); | |||
}; | |||
@@ -0,0 +1,19 @@ | |||
#include "PublisherInterface.h" | |||
#include <easylogging++.h> | |||
u_int PublisherInterface::id_static = 0; | |||
PublisherInterface::PublisherInterface(const PublisherType type) | |||
: id(id_static++), type(type) | |||
{} | |||
bool PublisherInterface::operator==(const PublisherInterface& p2) const{ | |||
return this->id == p2.id; | |||
} | |||
ts_queue<ParameterSpecification>& PublisherInterface::enqueueReadingRegisters(ts_queue< ParameterSpecification>& queue, Category cat){ | |||
modbusData->modbusRegisterCat(cat, queue, connection); | |||
return queue; | |||
} |
@@ -0,0 +1,35 @@ | |||
#pragma once | |||
#include <memory> | |||
#include "PublisherData.h" | |||
#include "ModbusDataInterface.h" | |||
#include "modbus_interface_lib.h" | |||
#include "ts_queue.h" | |||
#include "SystemConfig.h" | |||
class PublisherInterface | |||
{ | |||
private: | |||
static u_int id_static; | |||
protected: | |||
const u_int id; | |||
const PublisherType type; | |||
std::unique_ptr<ModbusInterface> connection; | |||
std::unique_ptr<ModbusDataInterface> modbusData; | |||
public: | |||
PublisherInterface(const PublisherType type); | |||
virtual ~PublisherInterface(){} | |||
ts_queue<ParameterSpecification>& enqueueReadingRegisters(ts_queue<ParameterSpecification>& queue, Category cat); | |||
virtual std::string getName()const = 0; | |||
bool open(){ return connection->openConnection(); } | |||
bool isOpen() const { return connection->getIsOpen(); } | |||
u_int getID()const { return id; } | |||
//operator overloading | |||
inline bool operator==(const PublisherInterface& p2)const; | |||
}; |
@@ -0,0 +1,26 @@ | |||
#include "PublisherPowercenter.h" | |||
#include <easylogging++.h> | |||
PublisherPowercenter::PublisherPowercenter() : PublisherInterface(PublisherType::POWERCENTER) | |||
{ | |||
connection = std::make_unique<ModbusTCP>(SystemConfig::getIntConfigParameter("modbus_tcp_slave_address"), | |||
SystemConfig::getStringConfigParameter("modbus_poc_ip"), | |||
SystemConfig::getIntConfigParameter("modbus_poc_port")); | |||
modbusData = std::make_unique<ModbusDataPOC>(this->id); | |||
} | |||
PublisherPowercenter::~PublisherPowercenter() { | |||
//modbus->disconnect(); | |||
} | |||
std::string PublisherPowercenter::getName() const | |||
{ | |||
std::stringstream name; | |||
name << "Powercenter - Siemens (ID: " << id << ")"; | |||
return name.str(); | |||
} |
@@ -0,0 +1,16 @@ | |||
#pragma once | |||
#include "PublisherInterface.h" | |||
#include "ModbusDataPOC.h" | |||
#include <memory> | |||
class PublisherPowercenter : | |||
public PublisherInterface | |||
{ | |||
private: | |||
public: | |||
PublisherPowercenter(); | |||
virtual ~PublisherPowercenter(); | |||
std::string getName()const override; | |||
}; |
@@ -0,0 +1,7 @@ | |||
#pragma once | |||
enum class PublisherType{ | |||
NA = 0, | |||
RCMS_BENDER, | |||
POWERCENTER | |||
}; |
@@ -0,0 +1,137 @@ | |||
#include <sstream> | |||
#include <regex> | |||
#include "SystemConfig.h" | |||
#include "CustomStringUtilities.h" | |||
std::map<std::string, int> SystemConfig::intParameter; | |||
std::map<std::string, float> SystemConfig::floatParameter; | |||
std::map<std::string, std::string> SystemConfig::stringParameter; | |||
std::ostream& operator<<(std::ostream& os, SystemConfig& config) { | |||
os << "System Configuration: " << std::endl; | |||
std::for_each(config.intParameter.begin(), config.intParameter.end(), | |||
[&config, &os](auto& param) { os << "\t"<< param.first << ":\t" << param.second << std::endl; }); | |||
return os; | |||
} | |||
//Read in the data from the config file | |||
void SystemConfig::readStandardConfig() | |||
{ | |||
std::fstream fileStream(filePath, std::ios::in); | |||
if (fileStream.is_open()) { | |||
std::stringstream buf; | |||
while (std::getline(fileStream, fileContent)) | |||
buf << fileContent << '\n'; | |||
fileContent = buf.str(); | |||
fileStream.close(); | |||
parseConfFile(); | |||
} | |||
else { | |||
LOG(ERROR) << "Couldn't open " << filePath << " for reading -> using standard configuration instead"; | |||
} | |||
} | |||
//Place parameters to read in here | |||
SystemConfig::SystemConfig() | |||
{ | |||
//define name and default value here | |||
intParameter.emplace("read_data_interval_s", 60); | |||
intParameter.emplace("tcp_server_port", 7777); | |||
stringParameter.emplace("modbus_poc_ip", "127.0.0.1"); | |||
intParameter.emplace("modbus_poc_port", 502); | |||
intParameter.emplace("alert_read_interval", 5); | |||
stringParameter.emplace("modbus_rtu_device", "dev/tty0"); | |||
intParameter.emplace("modbus_rtu_baud", 9800); | |||
intParameter.emplace("modbus_rtu_stop_bits", 1); | |||
stringParameter.emplace("modbus_rtu_pairity", "E"); | |||
intParameter.emplace("modbus_rtu_slave_address", 1); | |||
intParameter.emplace("modbus_tcp_slave_address", 1); | |||
intParameter.emplace("permanent_param_history", 1000); | |||
floatParameter.emplace("crit_residual_current", 30.0); | |||
intParameter.emplace("crit_residual_timerange", 12); | |||
intParameter.emplace("update_model_rate", 100); | |||
intParameter.emplace("narrow_block", 100); | |||
} | |||
void SystemConfig::parseConfFile() | |||
{ | |||
//remove comments | |||
std::regex patternComment("#(\\w|\\s)*\n+"); | |||
fileContent = std::regex_replace(fileContent, patternComment, "\n"); | |||
auto posHeader = fileContent.find("SystemConfig:"); | |||
if (posHeader == std::string::npos){ | |||
LOG(ERROR) << "Found no Header for the configuration file -> using standard configuration instead"; | |||
return; | |||
} | |||
for (auto& param : intParameter) { | |||
std::regex pattern{ param.first + "\\s*=\\s*\\w+" }; | |||
std::smatch match; | |||
if (std::regex_search(fileContent, match, pattern)) { | |||
std::string numberString = match.str(); | |||
CustomStringUtilities::removeAllWhitespaces(numberString); | |||
param.second = std::stoi(numberString.substr(numberString.find('=') + 1)); | |||
} | |||
else { | |||
LOG(INFO) << "No specified config paramter (int) for " << param.first << ", using the default value " << param.second; | |||
} | |||
} | |||
for (auto& param : floatParameter) { | |||
std::regex pattern{ param.first + "\\s*=\\s*\\d+\\.\\d*" }; | |||
std::smatch match; | |||
if (std::regex_search(fileContent, match, pattern)) { | |||
std::string numberString = match.str(); | |||
CustomStringUtilities::removeAllWhitespaces(numberString); | |||
param.second = std::stof(numberString.substr(numberString.find('=') + 1)); | |||
} | |||
else { | |||
LOG(INFO) << "No specified config paramter (float) for " << param.first << ", using the default value " << param.second; | |||
} | |||
} | |||
for (auto& param : stringParameter) { | |||
std::regex pattern{ param.first + "\\s*=\\s*(\\w|.)*" }; | |||
std::smatch match; | |||
if (std::regex_search(fileContent, match, pattern)) { | |||
std::string paramString = match.str(); | |||
CustomStringUtilities::removeAllWhitespaces(paramString); | |||
param.second = paramString.substr(paramString.find('=') + 1); | |||
} | |||
else { | |||
LOG(INFO) << "No specified config paramter (float) for " << param.first << ", using the default value " << param.second; | |||
} | |||
} | |||
} | |||
int SystemConfig::getIntConfigParameter(std::string paramName){ | |||
auto it = intParameter.find(paramName.data()); | |||
if (it != intParameter.end()) { | |||
return it->second; | |||
} | |||
LOG(ERROR) << "Tried to read non existant int config parameter " << paramName; | |||
return 0; | |||
} | |||
float SystemConfig::getFloatConfigParameter(std::string paramName){ | |||
auto it = floatParameter.find(paramName.data()); | |||
if (it != floatParameter.end()) { | |||
return it->second; | |||
} | |||
LOG(ERROR) << "Tried to read non existant float config parameter " << paramName; | |||
return 0.0f; | |||
} | |||
std::string SystemConfig::getStringConfigParameter(std::string paramName) | |||
{ | |||
auto it = stringParameter.find(paramName.data()); | |||
if (it != stringParameter.end()) { | |||
return it->second; | |||
} | |||
LOG(ERROR) << "Tried to read non existant float config parameter " << paramName; | |||
return ""; | |||
} |
@@ -0,0 +1,28 @@ | |||
#pragma once | |||
#include <iostream> | |||
#include <fstream> | |||
#include <string> | |||
#include <array> | |||
#include <map> | |||
#include <easylogging++.h> | |||
#include <memory> | |||
class SystemConfig | |||
{ | |||
public: | |||
void readStandardConfig(); | |||
SystemConfig(); | |||
static int getIntConfigParameter(std::string paramName); | |||
static float getFloatConfigParameter(std::string paramName); | |||
static std::string getStringConfigParameter(std::string paramName); | |||
private: | |||
//Parameters to read from the file (name and default value) | |||
static std::map<std::string, int> intParameter; | |||
static std::map<std::string, float> floatParameter; | |||
static std::map<std::string, std::string> stringParameter; | |||
const std::string filePath = "system_conf.conf"; | |||
std::string fileContent; | |||
void parseConfFile(); | |||
friend std::ostream& operator<<(std::ostream& os, SystemConfig& config); | |||
}; |
@@ -0,0 +1,203 @@ | |||
#include "SystemControler.h" | |||
#include <easylogging++.h> | |||
#include "SystemConfig.h" | |||
#include <optional> | |||
using namespace std::chrono; | |||
std::atomic<bool> dataReadingCancelled = false; | |||
std::atomic<bool> updateServerCancelled = false; | |||
std::atomic<bool> threadRunning = false; | |||
std::thread* readDataThread = nullptr; | |||
SystemControler::SystemControler() : | |||
dataModel(DataModel::Instance()), dataAcquire(std::make_shared<DataAcquisition>()) | |||
{ | |||
this->alertReadInterval = SystemConfig::getIntConfigParameter("alert_read_interval"); | |||
this->serverPort = SystemConfig::getIntConfigParameter("tcp_server_port"); | |||
initializePermanentStorage(); | |||
} | |||
SystemControler::~SystemControler() | |||
{ | |||
LOG(INFO) << "Wait until continous modbus reading thread finishes..."; | |||
if(readDataThread != nullptr && threadRunning){ | |||
dataReadingCancelled = true; | |||
if(readDataThread->joinable()) | |||
readDataThread->join(); | |||
} | |||
LOG(INFO) << "Wait until server update thread finishes..."; | |||
if(serverUpdateThread != nullptr){ | |||
updateServerCancelled = true; | |||
if(serverUpdateThread->joinable()) | |||
serverUpdateThread->join(); | |||
} | |||
server.reset(); | |||
if(readDataThread != nullptr) | |||
delete readDataThread; | |||
if(serverUpdateThread != nullptr) | |||
delete serverUpdateThread; | |||
DataModel::Destroy(); | |||
} | |||
void updateServer(std::shared_ptr<NetServer> server, std::chrono::milliseconds ms) { | |||
auto now = std::chrono::system_clock::now(); | |||
//Run as long as server exists and is not stopped | |||
while (server != nullptr && !server->isStopped()) { | |||
if(updateServerCancelled) | |||
return; | |||
//Check for alerts | |||
auto& alerts = DataModel::Instance()->getAlerts(); | |||
while(alerts.size() > 0){ | |||
net::Message<net::MessageTypes> msg; | |||
auto a = alerts.pop_front(); | |||
msg.header.id = net::MessageTypes::ServerAlert; | |||
short bitmask = 0; | |||
std::memcpy(&bitmask, a.readedBytes.data(), 2); | |||
std::stringstream tmp; | |||
tmp << "Modbus " << a.connection->getConnectionType() << " alert: " << ModbusDataPOC::getStatusMessage(bitmask); | |||
msg << tmp.str(); | |||
server->MessageAllClients(msg); | |||
} | |||
//check for messages, specify no max message count | |||
//return if no message is available | |||
server->Update(-1, false); | |||
std::this_thread::sleep_until(now + ms); | |||
} | |||
return; | |||
} | |||
void SystemControler::startIpTcpServer() { | |||
try { | |||
//start server | |||
if(server == nullptr) | |||
server = std::make_shared<NetServer>(serverPort); | |||
if(server->isRunning()){ | |||
LOG(WARNING) << "Invoked server start, but server is already running"; | |||
return; | |||
} | |||
server->Start(); | |||
//Continous running update thread, that checks for incomming messages | |||
serverUpdateThread = new std::thread(updateServer, server, serverTickRateMs); | |||
} | |||
catch (asio::system_error& e) { | |||
LOG(FATAL) << "[SERVER]: Error " | |||
<< e.code() << " >> " | |||
<< e.what(); | |||
} | |||
} | |||
/* | |||
thread whhich acquires data from all registered publishers | |||
->pointer to dataAcquisition, which handles the readiong requests | |||
->intervalSec: # of seconds | |||
*/ | |||
void enqueueRegisterThread(std::shared_ptr<DataAcquisition> dataAcquisition, SystemControler* controler, const u_int intervalSec) | |||
{ | |||
threadRunning = true; | |||
dataReadingCancelled = false; | |||
LOG(INFO) << "Started continous data reading"; | |||
while (!dataReadingCancelled) { | |||
const auto currentTime = system_clock::now(); | |||
//read in the data from the publishers | |||
dataAcquisition->enqueuePublisherRegister(); | |||
controler->evaluate(); | |||
for (u_int i = 1; i <= intervalSec; i++) | |||
{ | |||
if (dataReadingCancelled) { | |||
LOG(INFO) << "Stopped continous data reading"; | |||
threadRunning = false; | |||
dataReadingCancelled = false; | |||
return; | |||
} | |||
//Wait for 1 additional second and then check for cancelled thread | |||
std::this_thread::sleep_until(currentTime + std::chrono::seconds(1) * i); | |||
} | |||
} | |||
LOG(INFO) << "Stopped continous data reading"; | |||
dataReadingCancelled = false; | |||
threadRunning = false; | |||
} | |||
//Invokes continous modbus alert reading | |||
void SystemControler::startEnqueuingRegister() | |||
{ | |||
if (threadRunning && !dataReadingCancelled) { | |||
LOG(ERROR) << "ILLEGAL STATE: Invoked starting readDataThread while thread is already running (Thread ID: " << readDataThread->get_id() << ")"; | |||
return; | |||
} | |||
//Continue thread if it is already cancelled but still running | |||
else if (threadRunning && dataReadingCancelled) { | |||
dataReadingCancelled = false; | |||
} | |||
else { | |||
if(readDataThread != nullptr) | |||
delete readDataThread; | |||
readDataThread = new std::thread(enqueueRegisterThread, dataAcquire, this, alertReadInterval); | |||
} | |||
} | |||
//Cancels continous modbus alert reading | |||
void SystemControler::cancelReadingModbusAlerts() | |||
{ | |||
if (dataReadingCancelled){ | |||
LOG(INFO) << "Thread is already cancelled, waiting for thread to stop"; | |||
} | |||
else if (!threadRunning || readDataThread == nullptr){ | |||
LOG(ERROR) << "ILLEGAL STATE: Invoke cancelling of readDataThread while thread is not running"; | |||
} | |||
else{ | |||
dataReadingCancelled = true; | |||
if(readDataThread->joinable()) | |||
readDataThread->join(); | |||
} | |||
} | |||
//Check wether to flush or not, else counts up | |||
void SystemControler::flushAfterNData() | |||
{ | |||
dataModel->checkForFlushData(); | |||
} | |||
//delegate register Publisher | |||
void SystemControler::registerPublisher(std::unique_ptr<PublisherInterface> publisher) | |||
{ | |||
dataAcquire->registerPublisher(std::move(publisher)); | |||
} | |||
void SystemControler::evaluate(){ | |||
if(evaluator == nullptr) | |||
return; | |||
auto alerts = evaluator->evaluate(); | |||
for(auto &a : alerts){ | |||
net::Message<net::MessageTypes> msg; | |||
msg.header.id = net::MessageTypes::ServerAlert; | |||
msg << a.message; | |||
server->MessageAllClients(msg); | |||
} | |||
} | |||
void SystemControler::initializePermanentStorage(){ | |||
//register Residual Current (dummy) to perform linear regression | |||
//set biased fo ML Algorithm | |||
dataModel->makePermanent(ModbusRegister::BENDER_Residual_current, true); | |||
evaluator = std::make_unique<Evaluator>(); | |||
} |
@@ -0,0 +1,60 @@ | |||
#pragma once | |||
#include <memory> | |||
#include <vector> | |||
#include <thread> | |||
#include "DataAcquisition.h" | |||
#include "PublisherType.h" | |||
#include "Evaluator.h" | |||
#include "NetServer.h" | |||
class SystemControler | |||
{ | |||
private: | |||
u_int serverPort; | |||
u_int alertReadInterval; | |||
std::unique_ptr<DataModel>& dataModel; | |||
std::shared_ptr<DataAcquisition> dataAcquire; | |||
std::shared_ptr<Evaluator> evaluator; | |||
//Estimated raw data which are collected over one hour of continous running in Bytes | |||
size_t dataSizePerHour = 0; | |||
std::shared_ptr<NetServer> server; | |||
//Continous server updating with fixed tick rate, spcified in ms | |||
std::thread* serverUpdateThread = nullptr; | |||
std::chrono::milliseconds serverTickRateMs { 100 }; | |||
void initializePermanentStorage(); | |||
public: | |||
SystemControler(); | |||
~SystemControler(); | |||
void startIpTcpServer(); | |||
//getter | |||
std::shared_ptr<DataAcquisition>& getDataAcquisitionRef() { return dataAcquire; } | |||
//Delegate read requests threaded to to acquire unit | |||
void startEnqueuingRegister(); | |||
size_t getDataSizePerHour()const { return dataSizePerHour; }; | |||
void cancelReadingModbusAlerts(); | |||
void flushAfterNData(); | |||
void startModbusWorkerThread(){ dataAcquire->startModbusThread(); } | |||
void test(){ } | |||
//delegate register Publisher | |||
void registerPublisher(std::unique_ptr<PublisherInterface> publisher); | |||
//call evaluation manually, delegates call | |||
void evaluate(); | |||
unsigned long deleteFiles(std::chrono::seconds olderThan) { return (olderThan == seconds(0)) ? dataModel->removeStoredData() : dataModel->removeStoredData(olderThan); } | |||
}; |
@@ -0,0 +1,157 @@ | |||
/* | |||
MMO Client/Server Framework using ASIO | |||
"Happy Birthday Mrs Javidx9!" - javidx9 | |||
Videos: | |||
Part #1: https://youtu.be/2hNdkYInj4g | |||
Part #2: https://youtu.be/UbjxGvrDrbw | |||
License (OLC-3) | |||
~~~~~~~~~~~~~~~ | |||
Copyright 2018 - 2020 OneLoneCoder.com | |||
Redistribution and use in source and binary forms, with or without | |||
modification, are permitted provided that the following conditions | |||
are met: | |||
1. Redistributions or derivations of source code must retain the above | |||
copyright notice, this list of conditions and the following disclaimer. | |||
2. Redistributions or derivative works in binary form must reproduce | |||
the above copyright notice. This list of conditions and the following | |||
disclaimer must be reproduced in the documentation and/or other | |||
materials provided with the distribution. | |||
3. Neither the name of the copyright holder nor the names of its | |||
contributors may be used to endorse or promote products derived | |||
from this software without specific prior written permission. | |||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
Links | |||
~~~~~ | |||
YouTube: https://www.youtube.com/javidx9 | |||
Discord: https://discord.gg/WhwHUMV | |||
Twitter: https://www.twitter.com/javidx9 | |||
Twitch: https://www.twitch.tv/javidx9 | |||
GitHub: https://www.github.com/onelonecoder | |||
Homepage: https://www.onelonecoder.com | |||
Author | |||
~~~~~~ | |||
David Barr, aka javidx9, ©OneLoneCoder 2019, 2020 | |||
*/ | |||
#pragma once | |||
#include "net_common.h" | |||
#include "net_connection.h" | |||
namespace net | |||
{ | |||
template <typename T> | |||
class ClientInterface | |||
{ | |||
public: | |||
ClientInterface() | |||
{} | |||
virtual ~ClientInterface() | |||
{ | |||
// If the client is destroyed, always try and disconnect from server | |||
Disconnect(); | |||
} | |||
public: | |||
// Connect to server with hostname/ip-address and port | |||
bool Connect(const std::string& host, const uint16_t port) | |||
{ | |||
try | |||
{ | |||
// Resolve hostname/ip-address into tangiable physical address | |||
asio::ip::tcp::resolver resolver(m_context); | |||
asio::ip::tcp::resolver::results_type endpoints = resolver.resolve(host, std::to_string(port)); | |||
// Create connection | |||
m_connection = std::make_unique<Connection<T>>(Connection<T>::Owner::CLIENT, m_context, asio::ip::tcp::socket(m_context), m_qMessagesIn); | |||
// Tell the connection object to connect to server | |||
m_connection->ConnectToServer(endpoints); | |||
// Start Context Thread | |||
thrContext = std::thread([this]() { m_context.run(); }); | |||
} | |||
catch (std::exception& e) | |||
{ | |||
std::cerr << "Client Exception: " << e.what() << "\n"; | |||
return false; | |||
} | |||
return true; | |||
} | |||
// Disconnect from server | |||
void Disconnect() | |||
{ | |||
// If connection exists, and it's connected then... | |||
if(IsConnected()) | |||
{ | |||
// ...disconnect from server gracefully | |||
m_connection->Disconnect(); | |||
} | |||
// Either way, we're also done with the asio context... | |||
m_context.stop(); | |||
// ...and its thread | |||
if (thrContext.joinable()) | |||
thrContext.join(); | |||
// Destroy the connection object | |||
m_connection.release(); | |||
} | |||
// Check if client is actually connected to a server | |||
bool IsConnected() | |||
{ | |||
if (m_connection) | |||
return m_connection->IsConnected(); | |||
else | |||
return false; | |||
} | |||
public: | |||
// Send message to server | |||
void Send(const Message<T>& msg) | |||
{ | |||
if (IsConnected()) | |||
m_connection->Send(msg); | |||
} | |||
// Retrieve queue of messages from server | |||
net::ts_dequeue<OwnedMessage<T>>& Incoming() | |||
{ | |||
return m_qMessagesIn; | |||
} | |||
protected: | |||
// asio context handles the data transfer... | |||
asio::io_context m_context; | |||
// ...but needs a thread of its own to execute its work commands | |||
std::thread thrContext; | |||
// The client has a single instance of a "connection" object, which handles data transfer | |||
std::unique_ptr<Connection<T>> m_connection; | |||
private: | |||
// This is the thread safe queue of incoming messages from server | |||
net::ts_dequeue<OwnedMessage<T>> m_qMessagesIn; | |||
}; | |||
} |
@@ -0,0 +1,76 @@ | |||
/* | |||
MMO Client/Server Framework using ASIO | |||
"Happy Birthday Mrs Javidx9!" - javidx9 | |||
Videos: | |||
Part #1: https://youtu.be/2hNdkYInj4g | |||
Part #2: https://youtu.be/UbjxGvrDrbw | |||
License (OLC-3) | |||
~~~~~~~~~~~~~~~ | |||
Copyright 2018 - 2020 OneLoneCoder.com | |||
Redistribution and use in source and binary forms, with or without | |||
modification, are permitted provided that the following conditions | |||
are met: | |||
1. Redistributions or derivations of source code must retain the above | |||
copyright notice, this list of conditions and the following disclaimer. | |||
2. Redistributions or derivative works in binary form must reproduce | |||
the above copyright notice. This list of conditions and the following | |||
disclaimer must be reproduced in the documentation and/or other | |||
materials provided with the distribution. | |||
3. Neither the name of the copyright holder nor the names of its | |||
contributors may be used to endorse or promote products derived | |||
from this software without specific prior written permission. | |||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
Links | |||
~~~~~ | |||
YouTube: https://www.youtube.com/javidx9 | |||
Discord: https://discord.gg/WhwHUMV | |||
Twitter: https://www.twitter.com/javidx9 | |||
Twitch: https://www.twitch.tv/javidx9 | |||
GitHub: https://www.github.com/onelonecoder | |||
Homepage: https://www.onelonecoder.com | |||
Author | |||
~~~~~~ | |||
David Barr, aka javidx9, ©OneLoneCoder 2019, 2020 | |||
*/ | |||
#pragma once | |||
#include <memory> | |||
#include <thread> | |||
#include <mutex> | |||
#include <deque> | |||
#include <optional> | |||
#include <vector> | |||
#include <iostream> | |||
#include <algorithm> | |||
#include <chrono> | |||
#include <cstdint> | |||
#ifdef _WIN32 | |||
#define _WIN32_WINNT 0x0A00 | |||
#endif | |||
#define ASIO_STANDALONE | |||
#include <asio.hpp> | |||
#include <asio/ts/buffer.hpp> | |||
#include <asio/ts/internet.hpp> |
@@ -0,0 +1,343 @@ | |||
/* | |||
MMO Client/Server Framework using ASIO | |||
"Happy Birthday Mrs Javidx9!" - javidx9 | |||
Videos: | |||
Part #1: https://youtu.be/2hNdkYInj4g | |||
Part #2: https://youtu.be/UbjxGvrDrbw | |||
License (OLC-3) | |||
~~~~~~~~~~~~~~~ | |||
Copyright 2018 - 2020 OneLoneCoder.com | |||
Redistribution and use in source and binary forms, with or without | |||
modification, are permitted provided that the following conditions | |||
are met: | |||
1. Redistributions or derivations of source code must retain the above | |||
copyright notice, this list of conditions and the following disclaimer. | |||
2. Redistributions or derivative works in binary form must reproduce | |||
the above copyright notice. This list of conditions and the following | |||
disclaimer must be reproduced in the documentation and/or other | |||
materials provided with the distribution. | |||
3. Neither the name of the copyright holder nor the names of its | |||
contributors may be used to endorse or promote products derived | |||
from this software without specific prior written permission. | |||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
Links | |||
~~~~~ | |||
YouTube: https://www.youtube.com/javidx9 | |||
Discord: https://discord.gg/WhwHUMV | |||
Twitter: https://www.twitter.com/javidx9 | |||
Twitch: https://www.twitch.tv/javidx9 | |||
GitHub: https://www.github.com/onelonecoder | |||
Homepage: https://www.onelonecoder.com | |||
Author | |||
~~~~~~ | |||
David Barr, aka javidx9, ©OneLoneCoder 2019, 2020 | |||
*/ | |||
#pragma once | |||
#include "net_common.h" | |||
#include "net_dequeue_ts.h" | |||
#include "net_message.h" | |||
namespace net | |||
{ | |||
template<typename T> | |||
class Connection : public std::enable_shared_from_this<Connection<T>> | |||
{ | |||
public: | |||
// A connection is "owned" by either a server or a client, and its | |||
// behaviour is slightly different bewteen the two. | |||
enum class Owner | |||
{ | |||
SERVER, | |||
CLIENT | |||
}; | |||
public: | |||
// Constructor: Specify Owner, connect to context, transfer the socket | |||
// Provide reference to incoming message queue | |||
Connection(Owner parent, asio::io_context& asioContext, asio::ip::tcp::socket socket, ts_dequeue<OwnedMessage<T>>& qIn) | |||
: m_socket(std::move(socket)), m_asioContext(asioContext), m_qMessagesIn(qIn) | |||
{ | |||
m_nOwnerType = parent; | |||
} | |||
virtual ~Connection() | |||
{} | |||
// This ID is used system wide - its how clients will understand other clients | |||
// exist across the whole system. | |||
uint32_t GetID() const | |||
{ | |||
return id; | |||
} | |||
public: | |||
void ConnectToClient(uint32_t uid = 0) | |||
{ | |||
if (m_nOwnerType == Owner::SERVER) | |||
{ | |||
if (m_socket.is_open()) | |||
{ | |||
id = uid; | |||
ReadHeader(); | |||
} | |||
} | |||
} | |||
void ConnectToServer(const asio::ip::tcp::resolver::results_type& endpoints) | |||
{ | |||
// Only clients can connect to servers | |||
if (m_nOwnerType == Owner::CLIENT) | |||
{ | |||
// Request asio attempts to connect to an endpoint | |||
asio::async_connect(m_socket, endpoints, | |||
[this](std::error_code ec, asio::ip::tcp::endpoint endpoint) | |||
{ | |||
if (!ec) | |||
{ | |||
ReadHeader(); | |||
} | |||
}); | |||
} | |||
} | |||
void Disconnect() | |||
{ | |||
if (IsConnected()) | |||
asio::post(m_asioContext, [this]() { m_socket.close(); }); | |||
} | |||
bool IsConnected() const | |||
{ | |||
return m_socket.is_open(); | |||
} | |||
// Prime the connection to wait for incoming messages | |||
void StartListening() | |||
{ | |||
} | |||
public: | |||
// ASYNC - Send a message, connections are one-to-one so no need to specifiy | |||
// the target, for a client, the target is the server and vice versa | |||
void Send(const Message<T>& msg) | |||
{ | |||
asio::post(m_asioContext, | |||
[this, msg]() | |||
{ | |||
// If the queue has a message in it, then we must | |||
// assume that it is in the process of asynchronously being written. | |||
// Either way add the message to the queue to be output. If no messages | |||
// were available to be written, then start the process of writing the | |||
// message at the front of the queue. | |||
bool bWritingMessage = !m_qMessagesOut.empty(); | |||
m_qMessagesOut.push_back(msg); | |||
if (!bWritingMessage) | |||
{ | |||
WriteHeader(); | |||
} | |||
}); | |||
} | |||
private: | |||
// ASYNC - Prime context to write a message header | |||
void WriteHeader() | |||
{ | |||
// If this function is called, we know the outgoing message queue must have | |||
// at least one message to send. So allocate a transmission buffer to hold | |||
// the message, and issue the work - asio, send these bytes | |||
asio::async_write(m_socket, asio::buffer(&m_qMessagesOut.front().header, sizeof(MessageHeader<T>)), | |||
[this](std::error_code ec, std::size_t length) | |||
{ | |||
// asio has now sent the bytes - if there was a problem | |||
// an error would be available... | |||
if (!ec) | |||
{ | |||
// ... no error, so check if the message header just sent also | |||
// has a message body... | |||
if (m_qMessagesOut.front().body.size() > 0) | |||
{ | |||
// ...it does, so issue the task to write the body bytes | |||
WriteBody(); | |||
} | |||
else | |||
{ | |||
// ...it didnt, so we are done with this message. Remove it from | |||
// the outgoing message queue | |||
m_qMessagesOut.pop_front(); | |||
// If the queue is not empty, there are more messages to send, so | |||
// make this happen by issuing the task to send the next header. | |||
if (!m_qMessagesOut.empty()) | |||
{ | |||
WriteHeader(); | |||
} | |||
} | |||
} | |||
else | |||
{ | |||
// ...asio failed to write the message, we could analyse why but | |||
// for now simply assume the connection has died by closing the | |||
// socket. When a future attempt to write to this client fails due | |||
// to the closed socket, it will be tidied up. | |||
std::cout << "[" << id << "] Write Header Fail.\n"; | |||
m_socket.close(); | |||
} | |||
}); | |||
} | |||
// ASYNC - Prime context to write a message body | |||
void WriteBody() | |||
{ | |||
// If this function is called, a header has just been sent, and that header | |||
// indicated a body existed for this message. Fill a transmission buffer | |||
// with the body data, and send it! | |||
asio::async_write(m_socket, asio::buffer(m_qMessagesOut.front().body.data(), m_qMessagesOut.front().body.size()), | |||
[this](std::error_code ec, std::size_t length) | |||
{ | |||
if (!ec) | |||
{ | |||
// Sending was successful, so we are done with the message | |||
// and remove it from the queue | |||
m_qMessagesOut.pop_front(); | |||
// If the queue still has messages in it, then issue the task to | |||
// send the next messages' header. | |||
if (!m_qMessagesOut.empty()) | |||
{ | |||
WriteHeader(); | |||
} | |||
} | |||
else | |||
{ | |||
// Sending failed, see WriteHeader() equivalent for description :P | |||
std::cout << "[" << id << "] Write Body Fail.\n"; | |||
m_socket.close(); | |||
} | |||
}); | |||
} | |||
// ASYNC - Prime context ready to read a message header | |||
void ReadHeader() | |||
{ | |||
// asio to waits until it receives | |||
// enough bytes to form a header of a message. We know the headers are a fixed | |||
// size, so allocate a transmission buffer large enough to store it. | |||
//Call this function to set up an asynchronous listener for a certain Connection | |||
asio::async_read(m_socket, asio::buffer(&m_msgTemporaryIn.header, sizeof(MessageHeader<T>)), | |||
[this](std::error_code ec, std::size_t length) | |||
{ | |||
if (!ec) | |||
{ | |||
//Full header readed | |||
//Check for message body | |||
if (m_msgTemporaryIn.header.size > 0) | |||
{ | |||
//Allocate storage | |||
m_msgTemporaryIn.body.resize(m_msgTemporaryIn.header.size); | |||
//Read if available | |||
ReadBody(); | |||
} | |||
else | |||
{ | |||
//Bodyless message, add to queue | |||
AddToIncomingMessageQueue(); | |||
} | |||
} | |||
else | |||
{ | |||
//Failure, probably a disconnection | |||
std::cout << "[" << id << "] Error: " << ec.message() << std::endl; | |||
m_socket.close(); | |||
} | |||
}); | |||
} | |||
// ASYNC - Prime context to read a message body | |||
void ReadBody() | |||
{ | |||
//Called after header has been read successfully | |||
//Read the body in the pre allocated storage | |||
asio::async_read(m_socket, asio::buffer(m_msgTemporaryIn.body.data(), m_msgTemporaryIn.body.size()), | |||
[this](std::error_code ec, std::size_t length) | |||
{ | |||
if (!ec) | |||
{ | |||
// Complete message, add to queue | |||
AddToIncomingMessageQueue(); | |||
} | |||
else | |||
{ | |||
//Failure, probably a disconnection | |||
std::cout << "[" << id << "] Error reading body: " << ec.message() << std::endl; | |||
m_socket.close(); | |||
} | |||
}); | |||
} | |||
// Add a complete message to the incoming queue, with or without body | |||
void AddToIncomingMessageQueue() | |||
{ | |||
//Put it in the queue, put a owner to the object to let the server know who send the message (which connection) | |||
if(m_nOwnerType == Owner::SERVER) | |||
m_qMessagesIn.push_back({ this->shared_from_this(), m_msgTemporaryIn }); | |||
else | |||
m_qMessagesIn.push_back({ nullptr, m_msgTemporaryIn }); | |||
//Done queueing the message, now initialize a new async read to wait for next message | |||
ReadHeader(); | |||
} | |||
protected: | |||
// unique socket to a remote | |||
asio::ip::tcp::socket m_socket; | |||
// This context is shared with the whole asio instance | |||
asio::io_context& m_asioContext; | |||
// This queue holds all messages to be sent to the remote side | |||
// of this connection | |||
ts_dequeue<Message<T>> m_qMessagesOut; | |||
// This references the incoming queue of the parent object | |||
ts_dequeue<OwnedMessage<T>>& m_qMessagesIn; | |||
// Incoming messages are constructed asynchronously, so we will | |||
// store the part assembled message here, until it is ready | |||
Message<T> m_msgTemporaryIn; | |||
// The "owner" decides how some of the connection behaves | |||
Owner m_nOwnerType = Owner::SERVER; | |||
uint32_t id = 0; | |||
}; | |||
} |
@@ -0,0 +1,161 @@ | |||
/* | |||
MMO Client/Server Framework using ASIO | |||
"Happy Birthday Mrs Javidx9!" - javidx9 | |||
Videos: | |||
Part #1: https://youtu.be/2hNdkYInj4g | |||
Part #2: https://youtu.be/UbjxGvrDrbw | |||
License (OLC-3) | |||
~~~~~~~~~~~~~~~ | |||
Copyright 2018 - 2020 OneLoneCoder.com | |||
Redistribution and use in source and binary forms, with or without | |||
modification, are permitted provided that the following conditions | |||
are met: | |||
1. Redistributions or derivations of source code must retain the above | |||
copyright notice, this list of conditions and the following disclaimer. | |||
2. Redistributions or derivative works in binary form must reproduce | |||
the above copyright notice. This list of conditions and the following | |||
disclaimer must be reproduced in the documentation and/or other | |||
materials provided with the distribution. | |||
3. Neither the name of the copyright holder nor the names of its | |||
contributors may be used to endorse or promote products derived | |||
from this software without specific prior written permission. | |||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
Links | |||
~~~~~ | |||
YouTube: https://www.youtube.com/javidx9 | |||
Discord: https://discord.gg/WhwHUMV | |||
Twitter: https://www.twitter.com/javidx9 | |||
Twitch: https://www.twitch.tv/javidx9 | |||
GitHub: https://www.github.com/onelonecoder | |||
Homepage: https://www.onelonecoder.com | |||
Author | |||
~~~~~~ | |||
David Barr, aka javidx9, ©OneLoneCoder 2019, 2020 | |||
*/ | |||
#pragma once | |||
#include "net_common.h" | |||
namespace net | |||
{ | |||
template<typename T> | |||
class ts_dequeue | |||
{ | |||
public: | |||
ts_dequeue() = default; | |||
ts_dequeue(const ts_dequeue<T>&) = delete; | |||
virtual ~ts_dequeue() { clear(); } | |||
public: | |||
// Returns and maintains item at front of Queue | |||
const T& front() | |||
{ | |||
std::scoped_lock lock(muxQueue); | |||
return deqQueue.front(); | |||
} | |||
// Returns and maintains item at back of Queue | |||
const T& back() | |||
{ | |||
std::scoped_lock lock(muxQueue); | |||
return deqQueue.back(); | |||
} | |||
// Removes and returns item from front of Queue | |||
T pop_front() | |||
{ | |||
std::scoped_lock lock(muxQueue); | |||
auto t = std::move(deqQueue.front()); | |||
deqQueue.pop_front(); | |||
return t; | |||
} | |||
// Removes and returns item from back of Queue | |||
T pop_back() | |||
{ | |||
std::scoped_lock lock(muxQueue); | |||
auto t = std::move(deqQueue.back()); | |||
deqQueue.pop_back(); | |||
return t; | |||
} | |||
// Adds an item to back of Queue | |||
void push_back(const T& item) | |||
{ | |||
std::scoped_lock lock(muxQueue); | |||
deqQueue.emplace_back(std::move(item)); | |||
std::unique_lock<std::mutex> ul(muxBlocking); | |||
cvBlocking.notify_one(); | |||
} | |||
// Adds an item to front of Queue | |||
void push_front(const T& item) | |||
{ | |||
std::scoped_lock lock(muxQueue); | |||
deqQueue.emplace_front(std::move(item)); | |||
std::unique_lock<std::mutex> ul(muxBlocking); | |||
cvBlocking.notify_one(); | |||
} | |||
// Returns true if Queue has no items | |||
bool empty() | |||
{ | |||
std::scoped_lock lock(muxQueue); | |||
return deqQueue.empty(); | |||
} | |||
// Returns number of items in Queue | |||
size_t count() | |||
{ | |||
std::scoped_lock lock(muxQueue); | |||
return deqQueue.size(); | |||
} | |||
// Clears Queue | |||
void clear() | |||
{ | |||
std::scoped_lock lock(muxQueue); | |||
deqQueue.clear(); | |||
} | |||
void wait() | |||
{ | |||
while (empty()) | |||
{ | |||
std::unique_lock<std::mutex> ul(muxBlocking); | |||
cvBlocking.wait(ul); | |||
} | |||
} | |||
protected: | |||
std::mutex muxQueue; | |||
std::deque<T> deqQueue; | |||
std::condition_variable cvBlocking; | |||
std::mutex muxBlocking; | |||
}; | |||
} |
@@ -0,0 +1,214 @@ | |||
/* | |||
MMO Client/Server Framework using ASIO | |||
"Happy Birthday Mrs Javidx9!" - javidx9 | |||
Videos: | |||
Part #1: https://youtu.be/2hNdkYInj4g | |||
Part #2: https://youtu.be/UbjxGvrDrbw | |||
License (OLC-3) | |||
~~~~~~~~~~~~~~~ | |||
Copyright 2018 - 2020 OneLoneCoder.com | |||
Redistribution and use in source and binary forms, with or without | |||
modification, are permitted provided that the following conditions | |||
are met: | |||
1. Redistributions or derivations of source code must retain the above | |||
copyright notice, this list of conditions and the following disclaimer. | |||
2. Redistributions or derivative works in binary form must reproduce | |||
the above copyright notice. This list of conditions and the following | |||
disclaimer must be reproduced in the documentation and/or other | |||
materials provided with the distribution. | |||
3. Neither the name of the copyright holder nor the names of its | |||
contributors may be used to endorse or promote products derived | |||
from this software without specific prior written permission. | |||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
Links | |||
~~~~~ | |||
YouTube: https://www.youtube.com/javidx9 | |||
Discord: https://discord.gg/WhwHUMV | |||
Twitter: https://www.twitter.com/javidx9 | |||
Twitch: https://www.twitch.tv/javidx9 | |||
GitHub: https://www.github.com/onelonecoder | |||
Homepage: https://www.onelonecoder.com | |||
Author | |||
~~~~~~ | |||
David Barr, aka javidx9, �OneLoneCoder 2019, 2020 | |||
*/ | |||
#pragma once | |||
#include "net_common.h" | |||
#include <string> | |||
namespace net | |||
{ | |||
///[OLC_HEADERIFYIER] START "MESSAGE" | |||
// Message Header is sent at start of all messages. The template allows us | |||
// to use "enum class" to ensure that the messages are valid at compile time | |||
template <typename T> | |||
struct MessageHeader | |||
{ | |||
T id{}; | |||
uint32_t size = 0; | |||
}; | |||
// Message Body contains a header and a std::vector, containing raw bytes | |||
// of infomation. This way the message can be variable length, but the size | |||
// in the header must be updated. | |||
template <typename T> | |||
struct Message | |||
{ | |||
// Header & Body vector | |||
MessageHeader<T> header{}; | |||
std::vector<uint8_t> body; | |||
// returns size of entire message packet in bytes | |||
size_t size() const | |||
{ | |||
return body.size(); | |||
} | |||
// Override for std::cout compatibility - produces friendly description of message | |||
friend std::ostream& operator << (std::ostream& os, const Message<T>& msg) | |||
{ | |||
os << "ID:" << int(msg.header.id) << " Size:" << msg.header.size; | |||
return os; | |||
} | |||
// Convenience Operator overloads - These allow us to add and remove stuff from | |||
// the body vector as if it were a stack, so First in, Last Out. These are a | |||
// template in itself, because we dont know what data type the user is pushing or | |||
// popping, so lets allow them all. NOTE: It assumes the data type is fundamentally | |||
// Plain Old Data (POD). TLDR: Serialise & Deserialise into/from a vector | |||
// Pushes any POD-like data into the message buffer | |||
template<typename DataType> | |||
friend Message<T>& operator << (Message<T>& msg, const DataType& data) | |||
{ | |||
// Check that the type of the data being pushed is trivially copyable | |||
static_assert(std::is_standard_layout<DataType>::value, "Data is too complex to be pushed into vector"); | |||
// Cache current size of vector, as this will be the point we insert the data | |||
size_t i = msg.body.size(); | |||
// Resize the vector by the size of the data being pushed | |||
msg.body.resize(i + sizeof(DataType)); | |||
// Physically copy the data into the newly allocated vector space | |||
std::memcpy(msg.body.data() + i, &data, sizeof(DataType)); | |||
// Recalculate the message size | |||
msg.header.size = msg.size(); | |||
// Return the target message so it can be "chained" | |||
return msg; | |||
} | |||
//Specified template to write string | |||
friend Message<T>& operator << (Message<T>& msg, const std::string& data) | |||
{ | |||
// Cache current size of vector, as this will be the point we insert the data | |||
size_t i = msg.body.size(); | |||
// Resize the vector by the size of the data being pushed | |||
//msg.body.resize(i + sizeof(data)); | |||
// Physically copy the data of the string character by character | |||
msg.body.resize(i + data.size()); | |||
for (size_t index = 0; index < data.size(); index++) { | |||
msg.body[i+index] = data.at(index); | |||
//std::memcpy(msg.body.data() + i, &c, sizeof(uint8_t)); | |||
} | |||
// Recalculate the message size | |||
msg.header.size = (uint32_t)msg.size(); | |||
// Return the target message so it can be "chained" | |||
return msg; | |||
} | |||
// Pulls any POD-like data form the message buffer | |||
template<typename DataType> | |||
friend Message<T>& operator >> (Message<T>& msg, DataType& data) | |||
{ | |||
// Check that the type of the data being pushed is trivially copyable | |||
static_assert(std::is_standard_layout<DataType>::value, "Data is too complex to be pulled from vector"); | |||
// Cache the location towards the end of the vector where the pulled data starts | |||
size_t i = msg.body.size() - sizeof(DataType); | |||
// Physically copy the data from the vector into the user variable | |||
std::memcpy(&data, msg.body.data() + i, sizeof(DataType)); | |||
// Shrink the vector to remove read bytes, and reset end position | |||
msg.body.resize(i); | |||
// Recalculate the message size | |||
msg.header.size = msg.size(); | |||
// Return the target message so it can be "chained" | |||
return msg; | |||
} | |||
//Specified template to read string | |||
friend Message<T>& operator >> (Message<T>& msg, std::string& data) | |||
{ | |||
// Cache the location towards the end of the vector where the pulled data starts | |||
size_t i = 0; | |||
// Physically copy the data from the vector into the user variable | |||
std::memcpy(&data, msg.body.data(), msg.body.size()); | |||
// Shrink the vector to remove read bytes, and reset end position | |||
msg.body.resize(i); | |||
// Return the target message so it can be "chained" | |||
return msg; | |||
} | |||
}; | |||
// An "owned" message is identical to a regular message, but it is associated with | |||
// a connection. On a server, the owner would be the client that sent the message, | |||
// on a client the owner would be the server. | |||
// Forward declare the connection | |||
template <typename T> | |||
class Connection; | |||
template <typename T> | |||
struct OwnedMessage | |||
{ | |||
std::shared_ptr<Connection<T>> remote = nullptr; | |||
Message<T> msg; | |||
// Again, a friendly string maker | |||
friend std::ostream& operator<<(std::ostream& os, const OwnedMessage<T>& msg) | |||
{ | |||
os << msg.msg; | |||
return os; | |||
} | |||
}; | |||
} |
@@ -0,0 +1,276 @@ | |||
/* | |||
MMO Client/Server Framework using ASIO | |||
"Happy Birthday Mrs Javidx9!" - javidx9 | |||
Videos: | |||
Part #1: https://youtu.be/2hNdkYInj4g | |||
Part #2: https://youtu.be/UbjxGvrDrbw | |||
License (OLC-3) | |||
~~~~~~~~~~~~~~~ | |||
Copyright 2018 - 2020 OneLoneCoder.com | |||
Redistribution and use in source and binary forms, with or without | |||
modification, are permitted provided that the following conditions | |||
are met: | |||
1. Redistributions or derivations of source code must retain the above | |||
copyright notice, this list of conditions and the following disclaimer. | |||
2. Redistributions or derivative works in binary form must reproduce | |||
the above copyright notice. This list of conditions and the following | |||
disclaimer must be reproduced in the documentation and/or other | |||
materials provided with the distribution. | |||
3. Neither the name of the copyright holder nor the names of its | |||
contributors may be used to endorse or promote products derived | |||
from this software without specific prior written permission. | |||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
Links | |||
~~~~~ | |||
YouTube: https://www.youtube.com/javidx9 | |||
Discord: https://discord.gg/WhwHUMV | |||
Twitter: https://www.twitter.com/javidx9 | |||
Twitch: https://www.twitch.tv/javidx9 | |||
GitHub: https://www.github.com/onelonecoder | |||
Homepage: https://www.onelonecoder.com | |||
Author | |||
~~~~~~ | |||
David Barr, aka javidx9, ?OneLoneCoder 2019, 2020 | |||
*/ | |||
#pragma once | |||
#include "net_dequeue_ts.h" | |||
#include "net_common.h" | |||
#include "net_message.h" | |||
#include "net_connection.h" | |||
#include "easylogging++.h" | |||
namespace net | |||
{ | |||
template<typename T> | |||
class ServerInterface | |||
{ | |||
public: // Create a server, ready to listen on specified port | |||
ServerInterface(uint16_t port) | |||
: m_asioAcceptor(m_asioContext, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port)) { } | |||
virtual ~ServerInterface() { Stop(); } | |||
// Starts the server | |||
virtual bool Start() | |||
{ | |||
try | |||
{ | |||
// Issue a task to the asio context - This is important | |||
// as it will prime the context with "work", and stop it | |||
// from exiting immediately. Since this is a server, we | |||
// want it primed ready to handle clients trying to | |||
// connect. | |||
WaitForClientConnection(); | |||
// Launch the asio context in its own thread | |||
m_threadContext = std::thread([this]() { m_asioContext.run(); }); | |||
} | |||
catch (std::exception& e) | |||
{ | |||
// Something prohibited the server from listening | |||
LOG(ERROR) << "[SERVER] Exception: " << e.what(); | |||
return false; | |||
} | |||
return true; | |||
} | |||
bool isStopped()const { | |||
return m_asioContext.stopped(); | |||
} | |||
// Stops the server! | |||
virtual void Stop() | |||
{ | |||
// Request the context to close | |||
m_asioContext.stop(); | |||
// Tidy up the context thread | |||
if (m_threadContext.joinable()) m_threadContext.join(); | |||
// Inform someone, anybody, if they care... | |||
LOG(INFO) << "[SERVER] Stopped!\n"; | |||
} | |||
// ASYNC - Instruct asio to wait for connection | |||
void WaitForClientConnection() | |||
{ | |||
// Prime context with an instruction to wait until a socket connects. This | |||
// is the purpose of an "acceptor" object. It will provide a unique socket | |||
// for each incoming connection attempt | |||
m_asioAcceptor.async_accept( | |||
[this](std::error_code ec, asio::ip::tcp::socket socket) | |||
{ | |||
// Triggered by incoming connection request | |||
if (!ec) | |||
{ | |||
LOG(INFO) << "[SERVER] New Connection: " << socket.remote_endpoint(); | |||
// Create a new connection to handle this client | |||
std::shared_ptr<Connection<T>> newconn = | |||
std::make_shared<Connection<T>>(Connection<T>::Owner::SERVER, | |||
m_asioContext, std::move(socket), m_qMessagesIn); | |||
// Give the user server a chance to deny connection | |||
if (OnClientConnect(newconn)) | |||
{ | |||
// Connection allowed, so add to container of new connections | |||
m_deqConnections.push_back(std::move(newconn)); | |||
// Issue a task to the connection's | |||
// asio context to sit and wait for bytes to arrive! | |||
m_deqConnections.back()->ConnectToClient(nIDCounter++); | |||
LOG(INFO) << "[" << m_deqConnections.back()->GetID() << "] Connection Approved"; | |||
} | |||
else | |||
{ | |||
LOG(INFO) << "[SERVER] Connection Denied"; | |||
// Connection will go out of scope with no pending tasks, so will | |||
// get destroyed automatically (smart pointer) | |||
} | |||
} | |||
else | |||
{ | |||
// Error has occurred during acceptance | |||
LOG(INFO) << "[SERVER] New Connection Error: " << ec.message(); | |||
} | |||
// Prime the asio context with more work - again simply wait for | |||
// another connection... | |||
WaitForClientConnection(); | |||
}); | |||
} | |||
// Send a message to a specific client | |||
void MessageClient(std::shared_ptr<Connection<T>> client, const Message<T>& msg) | |||
{ | |||
// Check client is legitimate... | |||
if (client && client->IsConnected()) | |||
{ | |||
// ...and post the message via the connection | |||
client->Send(msg); | |||
} | |||
else | |||
{ | |||
// If we cant communicate with client then we may as | |||
// well remove the client - let the server know, it may | |||
// be tracking it somehow | |||
OnClientDisconnect(client); | |||
// Off you go now, bye bye! | |||
client.reset(); | |||
// Then physically remove it from the container | |||
m_deqConnections.erase( | |||
std::remove(m_deqConnections.begin(), m_deqConnections.end(), client), m_deqConnections.end()); | |||
} | |||
} | |||
// Send message to all clients | |||
void MessageAllClients(const Message<T>& msg, std::shared_ptr<Connection<T>> pIgnoreClient = nullptr) | |||
{ | |||
bool bInvalidClientExists = false; | |||
// Iterate through all clients in container | |||
for (auto& client : m_deqConnections) | |||
{ | |||
// Check client is connected... | |||
if (client && client->IsConnected()) | |||
{ | |||
// ..it is! | |||
if(client != pIgnoreClient) | |||
client->Send(msg); | |||
} | |||
else | |||
{ | |||
// The client couldnt be contacted, so assume it has | |||
// disconnected. | |||
OnClientDisconnect(client); | |||
client.reset(); | |||
// Set this flag to then remove dead clients from container | |||
bInvalidClientExists = true; | |||
} | |||
} | |||
// Remove dead clients, all in one go - this way, we dont invalidate the | |||
// container as we iterated through it. | |||
if (bInvalidClientExists) | |||
m_deqConnections.erase( | |||
std::remove(m_deqConnections.begin(), m_deqConnections.end(), nullptr), m_deqConnections.end()); | |||
} | |||
// Force server to respond to incoming messages | |||
// size_t nmaxMessages: Assign -1 to unsigned to unspecify max message count | |||
// bool bWait: if queue is empty, wait synchronously until message arrives | |||
void Update(size_t nMaxMessages = -1, bool bWait = false) | |||
{ | |||
if (bWait) m_qMessagesIn.wait(); | |||
// Process as many messages as you can up to the value | |||
// specified | |||
size_t nMessageCount = 0; | |||
while (nMessageCount < nMaxMessages && !m_qMessagesIn.empty()) | |||
{ | |||
// Grab the front message | |||
auto msg = m_qMessagesIn.pop_front(); | |||
// Pass to message handler | |||
OnMessage(msg.remote, msg.msg); | |||
nMessageCount++; | |||
} | |||
} | |||
protected: | |||
//Overwritable functions to customize server behaviour | |||
// Called when a client connects, you can veto the connection by returning false | |||
virtual bool OnClientConnect(std::shared_ptr<Connection<T>> client) = 0; | |||
// Called when a client appears to have disconnected | |||
virtual void OnClientDisconnect(std::shared_ptr<Connection<T>> client) = 0; | |||
// Called when a message arrives | |||
virtual void OnMessage(std::shared_ptr<Connection<T>> client, Message<T>& msg) = 0; | |||
// Thread Safe Queue for incoming message packets | |||
ts_dequeue<OwnedMessage<T>> m_qMessagesIn; | |||
// Container of active validated connections | |||
std::deque<std::shared_ptr<Connection<T>>> m_deqConnections; | |||
// Order of declaration is important - it is also the order of initialisation | |||
asio::io_context m_asioContext; | |||
std::thread m_threadContext; | |||
// These things need an asio context | |||
asio::ip::tcp::acceptor m_asioAcceptor; // Handles new incoming connection attempts... | |||
// Clients will be identified in the "wider system" via an ID | |||
uint32_t nIDCounter = 10000; | |||
}; | |||
} |
@@ -0,0 +1,88 @@ | |||
TEMPLATE = app | |||
CONFIG += console c++17 | |||
CONFIG -= app_bundle | |||
CONFIG -= qt | |||
#INCLUDEPATH += ../AsioNetClientServer | |||
#LIBS += -L../AsioNetClientServer/debug -lasio_inet_client_server | |||
#LIBS += -pthread | |||
unix: CONFIG += link_pkgconfig | |||
unix: PKGCONFIG += libmodbuspp | |||
SOURCES += \ | |||
CustomStringUtilities.cpp \ | |||
DataAcquisition.cpp \ | |||
DataModel.cpp \ | |||
Evaluator.cpp \ | |||
MLAnalyzer.cpp \ | |||
MLLinReg.cpp \ | |||
ModbusDataBender.cpp \ | |||
ModbusDataInterface.cpp \ | |||
ModbusDataPOC.cpp \ | |||
ModbusInterface.cpp \ | |||
ModbusRtu.cpp \ | |||
ModbusTcp.cpp \ | |||
NetServer.cpp \ | |||
ParameterCharP.cpp \ | |||
ParameterDouble.cpp \ | |||
ParameterFloat.cpp \ | |||
ParameterS16.cpp \ | |||
ParameterUInt16.cpp \ | |||
ParameterUInt32.cpp \ | |||
PublisherBenderRcm.cpp \ | |||
PublisherInterface.cpp \ | |||
PublisherPowercenter.cpp \ | |||
SystemConfig.cpp \ | |||
SystemControler.cpp \ | |||
cappedstorage.cpp \ | |||
easylogging++.cc \ | |||
main.cpp | |||
HEADERS += \ | |||
CustomStringUtilities.h \ | |||
DataAcquisition.h \ | |||
DataModel.h \ | |||
Evaluator.h \ | |||
MLAlert.h \ | |||
MLAnalyzer.h \ | |||
MLLinReg.h \ | |||
ModbusDataBender.h \ | |||
ModbusDataInterface.h \ | |||
ModbusDataPOC.h \ | |||
ModbusInterface.h \ | |||
ModbusRegister.h \ | |||
ModbusRtu.h \ | |||
ModbusTcp.h \ | |||
NetServer.h \ | |||
ParameterCharP.h \ | |||
ParameterDouble.h \ | |||
ParameterFloat.h \ | |||
ParameterInterface.h \ | |||
ParameterS16.h \ | |||
ParameterUInt16.h \ | |||
ParameterUInt32.h \ | |||
PublisherBenderRcm.h \ | |||
PublisherInterface.h \ | |||
PublisherPowercenter.h \ | |||
PublisherType.h \ | |||
SystemConfig.h \ | |||
SystemControler.h \ | |||
asioClientServer/net_client.h \ | |||
asioClientServer/net_common.h \ | |||
asioClientServer/net_connection.h \ | |||
asioClientServer/net_dequeue_ts.h \ | |||
asioClientServer/net_message.h \ | |||
asioClientServer/net_server.h \ | |||
cappedstorage.h \ | |||
modbus_interface_lib.h \ | |||
net_server_client.h \ | |||
ts_map.h \ | |||
ts_queue.h \ | |||
easylogging++.h | |||
DISTFILES += \ | |||
build_baConditionMonitoring/system_conf.conf | |||
@@ -0,0 +1,70 @@ | |||
#include "cappedstorage.h" | |||
#include <cstring> | |||
#include <iostream> | |||
#include <easylogging++.h> | |||
#include "SystemConfig.h" | |||
CappedStorage::CappedStorage(bool _biased): biased(_biased) | |||
{ | |||
if(biased){ | |||
X.resize(0, 2); | |||
} | |||
else | |||
X.resize(0, 1); | |||
accessMtx = std::make_unique<std::mutex>(); | |||
} | |||
void CappedStorage::lock(){ | |||
accessMtx->lock(); | |||
} | |||
void CappedStorage::unlock(){ | |||
accessMtx->unlock(); | |||
} | |||
void CappedStorage::store(const std::vector<uint16_t>& d) | |||
{ | |||
std::scoped_lock lock(*accessMtx); | |||
float value = 0; | |||
std::memcpy(&value, d.data(), 4); | |||
const double time = c::time_point_cast<c::seconds>(c::system_clock::now()).time_since_epoch().count(); | |||
X.conservativeResize(X.rows()+1, X.cols()); | |||
y.conservativeResize(y.rows()+1, 1); | |||
y(y.rows()-1, 0) = value; | |||
if(biased){ | |||
X(X.rows()-1, 0) = 1; | |||
X(X.rows()-1, 1) = time; | |||
} | |||
else | |||
X(X.rows()-1, 0) = time; | |||
} | |||
void CappedStorage::clear() | |||
{ | |||
std::scoped_lock lock(*accessMtx); | |||
X.resize(0, Eigen::NoChange_t()); | |||
} | |||
//Stream operator for 1 dimensional X Matrix | |||
std::ostream& operator<<(std::ostream& os, const CappedStorage& c){ | |||
std::scoped_lock lock(*(c.accessMtx)); | |||
int col =0; | |||
if(c.biased){ | |||
col = 1; | |||
} | |||
for (int i = 0; i < c.X.rows(); i++) { | |||
os << c.X(i, col) << ","; | |||
} | |||
os << std::endl; | |||
for (int i = 0; i < c.y.size(); i++) { | |||
os << c.y(i, 0) << ","; | |||
} | |||
os << std::endl; | |||
return os; | |||
} |
@@ -0,0 +1,49 @@ | |||
#ifndef CAPPEDSTORAGE_H | |||
#define CAPPEDSTORAGE_H | |||
#include <vector> | |||
#include <map> | |||
#include <string> | |||
#include <eigen3/Eigen/Core> | |||
#include <chrono> | |||
#include <iostream> | |||
#include <mutex> | |||
#include <memory> | |||
namespace c = std::chrono; | |||
//Class for one dimensional double values | |||
class CappedStorage | |||
{ | |||
private: | |||
//Stored values | |||
Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> X; | |||
Eigen::Matrix<double, Eigen::Dynamic, 1> y; | |||
std::unique_ptr<std::mutex> accessMtx; | |||
//If set, storage is extended by bias column (X(:, 1)) | |||
//to perform ML Algorithms | |||
const bool biased; | |||
public: | |||
CappedStorage(bool _biased); | |||
void store(const std::vector<uint16_t> &d); | |||
long size() const{ return X.rows(); }; | |||
void clear(); | |||
Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic>& getX(){return X;} | |||
Eigen::Matrix<double, Eigen::Dynamic, 1>& getY(){return y;} | |||
long long getN(){return X.cols();} | |||
long long getM(){return X.rows();} | |||
friend std::ostream &operator<<(std::ostream& os, const CappedStorage& c); | |||
void lock(); | |||
void unlock(); | |||
}; | |||
#endif // CAPPEDSTORAGE_H |
@@ -0,0 +1,84 @@ | |||
//Entry point of application, crates central controller class and initializes the publishers | |||
#include <iostream> | |||
#include <chrono> | |||
#include <filesystem> | |||
#include "SystemControler.h" | |||
#include "SystemConfig.h" | |||
#include <easylogging++.h> | |||
#include "PublisherBenderRcm.h" | |||
#include "PublisherPowercenter.h" | |||
INITIALIZE_EASYLOGGINGPP | |||
using namespace std; | |||
typedef std::unique_ptr<PublisherInterface> unique_publisher_ptr; | |||
typedef unsigned int u_int; | |||
int main() { | |||
//Load global System configuration for parameters | |||
SystemConfig configuration; | |||
configuration.readStandardConfig(); | |||
//Load config file and apply on all loggers | |||
std::filesystem::create_directory("log"); | |||
el::Configurations conf("log/logger_config.conf"); | |||
el::Loggers::reconfigureAllLoggers(conf); | |||
LOG(INFO) << "Started execution"; | |||
//Create the System Controller | |||
SystemControler controler; | |||
//register publisher at controler | |||
auto poc = std::make_unique<PublisherPowercenter>(); | |||
auto bender = std::make_unique<PublisherBenderRcm>(); | |||
//controler.registerPublisher(std::move(poc)); | |||
controler.registerPublisher(std::move(bender)); | |||
//Opens port for tcp/ip client connection | |||
controler.startIpTcpServer(); | |||
//Modbus Worker Thread, processes the modbus register queue | |||
controler.startModbusWorkerThread(); | |||
//Frequently enqueing of modbus register | |||
controler.startEnqueuingRegister(); | |||
//Test environment on main thread | |||
while(true) | |||
{ | |||
char key = cin.get(); | |||
switch (key) { | |||
case 'l': | |||
controler.deleteFiles(std::chrono::seconds(0)); | |||
break; | |||
case 'c': | |||
controler.cancelReadingModbusAlerts(); | |||
break; | |||
case 'e': | |||
controler.evaluate(); | |||
break; | |||
case 'q': | |||
return 0; | |||
case 'p': | |||
std::cout << configuration << std::endl; | |||
break; | |||
case 'f': | |||
controler.flushAfterNData(); | |||
break; | |||
default: | |||
break; | |||
} | |||
} | |||
LOG(INFO) << "End of execution\n"; | |||
return 0; | |||
} |
@@ -0,0 +1,6 @@ | |||
#include "mltimeseries.h" | |||
MLtimeSeries::MLtimeSeries() | |||
{ | |||
} |
@@ -0,0 +1,12 @@ | |||
#ifndef MLTIMESERIES_H | |||
#define MLTIMESERIES_H | |||
#include "MLAnalyzer.h" | |||
template <typename T> | |||
class MLtimeSeries : public MLAnalyzer<T> | |||
{ | |||
public: | |||
MLtimeSeries(); | |||
}; | |||
#endif // MLTIMESERIES_H |
@@ -0,0 +1,7 @@ | |||
#pragma once | |||
#include "ModbusInterface.h" | |||
#include "ModbusRtu.h" | |||
#include "ModbusTcp.h" | |||
#include <modbuspp/modbuspp.h> |
@@ -0,0 +1,64 @@ | |||
/* | |||
MMO Client/Server Framework using ASIO | |||
"Happy Birthday Mrs Javidx9!" - javidx9 | |||
Videos: | |||
Part #1: https://youtu.be/2hNdkYInj4g | |||
Part #2: https://youtu.be/UbjxGvrDrbw | |||
License (OLC-3) | |||
~~~~~~~~~~~~~~~ | |||
Copyright 2018 - 2020 OneLoneCoder.com | |||
Redistribution and use in source and binary forms, with or without | |||
modification, are permitted provided that the following conditions | |||
are met: | |||
1. Redistributions or derivations of source code must retain the above | |||
copyright notice, this list of conditions and the following disclaimer. | |||
2. Redistributions or derivative works in binary form must reproduce | |||
the above copyright notice. This list of conditions and the following | |||
disclaimer must be reproduced in the documentation and/or other | |||
materials provided with the distribution. | |||
3. Neither the name of the copyright holder nor the names of its | |||
contributors may be used to endorse or promote products derived | |||
from this software without specific prior written permission. | |||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
Links | |||
~~~~~ | |||
YouTube: https://www.youtube.com/javidx9 | |||
Discord: https://discord.gg/WhwHUMV | |||
Twitter: https://www.twitter.com/javidx9 | |||
Twitch: https://www.twitch.tv/javidx9 | |||
GitHub: https://www.github.com/onelonecoder | |||
Homepage: https://www.onelonecoder.com | |||
Author | |||
~~~~~~ | |||
David Barr, aka javidx9, ©OneLoneCoder 2019, 2020 | |||
*/ | |||
#pragma once | |||
#include "asioClientServer/net_common.h" | |||
#include "asioClientServer/net_dequeue_ts.h" | |||
#include "asioClientServer/net_message.h" | |||
#include "asioClientServer/net_client.h" | |||
#include "asioClientServer/net_server.h" | |||
#include "asioClientServer/net_connection.h" |
@@ -0,0 +1,90 @@ | |||
#ifndef TS_MAP_H | |||
#define TS_MAP_H | |||
#pragma once | |||
#include <map> | |||
#include <mutex> | |||
#include <condition_variable> | |||
template <typename KEY, typename VALUE> | |||
class ts_map | |||
{ | |||
public: | |||
ts_map() = default; | |||
ts_map(const ts_map<KEY, VALUE>&) = delete; | |||
virtual ~ts_map() { clear(); } | |||
public: | |||
void emplaceOrOverwrite(KEY key, VALUE value){ | |||
std::scoped_lock lock(muxMap); | |||
auto it = map.find(key); | |||
if(it != map.end()){ | |||
it->second = value; | |||
} | |||
else{ | |||
map.emplace(key, value); | |||
} | |||
} | |||
void emplace(KEY key, VALUE&& value){ | |||
std::scoped_lock lock(muxMap); | |||
map.emplace(key, std::move(value)); | |||
} | |||
auto find(KEY key){ | |||
std::scoped_lock lock(muxMap); | |||
return map.find(key); | |||
} | |||
// Returns true if Queue has no items | |||
bool empty() | |||
{ | |||
std::scoped_lock lock(muxMap); | |||
return map.empty(); | |||
} | |||
// Returns number of items in Queue | |||
size_t size() | |||
{ | |||
std::scoped_lock lock(muxMap); | |||
return map.size(); | |||
} | |||
// Clears Queue | |||
void clear() | |||
{ | |||
std::scoped_lock lock(muxMap); | |||
std::map<KEY, VALUE> empty; | |||
std::swap(map, empty); | |||
} | |||
void wait() | |||
{ | |||
while (empty()) | |||
{ | |||
std::unique_lock<std::mutex> ul(muxBlocking); | |||
cvBlocking.wait(ul); | |||
} | |||
} | |||
auto begin() const{ | |||
return map.begin(); | |||
} | |||
auto end() const{ | |||
return map.end(); | |||
} | |||
protected: | |||
std::mutex muxMap; | |||
std::map<KEY, VALUE> map; | |||
std::condition_variable cvBlocking; | |||
std::mutex muxBlocking; | |||
}; | |||
#endif // TS_MAP_H |
@@ -0,0 +1,143 @@ | |||
/* | |||
MMO Client/Server Framework using ASIO | |||
"Happy Birthday Mrs Javidx9!" - javidx9 | |||
Videos: | |||
Part #1: https://youtu.be/2hNdkYInj4g | |||
Part #2: https://youtu.be/UbjxGvrDrbw | |||
License (OLC-3) | |||
~~~~~~~~~~~~~~~ | |||
Copyright 2018 - 2020 OneLoneCoder.com | |||
Redistribution and use in source and binary forms, with or without | |||
modification, are permitted provided that the following conditions | |||
are met: | |||
1. Redistributions or derivations of source code must retain the above | |||
copyright notice, this list of conditions and the following disclaimer. | |||
2. Redistributions or derivative works in binary form must reproduce | |||
the above copyright notice. This list of conditions and the following | |||
disclaimer must be reproduced in the documentation and/or other | |||
materials provided with the distribution. | |||
3. Neither the name of the copyright holder nor the names of its | |||
contributors may be used to endorse or promote products derived | |||
from this software without specific prior written permission. | |||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
Links | |||
~~~~~ | |||
YouTube: https://www.youtube.com/javidx9 | |||
Discord: https://discord.gg/WhwHUMV | |||
Twitter: https://www.twitter.com/javidx9 | |||
Twitch: https://www.twitch.tv/javidx9 | |||
GitHub: https://www.github.com/onelonecoder | |||
Homepage: https://www.onelonecoder.com | |||
Author | |||
~~~~~~ | |||
David Barr, aka javidx9, �OneLoneCoder 2019, 2020 | |||
*/ | |||
#pragma once | |||
#include <queue> | |||
#include <mutex> | |||
#include <condition_variable> | |||
/* | |||
Threadsafe Queue to be accessed from different threads | |||
*/ | |||
template <typename T> | |||
class ts_queue | |||
{ | |||
public: | |||
ts_queue() = default; | |||
ts_queue(const ts_queue<T>&) = delete; | |||
virtual ~ts_queue() { clear(); } | |||
public: | |||
// Returns and maintains item at front of Queue | |||
const T& front() | |||
{ | |||
std::scoped_lock lock(muxQueue); | |||
return queue.front(); | |||
} | |||
// Returns and maintains item at back of Queue | |||
const T& back() | |||
{ | |||
std::scoped_lock lock(muxQueue); | |||
return queue.back(); | |||
} | |||
// Removes and returns the first item from Queue | |||
T pop_front() | |||
{ | |||
std::scoped_lock lock(muxQueue); | |||
auto t = std::move(queue.front()); | |||
queue.pop(); | |||
return t; | |||
} | |||
// Adds an item to front of Queue | |||
void push(const T& item) | |||
{ | |||
std::scoped_lock lock(muxQueue); | |||
queue.push(std::move(item)); | |||
std::unique_lock<std::mutex> ul(muxBlocking); | |||
cvBlocking.notify_one(); | |||
} | |||
// Returns true if Queue has no items | |||
bool empty() | |||
{ | |||
std::scoped_lock lock(muxQueue); | |||
return queue.empty(); | |||
} | |||
// Returns number of items in Queue | |||
size_t size() | |||
{ | |||
std::scoped_lock lock(muxQueue); | |||
return queue.size(); | |||
} | |||
// Clears Queue | |||
void clear() | |||
{ | |||
std::scoped_lock lock(muxQueue); | |||
std::queue<T> empty; | |||
std::swap(queue, empty); | |||
} | |||
void wait() | |||
{ | |||
while (empty()) | |||
{ | |||
std::unique_lock<std::mutex> ul(muxBlocking); | |||
cvBlocking.wait(ul); | |||
} | |||
} | |||
protected: | |||
std::mutex muxQueue; | |||
std::queue<T> queue; | |||
std::condition_variable cvBlocking; | |||
std::mutex muxBlocking; | |||
}; |