diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0f831ca --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build/ +defrost.cfg \ No newline at end of file diff --git a/BaseListeningThread.cpp b/BaseListeningThread.cpp new file mode 100644 index 0000000..55675b7 --- /dev/null +++ b/BaseListeningThread.cpp @@ -0,0 +1,100 @@ +#include "BaseListeningThread.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libdiff.h" + +using namespace std; + +BaseListeningThread::BaseListeningThread(LogFile *log, int port) : log(log), port(port) +{ + +} + +void BaseListeningThread::Run() +{ + string name = typeid(*this).name(); + bool first_time = true; + while (true) + { + if (first_time) first_time = false; + else sleep(5); + try { + // сокет, на котором мы слушаем соединения + int listensock; + // создаем сокет для приема соединений + if ((listensock = socket(PF_INET, SOCK_STREAM, 0)) == -1) + { + log->LogString("Could not create socket for listening..."); + continue; + } + + // задаем опцию REUSEADDR на сокете. Это нужно для его пересозданияв случае, когда + // прием соединений временно прекращается + if (setsockopt(listensock, SOL_SOCKET, SO_REUSEADDR, (&SOCKET_OPTION_TRUE), sizeof(SOCKET_OPTION_TRUE)) == -1) + { + log->LogString("Could not set socket option..."); + continue; + } + + struct sockaddr_in listenaddr; + + // Заполнить структуру данных с адресом сокета + memset(&listenaddr, 0, sizeof(listenaddr)); + listenaddr.sin_family = AF_INET; + listenaddr.sin_addr.s_addr = htonl(INADDR_ANY); + listenaddr.sin_port = htons(port); + + // привязываем сокет к адресу + if (bind(listensock, (struct sockaddr *)(&listenaddr), sizeof(struct sockaddr_in)) == -1) + { + log->LogString("Could not bind listening socket to port " + ToString(port)); + continue; + } + + if(listen(listensock, LISTEN_BACKLOG) == -1) + { + log->LogString("Could not set socket into listening mode..."); + continue; + } + + struct sockaddr_in peeraddr; + socklen_t paddrlen; + paddrlen = sizeof(peeraddr); + int recsock; + log->LogString(name + " accepting connections on port " + ToString(port)); + while(true) + { + (recsock = accept(listensock, (struct sockaddr *)(&peeraddr), &paddrlen)); + if (recsock == -1) + { + log->LogString("Error accepting connection, errno = " + ToString(errno)); + // поспим немного, чтобы не ломать ничего лишнего и не заспамливать логи + sleep(1); + continue; + } + // напишем чего-нибудь про пришедшее соединение + log->LogString(name + " accepted connection from " + ToString(inet_ntoa(peeraddr.sin_addr)) + ":" + ToString(ntohs(peeraddr.sin_port))); + OnNewClient(recsock); + } + } + catch (string s) + { + log->LogString("Exception in " + name + ": " + s); + } + catch (...) + { + log->LogString("Unknown exception in " + name); + } + } +} diff --git a/BaseListeningThread.h b/BaseListeningThread.h new file mode 100644 index 0000000..dd45627 --- /dev/null +++ b/BaseListeningThread.h @@ -0,0 +1,21 @@ +#ifndef DATA_PROCESSOR_BASE_LISTENING_THREAD_H +#define DATA_PROCESSOR_BASE_LISTENING_THREAD_H + +#include "Thread.h" +#include "liblog.h" + +class BaseListeningThread : public ftc::Thread +{ + const int SOCKET_OPTION_TRUE = 1; + const int LISTEN_BACKLOG = 100; + + int port; +protected: + LogFile *log; + virtual void OnNewClient(int socket) = 0; + virtual void Run() override; +public: + BaseListeningThread(LogFile *log, int port); +}; + +#endif diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..1753527 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 3.0) +project(defrost) + +set(SOURCE_FILES +BaseListeningThread.h +BaseListeningThread.cpp + +ModbusServiceListenThread.h +ModbusServiceListenThread.cpp +ModbusServiceWorker.h +ModbusServiceWorker.cpp + +SynchronousSocket.h +SynchronousSocket.cpp + +Thread.h +Thread.cpp + +configparser.h +configparser.cpp +globals.h +globals.cpp + +defrost.cpp + +libdiff.h +libdiff.cpp +liblog.h +liblog.cpp +) + +link_libraries(pthread) +add_executable(defrost ${SOURCE_FILES} ./defrost.cpp) \ No newline at end of file diff --git a/ModbusServiceListenThread.cpp b/ModbusServiceListenThread.cpp new file mode 100644 index 0000000..2cbaa4a --- /dev/null +++ b/ModbusServiceListenThread.cpp @@ -0,0 +1,15 @@ +#include "ModbusServiceListenThread.h" +#include "ModbusServiceWorker.h" + +ModbusServiceListenThread::ModbusServiceListenThread(LogFile *mainlog, int port) + : BaseListeningThread(mainlog, port) +{ + +} + +void ModbusServiceListenThread::OnNewClient(int socket) +{ + ModbusServiceWorker* worker = new ModbusServiceWorker(socket, log); + // worker удалится внутри класса Thread, так что можем на него с чистой совестью забить + Thread::Perform(worker); +} diff --git a/ModbusServiceListenThread.h b/ModbusServiceListenThread.h new file mode 100644 index 0000000..98092a8 --- /dev/null +++ b/ModbusServiceListenThread.h @@ -0,0 +1,17 @@ +#ifndef MODBUSSERVICELISTENTHREAD_H +#define MODBUSSERVICELISTENTHREAD_H + +#include "liblog.h" +#include "BaseListeningThread.h" + +using namespace ftc; + +class ModbusServiceListenThread : public BaseListeningThread +{ +protected: + virtual void OnNewClient(int socket) override; +public: + ModbusServiceListenThread(LogFile *log, int port); +}; + +#endif // FORMULASERVICELISTENTHREAD_H diff --git a/ModbusServiceWorker.cpp b/ModbusServiceWorker.cpp new file mode 100644 index 0000000..c4dabb0 --- /dev/null +++ b/ModbusServiceWorker.cpp @@ -0,0 +1,100 @@ +#include "ModbusServiceWorker.h" +#include "libdiff.h" +#include "globals.h" + +#include + +ModbusServiceWorker::ModbusServiceWorker(int socket, LogFile *log) + : socket(socket), log(log) +{ + s.Init(socket); +} + +void ModbusServiceWorker::Run() +{ + log->LogString("Started ModbusServiceWorker"); + try { + while (true) + { + string header = s.Receive(7); + string transaction_id = header.substr(0, 2); + string protocol_id = header.substr(2, 2); + int length = ((int)(unsigned char)header[5]) | (((int)(unsigned char)header[4]) << 8); + string unit_identifier = header.substr(6, 1); + log->LogString("Receiving modbus frame of length " + ToString(length)); + string data = s.Receive(length - 1); + log->LogString("Received modbus frame of length " + ToString(length)); + string answer_data = ""; + int function = data[0]; + log->LogString("Processing function " + ToString(function)); + if (function == 3) + { + LockMutex(register_mutex); + int address = GetInt(data.substr(1, 2)); + int amount = GetInt(data.substr(3, 2)); + string result = ""; + for (int a = address; a < address + amount; a++) + { + int val = 0; + if (a >= 0 && a <= 65535) val = registers[a]; + result += (char)((val >> 8) & 0xFF); + result += (char)(val & 0xFF); + } + ReleaseMutex(register_mutex); + answer_data += (char)3; + answer_data += (char)result.length(); + answer_data += result; + } + else if (function == 6) + { + LockMutex(register_mutex); + int address = GetInt(data.substr(1, 2)); + int value = GetInt(data.substr(3, 2)); + if (address >= 0 && address <= 65535) + { + registers[address] = value; + } + ReleaseMutex(register_mutex); + answer_data = data; + } + else throw (string)("Unknown function " + ToString(function)); + answer_data = unit_identifier + answer_data; + string length_str = ""; + length_str += (char)((answer_data.length() >> 8) & 0xFF); + length_str += (char)((answer_data.length()) & 0xFF); + log->LogString("Sending answer"); + s.Send(transaction_id + protocol_id + length_str + answer_data); + log->LogString("Answer sent"); + } + } + catch (string s) + { + log->LogString("Exception in ModbusServiceWorker: " + s); + } + catch (std::exception e) + { + log->LogString("Exception in ModbusServiceWorker: " + ToString(e.what())); + } + catch (...) + { + log->LogString("Unknown exception in ModbusServiceWorker"); + // непонятно, что случилось, закрываем сокет и все дела + } + s.Disconnect(); +} + +int ModbusServiceWorker::GetInt(const string &s) +{ + int res = 0; + for (int i = 0; i < (int)s.length(); i++) + { + res <<= 8; + res += (int)(unsigned char)s[i]; + } + return res; +} + +ModbusServiceWorker::~ModbusServiceWorker() +{ + s.Disconnect(); +} diff --git a/ModbusServiceWorker.h b/ModbusServiceWorker.h new file mode 100644 index 0000000..e16af8f --- /dev/null +++ b/ModbusServiceWorker.h @@ -0,0 +1,24 @@ +#ifndef MODBUS_SERVICE_WORKER_H +#define MODBUS_SERVICE_WORKER_H + +#include "Thread.h" +#include "SynchronousSocket.h" +#include "liblog.h" + +using namespace ftc; + +class ModbusServiceWorker : public Thread +{ + int socket; + SynchronousSocket s; + LogFile *log; + + int GetInt(const std::string &s); +protected: + virtual void Run(); +public: + ModbusServiceWorker(int socket, LogFile *log); + virtual ~ModbusServiceWorker(); +}; + +#endif diff --git a/README.md b/README.md index d041bc1..3dd112e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,17 @@ -# defrost_v2 - -Новая версия размораживалки (для работы на обычном ПК) \ No newline at end of file +Простенькое управляемое реле. + +При запуске поднимает modbus tcp сервер, с которым можно общаться и управлять (а также читать текущие показания). + +Список регистров: + +* 0 - Температура включения реле (если ниже, включится) (в 0.01 градуса) +* 1 - Температура выключения реле (если выше, выключится) (в 0.01 градуса) +* 2 - Режим работы (0 - авто, 1 - вручную) +* 3 - Текущая температура (в 0.01 градуса) +* 4 - Текущее состояние реле (0 - выключено, 1 - включено) +* 5 - Дополнительная температура (второй датчик) + +Используется вот такой термистор +https://files.amperka.ru/datasheets/B57164.pdf +Из них тот у которого 10кОм при 25градусах + diff --git a/SynchronousSocket.cpp b/SynchronousSocket.cpp new file mode 100644 index 0000000..e146971 --- /dev/null +++ b/SynchronousSocket.cpp @@ -0,0 +1,161 @@ +#include "SynchronousSocket.h" + +#include +#include +#include +#include +#include +#include + +SynchronousSocket::SynchronousSocket() +{ + socket = -1; +} + +void SynchronousSocket::Init(int s) +{ + socket = s; +} + +bool SynchronousSocket::IsConnected() +{ + return (socket != -1); +} + +void SynchronousSocket::Connect(string host, int port) +{ + // уже подключены? + if (socket != -1) return; + + struct sockaddr_in remoteaddr; + + struct addrinfo *addresses; + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_socktype = SOCK_STREAM; + if (getaddrinfo(host.c_str(), NULL, NULL, &addresses) != 0) + { + throw (string)("Unable to getaddrinfo"); + return; + } + // копипастим себе самый первый адрес (в принципе, нам любой норм) + memcpy(&remoteaddr, addresses[0].ai_addr, sizeof(remoteaddr)); + remoteaddr.sin_port = htons(port); + freeaddrinfo(addresses); + // создаем сокет + if ((socket = ::socket(PF_INET, SOCK_STREAM, 0)) == -1) + { + throw (string)("Unable to create socket"); + return; + } + if (connect(socket, (struct sockaddr *)(&remoteaddr), sizeof(struct sockaddr_in)) == -1) + { + if (errno != EINPROGRESS) + { + socket = -1; + throw (string)("Unable to connect"); + return; + } + } +} + +void SynchronousSocket::Send(const string & data) +{ + int pos = 0; + time_t start = time(NULL); + while (pos < (int)data.length()) + { + int res = send(socket, &((data.data())[pos]), data.length() - pos, 0); + if (res == -1) + { + if (errno == EAGAIN || errno == EWOULDBLOCK) + { + if (time(NULL) - start > TIMEOUT) + { + throw (string)("Timeout"); + } + continue; + } + else + { + // сокет поломался - закрываем его + throw (string)("Socket closed"); + } + } + else + { + pos += res; + } + } +} + +string SynchronousSocket::Receive(int amount) +{ + string r = ""; + time_t start = time(NULL); + while ((int)r.length() < amount) + { + char buf[1024]; + int to_recv = min((int)sizeof(buf), amount - (int)r.length()); + int res = recv(socket, buf, to_recv, 0); + if (res == 0) + { + // бросаем специфический exception, означающий, что сокет закрыт + throw (string)("Socket closed"); + } + else if (res == -1) + { + if (errno == EAGAIN || errno == EWOULDBLOCK) + { + if (time(NULL) - start > TIMEOUT) + { + throw (string)("Timeout"); + } + continue; + } + else + { + // что-то сломалось, надо закрыть сокет на всякий случай + // бросаем специфический exception, означающий, что сокет закрыт + throw (string)("Socket closed"); + } + } + else + { + for (int i = 0; i < res; i++) r += buf[i]; + } + } + return r; +} + +void SynchronousSocket::Disconnect() +{ + if (socket == -1) return; + close(socket); + socket = -1; +} + +string SynchronousSocket::ReceiveMessage() +{ + string len_s = Receive(4); + int len = 0; + for (size_t i = 0; i < len_s.length(); i++) + { + len |= ((unsigned char)len_s[i]) << (8 * i); + } + return Receive(len); +} + +void SynchronousSocket::SendMessage(const string & m) +{ + int len = m.length(); + string len_s = ""; + for (int i = 0; i < 4; i++) + { + len_s += (char)((len >> (8 * i)) & 0xFF); + } + Send(len_s); + Send(m); +} + + diff --git a/SynchronousSocket.h b/SynchronousSocket.h new file mode 100644 index 0000000..c6ab02c --- /dev/null +++ b/SynchronousSocket.h @@ -0,0 +1,29 @@ +#ifndef SYNCHRONOUS_SOCKET_HEADER_FILE_INCLUDED +#define SYNCHRONOUS_SOCKET_HEADER_FILE_INCLUDED + +#include + +using namespace std; + +class SynchronousSocket +{ +private: + const int TIMEOUT = 10; // in seconds +protected: + // собственно сокет + int socket; +public: + SynchronousSocket(); + // Используется для проброса уже готового сокета и работы с ним + void Init(int s); + bool IsConnected(); + virtual void Connect(string host, int port); + virtual void Send(const string &data); + virtual string Receive(int amount); + void Disconnect(); + string ReceiveMessage(); + void SendMessage(const string &m); +}; + + +#endif diff --git a/Thread.cpp b/Thread.cpp new file mode 100644 index 0000000..6bf2e37 --- /dev/null +++ b/Thread.cpp @@ -0,0 +1,48 @@ +#include "Thread.h" + +void * ftc::Thread::DoRun(void * arg) +{ + try { + ((Thread*)arg)->is_running = true; + ((Thread*)arg)->Run(); + } + catch (...) { + + } + ((Thread*)arg)->is_running = false; + if (((Thread*)arg)->delete_after_finish) + { + delete ((Thread*)arg); + } + return NULL; +} + +void ftc::Thread::Run() +{ +} + +bool ftc::Thread::Start() +{ + pthread_attr_t attr; + if (pthread_attr_init(&attr) != 0) + { + return false; + } + if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)) + { + return false; + } + is_running = true; + if (pthread_create(&thread, &attr, &Thread::DoRun, this) != 0) + { + is_running = false; + return false; + } + return true; +} + +void ftc::Thread::Perform(Thread *work) +{ + work->delete_after_finish = true; + work->Start(); +} diff --git a/Thread.h b/Thread.h new file mode 100644 index 0000000..ee227e7 --- /dev/null +++ b/Thread.h @@ -0,0 +1,27 @@ +#ifndef CORE_THREAD_HEADER_FILE_INCLUDED +#define CORE_THREAD_HEADER_FILE_INCLUDED + +#include + +namespace ftc +{ + class Thread + { + private: + pthread_t thread; + bool is_running = false; + bool delete_after_finish = false; + static void* DoRun(void *arg); + protected: + // Основная функция потока, переопределите в дочерних классах + virtual void Run(); + public: + bool IsRunning() { return is_running; } + virtual ~Thread() {} + bool Start(); + // Запускает поток, удаляя его после завершения + static void Perform(Thread *work); + }; +} + +#endif diff --git a/configparser.cpp b/configparser.cpp new file mode 100644 index 0000000..379a033 --- /dev/null +++ b/configparser.cpp @@ -0,0 +1,463 @@ +#include "configparser.h" + +#include +#include +#include +#include +#include + +using namespace std; + +const int OPERATIONSET = 0; +const int OPERATIONADD = 1; +const int OPERATION_MERGE = 2; + +// конструктор ConfigParam +ConfigParam::ConfigParam() +{ + name = ""; + this->val.clear(); +} + +// создаем ConfigParam из строки +ConfigParam::ConfigParam(string s) +{ + name = s; + this->val.clear(); +} + +// создаем ConfigParam по имени и значению +ConfigParam::ConfigParam(string name, string value) +{ + this->name = name; + this->val.clear(); + ConfigValue v; + v.type = CONFIGVALUETYPESTRING; + v.sval = value; + this->val.push_back(v); +} + +bool ConfigParser::IsDelim(char c) +{ + if (IsWS(c)) return true; + if ((c == '{') || (c == '}')) return true; + if ((c == '[') || (c == ']')) return true; + if (c == ',') return true; + if (c == ';') return true; + if (c == '#') return true; + return false; +} + +bool operator<(const ConfigParam &a, const ConfigParam &b) +{ + return a.name < b.name; +} + +string ConfigParser::i2s(int a) +{ + ostringstream oss; + oss << a; + return oss.str(); +} + +// Является ли символ whitespace-ом +bool ConfigParser::IsWS(char c) +{ + return ((c == ' ') || (c == '\n') || (c == '\r') || (c == '\t')); +} + +// Можно ли такие символы использовать в названии параметров +bool ConfigParser::GoodForName(char c) +{ + if (IsWS(c)) return false; + if ((c == '[') || (c == ']')) return false; + if ((c == '{') || (c == '}')) return false; + if ((c == '(') || (c == ')')) return false; + if (c == '"') return false; + if (c == '\\') return false; + if (c == '+') return false; + if (c == '=') return false; + return true; +} + +// возвращает символ, соответствующий escape-последовательности "\c" +char ConfigParser::UnSlash(char c) +{ + if (c == 'n') return '\n'; + if (c == '\\') return '\\'; + if (c == '\"') return '"'; + return c; +} + +// разбирает s с l по r, считая, что она должна быть ConfigParam - ом +// возвращает, какая операция была применена +int ConfigParser::ParseConfigParam(int &l, int &r, ConfigParam &p) +{ + while ((l <= r) && (IsWS(s[l]))) + { + if (s[l] == '\n') cline++; + l++; + } + if (l > r) + throw "Expected parameter at line " + i2s(cline); + if (!GoodForName(s[l])) + { + throw "Parameter name expected at line " + i2s(cline); + } + p.name = ""; + while (GoodForName(s[l])) + { + if (l == r) throw "Unfinished param at line " + i2s(cline); + p.name += s[l]; + l++; + } + while ((l <= r) && (IsWS(s[l]))) + { + if (s[l] == '\n') cline++; + l++; + } + if (l > r) throw "Unfinished param at line " + i2s(cline); + int op; + if (s[l] == '+') + { + if ((l == r) || (s[l + 1] != '=')) throw "Unrecognized operation: + or += expected at line " + i2s(cline); + op = OPERATIONADD; + l += 2; + } + else if (s[l] == '=') + { + op = OPERATIONSET; + l++; + } + else if (s[l] == '(') + { + // this is a function call + l++; + string args = ""; + while (l <= r && s[l] != ')') + { + args += s[l]; + if (s[l] == '\n') cline++; + l++; + } + if (l > r) throw (string)("Unfinished function args at line " + i2s(cline)); + l++; + if (p.name == "include") + { + // пока так костыльно, годный парсинг аргументов надо вместе с глобальным + // рефакторингом парсера делать + TrimSpaces(args); + TrimQuotes(args); + ConfigParser t; + t.Load(args); + p.val.push_back(t.cfg); + // в этом случае значение имеет тип "множество" + p.val[0].type = CONFIGVALUETYPESET; + return OPERATION_MERGE; + } + else + { + throw "Unknown function " + p.name; + } + } + else throw "Unrecognized operation: + or += expected at line " + i2s(cline); + while ((l <= r) && (IsWS(s[l]))) + { + if (s[l] == '\n') cline++; + l++; + } + if (l > r) throw "Expected parameter value " + i2s(cline); + if (s[l] == '{') + { + ConfigValue a; + p.val.push_back(a); + l++; + // в этом случае значение имеет тип "множество" + p.val[0].type = CONFIGVALUETYPESET; + ParseConfigValue(l, r, p.val[0]); + } + else if (s[l] == '[') + { + l++; + ParseConfigParamValues(l, r, p); + } + else + { + ConfigValue a; + p.val.push_back(a); + // в этом случае значение имеет тип "строка" + p.val[0].type = CONFIGVALUETYPESTRING; + ParseConfigValue(l, r, p.val[0]); + } + return op; +} + +// Разбирает несколько ConfigValues, заданных через запятую +void ConfigParser::ParseConfigParamValues(int &l, int &r, ConfigParam &b) +{ + while(1) + { + while ((l <= r) && (IsWS(s[l]))) + { + if (s[l] == '\n') cline++; + l++; + } + if (l > r) throw "Unfinished array at line " + i2s(cline); + if (s[l] == ']') + { + l++; + break; + } + ConfigValue a; + b.val.push_back(a); + if (s[l] == '{') + { + l++; + b.val[b.val.size() - 1].type = CONFIGVALUETYPESET; + } + else + { + b.val[b.val.size() - 1].type = CONFIGVALUETYPESTRING; + } + ParseConfigValue(l, r, b.val[b.val.size() - 1]); + if (l > r) throw "Unfinished array at line " + i2s(cline); + if (s[l] == ']') + { + l++; + break; + } + l++; + } +} + +// разбирает строку s с l по r, считая, что она - корректный ConfigValue +void ConfigParser::ParseConfigValue(int &l, int &r, ConfigValue &vl) +{ + while ((l <= r) && (IsWS(s[l]))) + { + if (s[l] == '\n') cline++; + l++; + } + if (l > r) return; + // отсечем комментарии + if ((s[l] == ';') || (s[l] == '#')) + { + while (s[l] != '\n') l++; + ParseConfigValue(l, r, vl); + return; + } + // если в точке l наличествует символ-разделитель, значит + // наше Value закончилось + if ((s[l] == ',') || (s[l] == ']')) + { + return; + } + if (vl.type == CONFIGVALUETYPESET) + { + // нашли скобочку - set закрылся + if (s[l] == '}') + { + l++; + return; + } + if (!GoodForName(s[l])) + { + throw "Parameter name expected at line " + i2s(cline); + } + ConfigParam p; + int oper = ParseConfigParam(l, r, p); + if (oper == OPERATION_MERGE) + { + for (set::iterator t = p.val[0].pval.begin(); t != p.val[0].pval.end(); t++) + { + vl.pval.insert(*t); + } + } + else + { + set::iterator it = vl.pval.find(p); + if (it == vl.pval.end()) + { + vl.pval.insert(p); + } + else + { + int i; + if (oper == OPERATIONADD) + { + reverse(p.val.begin(), p.val.end()); + for (i = ((int)it->val.size() - 1); i >= 0; i--) + { + p.val.push_back(it->val[i]); + } + reverse(p.val.begin(), p.val.end()); + vl.pval.erase(it); + vl.pval.insert(p); + } + else if (oper == OPERATIONSET) + { + vl.pval.erase(it); + vl.pval.insert(p); + } + else + { + throw (string)("Internal error (wrong operation returned) at line " + i2s(cline)); + } + } + } + ParseConfigValue(l, r, vl); + } + else + { + vl.sval = ""; + int isslashed = 0; + int isquoted = 0; + while (1) + { + if (l > r) throw "Unfinished value at line " + i2s(cline); + if (isslashed) + { + vl.sval += UnSlash(s[l]); + isslashed = 0; + } + else + { + if (s[l] == '\\') isslashed = 1; + else if (s[l] == '"') isquoted = 1 - isquoted; + else + { + if (IsDelim(s[l])) + { + if (isquoted == 0) break; + if (s[l] == '\n') throw "Unexpected end of line at line " + i2s(cline); + } + vl.sval += s[l]; + } + } + l++; + } + } +} + +// убирает двойные кавычки в начале/конце строки +void ConfigParser::TrimQuotes(string &s) +{ + if (((int)s.length()) < 2) return; + if ((s[0] == '\"') && (s[((int)s.length()) - 1] == '\"')) + { + s = s.substr(1, s.length() - 2); + } +} + +void ConfigParser::Load(string fname) +{ + // Загружаем весь конфиг в одну строку, теперь будем ее разбирать + FILE *fin = fopen(fname.c_str(), "rt"); + if (!fin) throw "Config file " + fname + " not found!"; + int c = getc(fin); + slen = 0; + while (c != EOF) + { + slen++; + c = getc(fin); + } + s = new char[slen + 1]; + int i; + fclose(fin); + fin = fopen(fname.c_str(), "rt"); + for (i = 0; i < slen; i++) s[i] = getc(fin); + fclose(fin); + s[slen] = '\n'; + slen++; + // Теперь считаем всю строку конфиг-значением (так оно и должно быть) + cline = 1; + cfg.type = CONFIGVALUETYPESET; + cfg.sval = ""; + cfg.pval.clear(); + try{ + int l = 0; + int r = slen - 1; + ParseConfigValue(l, r, cfg); + delete []s; + } + catch(string ss) + { + delete []s; + throw "Error parsing " + fname + ": " + ss; + } + catch(...) + { + delete []s; + throw "Unrecognized error parsing " + fname; + } +} + + +// возвращает значение параметра (в старом стиле) +string ConfigParser::GetParam(string name) +{ + set::iterator p; + p = this->cfg.pval.find(ConfigParam(name)); + if (p != this->cfg.pval.end()) + { + if (p->val.size() == 0) throw "There are no values for parameter " + name; + return p->val[0].sval; + } + throw ((string)"No param \"" + name + "\" found!"); +} + +// возвращает значение параметра (в старом стиле) +string ConfigParser::GetParamSilent(string name) +{ + set::iterator p; + p = this->cfg.pval.find(ConfigParam(name)); + if (p != this->cfg.pval.end()) + { + if (p->val.size() == 0) return ""; + return p->val[0].sval; + } + return ""; +} + + +// возвращает какое-то одно значение параметра +// генерирует исключение при ошибке +string ConfigParam::GetSingleVal() const +{ + if (val.size() == 0) throw ((string)"No value available"); + if (val[0].type == CONFIGVALUETYPESTRING) return val[0].sval; + else + { + throw "Wrong parameter value type (string expected)"; + } +} + +// убивает в строке пробелы в начале и в конце +void ConfigParser::TrimSpaces(string &s) +{ + string tmp = s; + s = ""; + int wf = 0, i; + for (i = 0; i < ((int)tmp.length()); i++) + { + if (! IsWS(tmp[i]) ) + { + wf = 1; + } + if (wf == 1) s += tmp[i]; + } + while ((s.length() > 0) && (IsWS(s[s.length() - 1]))) + { + s.erase(--s.end()); + } +} + +// Ищет в этом value заданный параметр. Возвращает пустую строку, если такого нет. +string ConfigValue::GetParamVal(string pname) const +{ + if (this->type == CONFIGVALUETYPESTRING) throw ((string)"Attempted to get param from value, which is not a set"); + set::iterator p = this->pval.find((ConfigParam)(pname)); + if (p == pval.end()) return ""; + return p->GetSingleVal(); +} + diff --git a/configparser.h b/configparser.h new file mode 100644 index 0000000..b62c68e --- /dev/null +++ b/configparser.h @@ -0,0 +1,85 @@ +#ifndef FELIX_THE_CAT_FSYSTEM_CONFIGPARSER_HEADER_FILE_INCLUDED_KHKLJWGNBBVKJGLKXBVMZVEKLEKFJKJSAGSKAJGHJKBHQRPOTU +#define FELIX_THE_CAT_FSYSTEM_CONFIGPARSER_HEADER_FILE_INCLUDED_KHKLJWGNBBVKJGLKXBVMZVEKLEKFJKJSAGSKAJGHJKBHQRPOTU + +#include +#include +#include + +using namespace std; + +const char CONFIGVALUETYPESTRING = 0; +const char CONFIGVALUETYPESET = 1; + +class ConfigValue; + +// Класс описывающий параметр из конфигурационного файла +class ConfigParam +{ +public: + string name; + vector val; + // возвращает первое значение параметра (если их несколько) + string GetSingleVal() const; + // создаем из строки + ConfigParam(string name); + // создаем просто так + ConfigParam(); + // создаем параметр с единственным значением + ConfigParam(string name, string value); +}; + +bool operator<(const ConfigParam &a, const ConfigParam &b); + + +// Класс, описывающий значение параметра в конфигурационном файле +class ConfigValue +{ +public: + char type; + string sval; + set pval; + string GetParamVal(string pname) const; +}; + +// Класс, разбирающий конфигурационный файл +class ConfigParser +{ +private: + char *s; + int slen; + int mode; + int cline; + // переделывает число в строку + string i2s(int a); + // разбирает s с l по r, считая, что она должна быть ConfigValue - ом + void ParseConfigValue(int &l, int &r, ConfigValue &vl); + // возвращает, является ли символ whitespace-ом + bool IsWS(char c); + // возвращает, может ли такой символ быть в имени параметра + bool GoodForName(char c); + // убирает двойные кавычки в начале/конце строки (если, конечно, таковые имеются) + void TrimQuotes(string &s); + // возвращает символ, соответствующий escape-последовательности "\c" + char UnSlash(char c); + // разбирает s с l по r, считая, что она должна быть ConfigParam - ом + // возвращает, какая операция была применена + int ParseConfigParam(int &l, int &r, ConfigParam &p); + // возращает, считается ли такой символ разделителем + // разделителями считаются ws-ы, скобки и запятые + bool IsDelim(char c); + // Разбирает несколько ConfigValues, заданных через запятую + void ParseConfigParamValues(int &l, int &r, ConfigParam &b); + void TrimSpaces(string &s); +public: + ConfigValue cfg; + // загружает конфигурационный файл + void Load(string fname); + // возвращает значение параметра (в старом стиле) + // генерирует исключение, если такого нет + string GetParam(string name); + // возвращает значение параметра (в старом стиле) + // возвращает пустую строку, если такого нет + string GetParamSilent(string name); +}; + +#endif diff --git a/defrost.cfg.sample b/defrost.cfg.sample new file mode 100644 index 0000000..68517d5 --- /dev/null +++ b/defrost.cfg.sample @@ -0,0 +1,2 @@ +ListenPort = 15000 +LogPath = "/var/log/defrost.log" \ No newline at end of file diff --git a/defrost.cpp b/defrost.cpp new file mode 100644 index 0000000..50a2615 --- /dev/null +++ b/defrost.cpp @@ -0,0 +1,88 @@ +#include "configparser.h" +#include "globals.h" +#include "ModbusServiceListenThread.h" +#include "libdiff.h" + +#include +#include +#include +#include +#include +#include +#include + + +void temperature_update() +{ + // other helper variables + float temperature = 1.5; + + registers[REGISTER_CUR_TEMP] = (int)(temperature * 100); + float additional_temperature = 2.5; + registers[REGISTER_SECONDARY_TEMP] = (int)(additional_temperature * 100); + +} + +int main(int argc, char *argv[]) +{ + try { + // ����� lock-file, ����� ���������� ������ ���� ��������� ������� + int lockfile = open("/tmp/defrost.lock", O_RDWR | O_CREAT, 0777); + if (lockfile == -1) + { + cout << "Unable to open file for locking, errno = " << errno << "\n"; + return 1; + } + if (flock(lockfile, LOCK_EX | LOCK_NB) == -1) + { + cout << "Unable to lock file, errno = " << errno << "\n"; + return 1; + } + // load config + ConfigParser cfg; + cfg.Load(argv[1] ); + mainlog.InitLogging(cfg.GetParam("LogPath"), LIBLOGSINGLEFILE, "defrost", true); + mainlog.LogString("Defrost service started"); + + if (pthread_mutex_init(®ister_mutex, NULL) != 0) + { + mainlog.LogString("Unable to create registers_mutex"); + return EXIT_FAILURE; + } + registers[REGISTER_ON_TEMP] = 100; + registers[REGISTER_OFF_TEMP] = 500; + registers[REGISTER_MODE] = 0; + + // run modbus service listen thread + ModbusServiceListenThread *modbus_service = new ModbusServiceListenThread(&mainlog, atoi(cfg.GetParam("ListenPort").c_str())); + modbus_service->Start(); + + int io_value = 0; + // loop forever updating the temperature values every second + while (true) + { + LockMutex(register_mutex); + temperature_update(); + int mode = registers[REGISTER_MODE]; + if (mode == 0) + { + if (registers[REGISTER_CUR_TEMP] < registers[REGISTER_ON_TEMP]) io_value = 1; + else if (registers[REGISTER_CUR_TEMP] > registers[REGISTER_OFF_TEMP]) io_value = 0; + } + else if (mode == 1) + { + io_value = registers[REGISTER_CUR_STATE]; + } + registers[REGISTER_CUR_STATE] = io_value; + //gpio.write(io_value); + ReleaseMutex(register_mutex); + sleep(1); + } + } + catch (string s) + { + cerr << "Exception: " << s << "\n"; + return EXIT_FAILURE; + } + return 0; +} diff --git a/defrost.service b/defrost.service new file mode 100644 index 0000000..ef56aee --- /dev/null +++ b/defrost.service @@ -0,0 +1,13 @@ +[Unit] +Description=Defrost service +After=network.target + +[Service] +ExecStart=/home/root/bin/defrost /home/root/bin/defrost.cfg +User=root +Group=root +RuntimeDirectory=/home/root/bin +RuntimeDirectoryMode=0755 + +[Install] +WantedBy=multi-user.target diff --git a/globals.cpp b/globals.cpp new file mode 100644 index 0000000..5855fbd --- /dev/null +++ b/globals.cpp @@ -0,0 +1,6 @@ +#include "globals.h" + +int registers[65536]; +pthread_mutex_t register_mutex; +LogFile mainlog; + diff --git a/globals.h b/globals.h new file mode 100644 index 0000000..f5673b9 --- /dev/null +++ b/globals.h @@ -0,0 +1,17 @@ +#ifndef GLOBALS_HEADER_FILE_INCLUDED +#define GLOBALS_HEADER_FILE_INCLUDED + +#include "liblog.h" + +const int REGISTER_ON_TEMP = 0; +const int REGISTER_OFF_TEMP = 1; +const int REGISTER_MODE = 2; +const int REGISTER_CUR_TEMP = 3; +const int REGISTER_CUR_STATE = 4; +const int REGISTER_SECONDARY_TEMP = 5; + +extern int registers[65536]; +extern pthread_mutex_t register_mutex; +extern LogFile mainlog; + +#endif diff --git a/libdiff.cpp b/libdiff.cpp new file mode 100644 index 0000000..2d6d63b --- /dev/null +++ b/libdiff.cpp @@ -0,0 +1,177 @@ +#include "libdiff.h" + +#include +#include +#include +#include +#include +#include + +using namespace std; + +ostream& operator<<(ostream &out, const pthread_mutex_t m) +{ + out << &m; + return out; +} + +void LockMutex(pthread_mutex_t &m) +{ + int mres; + while ((mres = pthread_mutex_lock(&m)) != 0) + { + if (mres != EAGAIN) + { + throw (string)("Fatal error acquiring mutex"); + } + } +} + +void ReleaseMutex(pthread_mutex_t &m) +{ + int mres = pthread_mutex_unlock(&m); + if (mres != 0) + { + throw (string)("Error releasing mutex " + ToString(m) + " res = " + ToString(mres)); + } +} + +int Round(double v) +{ + if (v < -1e-4) return -(int)(-v + 0.5); + else return (int)(v + 0.5); +} + +void CP866ToUTF8(unsigned char *src, unsigned char *dest, int destlen) +{ + iconv_t icd = iconv_open("UTF-8", "CP866"); + if (icd == (iconv_t)-1) + { + throw (string)("Unable to open iconv descriptor, errno = " + ToString(errno)); + } + size_t slen = strlen((char*)src); + destlen--; + // странные временные переменные, потому что иначе компилятор ругается, что + // результат приведения типов - не lvalue + char *s = (char*)src; + char *d = (char*)dest; + size_t dl = destlen; + size_t t = iconv(icd, &s, &slen, &d, &dl); + iconv_close(icd); + if (t == (size_t)-1) + { + throw (string)("Error during string conversion, errno = " + ToString(errno)); + } + // терминируем строку, iconv сам этого не сделает + d[0] = 0; +} + +string CP1251ToUTF8(const string &src) +{ + unsigned char *dest = new unsigned char[src.length() * 4]; + CP1251ToUTF8((const unsigned char *)src.c_str(), dest, src.length() * 4); + string res((char*)dest); + delete[] dest; + return res; +} + +void CP1251ToUTF8(const unsigned char *src, unsigned char *dest, int destlen) +{ + iconv_t icd = iconv_open("UTF-8", "CP1251"); + if (icd == (iconv_t)-1) + { + throw (string)("Unable to open iconv descriptor, errno = " + ToString(errno)); + } + size_t slen = strlen((char*)src); + destlen--; + // странные временные переменные, потому что иначе компилятор ругается, что + // результат приведения типов - не lvalue + char *s = (char*)src; + char *d = (char*)dest; + size_t dl = destlen; + size_t t = iconv(icd, &s, &slen, &d, &dl); + iconv_close(icd); + if (t == (size_t)-1) + { + throw (string)("Error during string conversion, errno = " + ToString(errno)); + } + // терминируем строку, iconv сам этого не сделает + d[0] = 0; +} + +int UnpackBCD(int a) +{ + if ((a & 0xF) > 9 || + ((a >> 4) & 0xF) > 9) throw (string)("Wrong BCD number: " + ToString(a)); + return (((a >> 4) & 0xF) * 10) + (a & 0xF); +} + +bool CheckBCD(int a) +{ + if ((a & 0xF) > 9 || + ((a >> 4) & 0xF) > 9) return false; + return true; +} + +int PackBCD(int a) +{ + return (((a / 10) % 10) << 4) | (a % 10); +} + +unsigned long long GetMilliseconds() +{ + struct timeb tm; + if (ftime(&tm) == -1) + { + // should never happen in fact + throw (string)("ftime() failed somehow"); + } + unsigned long long res = tm.time; + res *= 1000; + res += tm.millitm; + return res; +} + +string ReplaceAll(string text, string pattern, string replacement) +{ + while (1) + { + size_t pos = text.find(pattern); + if (pos == string::npos) return text; + text = text.replace(pos, pattern.length(), replacement); + } +} + +string IntToString(int a, int base) +{ + // no itoa in modern C++, what a pity + int sign = a < 0 ? -1 : 1; + if (a < 0) a = -a; + string res; + while (a > 0) + { + int cur = a % base; + char c = cur + '0'; + if (cur > 9) c = cur + 'A' - 10; + res += c; + a /= base; + } + if (sign < 0) res += '-'; + reverse(res.begin(), res.end()); + return res; +} + +int RaiseFDLimit() +{ + struct rlimit rl; + if (getrlimit(RLIMIT_NOFILE, &rl) != 0) + { + return -1; + } + rl.rlim_cur = rl.rlim_max; + if (setrlimit(RLIMIT_NOFILE, &rl) != 0) + { + return -1; + } + return rl.rlim_cur; +} diff --git a/libdiff.h b/libdiff.h new file mode 100644 index 0000000..0b02a39 --- /dev/null +++ b/libdiff.h @@ -0,0 +1,64 @@ +#ifndef FELIX_THE_CAT_DIFFERENT_LIBRARY_INCLUDED_AKJGNKJABVKALJGLKAJGLAKJG +#define FELIX_THE_CAT_DIFFERENT_LIBRARY_INCLUDED_AKJGNKJABVKALJGLKAJGLAKJG + +#include + +#include +#include +#include +#include + +using namespace std; + +template +string ToString(const T& a) +{ + ostringstream oss; + oss.setf(ios::fixed | ios::showpoint); + oss.precision(10); + oss << a; + return oss.str(); +} + +ostream& operator<<(ostream &out, const pthread_mutex_t m); + +void LockMutex(pthread_mutex_t &m); +void ReleaseMutex(pthread_mutex_t &m); + + +int Round(double v); + +// конвертирует из CP866 (в котором часто девайсы шлют всякую фигню) +// в UTF-8 (в котором весь наш код), чтобы можно было сравнивать строки +// и не иметь в исходниках две кодировки +// src предполагается нуль-терминированным +void CP866ToUTF8(unsigned char *src, unsigned char *dest, int destlen); +// То же самое для CP1251 +void CP1251ToUTF8(const unsigned char *src, unsigned char *dest, int destlen); + +string CP1251ToUTF8(const string &src); + +// распаковывает BCD-число. Сделано в виде функции, а не макроса, чтобы +// можно было подставлять в нее функции, изменяющие состояние параметров +int UnpackBCD(int a); +// аналогично, упаковывает BCD-число +int PackBCD(int a); +// проверяет, является ли a валидным BCD-байтом +bool CheckBCD(int a); + + +// получает количество миллисекунд с начала эпохи (иногда нужно для) +// всяких точных таймаутов при приеме/отправке +unsigned long long GetMilliseconds(); + +// заменяет в text все вхождения pattern на replacement +// аккуратно со случаем, когда replacement содержит pattern - может повиснуть (!) +string ReplaceAll(string text, string pattern, string replacement); + +// конвертирует число в строку в заданной base +string IntToString(int a, int base = 10); + +// поднимает до упора лимит на количество открытых дескрипторов +int RaiseFDLimit(); + +#endif diff --git a/liblog.cpp b/liblog.cpp new file mode 100644 index 0000000..c32c845 --- /dev/null +++ b/liblog.cpp @@ -0,0 +1,230 @@ +#include "liblog.h" +#include "libdiff.h" + +#include + +#include +#include +#include +#include +#include + +using namespace std; + +LogFile::LogFile() +{ + logmethod = -1; + logpath = ""; + file = NULL; + initialized = false; +} + +string IntToLength(int a, int len) +{ + string s = ToString(a); + while ((int)s.length() < len) s = '0' + s; + return s; +} + + +void LogFile::InitLogging(string logpath, int method, string prefix, bool reopen) +{ + this->logmethod = method; + this->logpath = logpath; + this->fileprefix = prefix; + this->reopen = reopen; + // инициализируем mutex + if (pthread_mutex_init(&filemutex, NULL) != 0) + { + throw (string)("Unable to create mutex"); + } + if (!reopen) + { + // открываем файл + if (method == LIBLOGSINGLEFILE) + { + // формируем имя файла + ostringstream filename; + filename << logpath << "/" << fileprefix << ".log"; + file = OpenFileWithRetries(filename.str(), "at"); + if (file == NULL) + { + throw (string)("Unable to open file for logging"); + } + } + else + { + struct tm curtime; + time_t ct = time(NULL); + localtime_r(&ct, &curtime); + // теперь имя файла выглядит намного забавнее + ostringstream filename; + filename << logpath << "/" << fileprefix << curtime.tm_year + 1900 << IntToLength(curtime.tm_mon + 1, 2) + << IntToLength(curtime.tm_mday, 2) << ".log"; + file = OpenFileWithRetries(filename.str(), "at"); + lastname = filename.str(); + if (file == NULL) + { + throw (string)("Unable to open file for logging"); + } + } + } + initialized = true; +} + + +// записывает в лог-файл строку +// файл определяется исходя из метода +// строка предваряется текущим временем +// в некотором формате +void LogFile::LogString(string s, string pfx) +{ + // Если лог еще не инициализирован, ничего не пишем (штатная ситуация, если, + // например, лог пришлось заглушить). + if (!initialized) return; + #ifdef FTC_DEBUG + cout << s << endl; + #endif + int res; + while ((res = pthread_mutex_lock(&filemutex)) != 0) + { + if (res != EAGAIN) + { + throw (string)("Fatal error acquiring mutex"); + } + } + struct tm curtime; + time_t ct = time(NULL); + localtime_r(&ct, &curtime); + if (this->logmethod == LIBLOGSINGLEFILE) + { + if (reopen) + { + // формируем имя файла + ostringstream filename; + filename << logpath << "/" << fileprefix << ".log"; + file = OpenFileWithRetries(filename.str(), "at"); + if (file == NULL) + { + throw (string)("Unable to open file for logging"); + pthread_mutex_unlock(&filemutex); + return; + } + } + // теперь сгенерить строку + ostringstream oss; + oss << pfx << " " << IntToLength(curtime.tm_year + 1900, 4) << "-" << IntToLength(curtime.tm_mon + 1, 2) << "-" + << IntToLength(curtime.tm_mday, 2) << " " << IntToLength(curtime.tm_hour, 2) + << ":" << IntToLength(curtime.tm_min, 2) << ":" << IntToLength(curtime.tm_sec, 2); + oss << " " << s + "\n"; + // и записать ее куда следует + fprintf(file, "%s", oss.str().c_str()); + if (reopen) + { + if (file != NULL) + { + fclose(file); + file = NULL; + } + } + } + else if (this->logmethod == LIBLOGDAYFILES) + { + // переоткрываем файл если нужно + ostringstream filename; + filename << logpath << "/" << fileprefix << curtime.tm_year + 1900 << IntToLength(curtime.tm_mon + 1, 2) + << IntToLength(curtime.tm_mday, 2) << ".log"; + if (!reopen) + { + if (filename.str() != lastname) + { + if (file != NULL) + { + cerr << "LIBLOG, fclose " << file << "\n"; + cerr << "LIBLOG, lastname = " << lastname << " newname = " << filename.str() << "\n"; + fclose(file); + } + file = OpenFileWithRetries(filename.str(), "at"); + if (file == NULL) + { + throw (string)("Unable to open file for logging"); + pthread_mutex_unlock(&filemutex); + return; + } + lastname = filename.str(); + cerr << "LIBLOG, reopened file = " << file << "\n"; + } + } + else + { + file = OpenFileWithRetries(filename.str(), "at"); + if (file == NULL) + { + throw (string)("Unable to open file for logging"); + pthread_mutex_unlock(&filemutex); + return; + } + } + // теперь сгенерить строку + ostringstream oss; + oss << pfx << " " << IntToLength(curtime.tm_hour, 2) + << ":" << IntToLength(curtime.tm_min, 2) << ":" << IntToLength(curtime.tm_sec, 2); + oss << " " << s << "\n"; + // и записать ее куда следует + if (file != NULL) fprintf(file, "%s", oss.str().c_str()); + if (reopen) + { + if (file != NULL) + { + fclose(file); + file = NULL; + } + } + } + else + { + // ничего не делаем - неинициализированный лог - не проблема + } + if (!reopen) + { + if (file != NULL) fflush(file); + } + pthread_mutex_unlock(&filemutex); +} + +char GHC(char c) +{ + if (c < 10) return (c + '0'); + else return (c - 10 + 'A'); +} + +string GetHex(unsigned char c) +{ + string res; + int l, h; + h = (c >> 4) & 15; + l = c & 15; + res += GHC(h); + res += GHC(l); + return res; +} + + +FILE* LogFile::OpenFileWithRetries(string fname, const char *mode) +{ + FILE *res = fopen(fname.c_str(), mode); + int rcnt = 0; + while (res == NULL) + { + rcnt++; + if (rcnt > LIBLOGNUMRETRIESTOOPEN) break; + sleep(1); + res = fopen(fname.c_str(), mode); + } + return res; +} + +string GetHexString(unsigned int a) +{ + return GetHex((a >> 24) & 0xFF) + GetHex((a >> 16) & 0xFF) + GetHex((a >> 8) & 0xFF) + GetHex(a & 0xFF); +} diff --git a/liblog.h b/liblog.h new file mode 100644 index 0000000..4d73de5 --- /dev/null +++ b/liblog.h @@ -0,0 +1,49 @@ +#ifndef FELIX_THE_CAT_LOGGING_LIBRARY_HEADER_FILE_INCLUDED_EJGHJWVNKJNWKJGNKJWGJKGKJHKJH +#define FELIX_THE_CAT_LOGGING_LIBRARY_HEADER_FILE_INCLUDED_EJGHJWVNKJNWKJGNKJWGJKGKJHKJH + +#include + +#include +#include +#include + +using namespace std; + +// библиотечка, выполняющая запись в логи +// два метода - один мегафайл или много +// файлов по одному на каждый день +const int LIBLOGSINGLEFILE = 1; +const int LIBLOGDAYFILES = 2; +const int LIBLOGNUMRETRIESTOOPEN = 1; + + +class LogFile +{ +private: + // запрещаем копирование и присваивание лог-файла (чтобы FILE* не копипастился почем зря) + // Передаем всегда указатели на лог + LogFile(const LogFile &l); + void operator=(const LogFile &l); + bool initialized; +public: + // mutex для блокирования дескриптора файла (чтобы толпой в один дескриптор не писать) + pthread_mutex_t filemutex; + // файл для записи + FILE *file; + // и его имя + string lastname; + int logmethod; // метод записи (смтор константы) + string logpath; // куда писать + string fileprefix; // префикс, который перед файлами генерировать + bool reopen; // нужно ли каждый раз переоткрывать лог-файл + LogFile(); + void InitLogging(string logpath, int method, string prefix = "", bool reopen = false); + void LogString(string s, string pfx = ""); + FILE *OpenFileWithRetries(string fname, const char *mode); +}; + +string GetHex(unsigned char c); +string GetHexString(unsigned int a); + + +#endif