#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)); | |||||
} | |||||
#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&); | |||||
}; | |||||
#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(); | |||||
} | |||||
#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(); | |||||
}; |
#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; | |||||
} |
#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); | |||||
}; |
#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; | |||||
} | |||||
#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(); | |||||
}; | |||||
#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 |
#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; | |||||
//} | |||||
} |
#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 |
#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(); | |||||
} | |||||
} |
#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 |
#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); | |||||
} |
#pragma once | |||||
#include "ModbusDataInterface.h" | |||||
class ModbusDataBender : public ModbusDataInterface | |||||
{ | |||||
public: | |||||
ModbusDataBender(const unsigned int id); | |||||
}; | |||||
#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; | |||||
} |
#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(); | |||||
}; |
#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); | |||||
} | |||||
#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); | |||||
}; |
#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; | |||||
} |
#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]) |
#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, | |||||
}; | |||||
} |
#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(); | |||||
} | |||||
} |
#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"; } | |||||
}; | |||||
#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(); | |||||
} | |||||
} |
#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"; } | |||||
}; | |||||
#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; | |||||
} | |||||
#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); | |||||
}; |
#include "ParameterCharP.h" |
#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; } | |||||
}; |
#include "ParameterDouble.h" | |||||
#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; } | |||||
}; |
#include "ParameterFloat.h" | |||||
#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; } | |||||
}; | |||||
#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); } | |||||
}; |
#include "ParameterS16.h" | |||||
#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; } | |||||
}; |
#include "ParameterUInt16.h" | |||||
#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; } | |||||
}; |
#include "ParameterUInt32.h" | |||||
#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; } | |||||
}; |
#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(); | |||||
} |
#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; | |||||
}; | |||||
#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; | |||||
} |
#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&); | |||||
}; | |||||
#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; | |||||
} |
#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; | |||||
}; |
#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(); | |||||
} |
#pragma once | |||||
#include "PublisherInterface.h" | |||||
#include "ModbusDataPOC.h" | |||||
#include <memory> | |||||
class PublisherPowercenter : | |||||
public PublisherInterface | |||||
{ | |||||
private: | |||||
public: | |||||
PublisherPowercenter(); | |||||
virtual ~PublisherPowercenter(); | |||||
std::string getName()const override; | |||||
}; |
#pragma once | |||||
enum class PublisherType{ | |||||
NA = 0, | |||||
RCMS_BENDER, | |||||
POWERCENTER | |||||
}; |
#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 ""; | |||||
} |
#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); | |||||
}; |
#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>(); | |||||
} |
#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); } | |||||
}; |
/* | |||||
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; | |||||
}; | |||||
} |
/* | |||||
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> |
/* | |||||
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; | |||||
}; | |||||
} |
/* | |||||
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; | |||||
}; | |||||
} |
/* | |||||
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; | |||||
} | |||||
}; | |||||
} |
/* | |||||
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; | |||||
}; | |||||
} |
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 | |||||
#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; | |||||
} |
#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 |
//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; | |||||
} |
#include "mltimeseries.h" | |||||
MLtimeSeries::MLtimeSeries() | |||||
{ | |||||
} |
#ifndef MLTIMESERIES_H | |||||
#define MLTIMESERIES_H | |||||
#include "MLAnalyzer.h" | |||||
template <typename T> | |||||
class MLtimeSeries : public MLAnalyzer<T> | |||||
{ | |||||
public: | |||||
MLtimeSeries(); | |||||
}; | |||||
#endif // MLTIMESERIES_H |
#pragma once | |||||
#include "ModbusInterface.h" | |||||
#include "ModbusRtu.h" | |||||
#include "ModbusTcp.h" | |||||
#include <modbuspp/modbuspp.h> |
/* | |||||
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" |
#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 |
/* | |||||
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; | |||||
}; |