From bd5a629fde6299051d6ae7f2276e4d648835eb99 Mon Sep 17 00:00:00 2001 From: Felix The Cat Date: Tue, 24 Nov 2020 22:48:54 +0300 Subject: [PATCH] Skeleton of application, at least compiles --- .gitignore | 2 + BaseListeningThread.cpp | 100 ++++++++ BaseListeningThread.h | 21 ++ CMakeLists.txt | 33 +++ ModbusServiceListenThread.cpp | 15 ++ ModbusServiceListenThread.h | 17 ++ ModbusServiceWorker.cpp | 100 ++++++++ ModbusServiceWorker.h | 24 ++ README.md | 20 +- SynchronousSocket.cpp | 161 ++++++++++++ SynchronousSocket.h | 29 +++ Thread.cpp | 48 ++++ Thread.h | 27 ++ configparser.cpp | 463 ++++++++++++++++++++++++++++++++++ configparser.h | 85 +++++++ defrost.cfg.sample | 2 + defrost.cpp | 88 +++++++ defrost.service | 13 + globals.cpp | 6 + globals.h | 17 ++ libdiff.cpp | 177 +++++++++++++ libdiff.h | 64 +++++ liblog.cpp | 230 +++++++++++++++++ liblog.h | 49 ++++ 24 files changed, 1788 insertions(+), 3 deletions(-) create mode 100644 .gitignore create mode 100644 BaseListeningThread.cpp create mode 100644 BaseListeningThread.h create mode 100644 CMakeLists.txt create mode 100644 ModbusServiceListenThread.cpp create mode 100644 ModbusServiceListenThread.h create mode 100644 ModbusServiceWorker.cpp create mode 100644 ModbusServiceWorker.h create mode 100644 SynchronousSocket.cpp create mode 100644 SynchronousSocket.h create mode 100644 Thread.cpp create mode 100644 Thread.h create mode 100644 configparser.cpp create mode 100644 configparser.h create mode 100644 defrost.cfg.sample create mode 100644 defrost.cpp create mode 100644 defrost.service create mode 100644 globals.cpp create mode 100644 globals.h create mode 100644 libdiff.cpp create mode 100644 libdiff.h create mode 100644 liblog.cpp create mode 100644 liblog.h 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