diff --git a/.cproject b/.cproject
index c31fccb..547191c 100644
--- a/.cproject
+++ b/.cproject
@@ -34,8 +34,9 @@
-
@@ -89,7 +90,7 @@
-
@@ -125,4 +126,5 @@
+
diff --git a/.settings/language.settings.xml b/.settings/language.settings.xml
index c889b83..9c59f29 100644
--- a/.settings/language.settings.xml
+++ b/.settings/language.settings.xml
@@ -5,7 +5,7 @@
-
+
@@ -16,7 +16,7 @@
-
+
diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..99f26c0
--- /dev/null
+++ b/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,2 @@
+eclipse.preferences.version=1
+encoding/=UTF-8
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..6df47a6
--- /dev/null
+++ b/README.md
@@ -0,0 +1,11 @@
+Простенькое управляемое реле.
+
+При запуске поднимает modbus tcp сервер, с которым можно общаться и управлять (а также читать текущие показания).
+
+Список регистров:
+
+0 - Температура включения реле (если ниже, включится) (в 0.01 градуса)
+1 - Температура выключения реле (если выше, выключится) (в 0.01 градуса)
+2 - Режим работы (0 - авто, 1 - принудительно включено, 2 - принудительно выключено)
+3 - Текущая температура (в 0.01 градуса)
+4 - Текущее состояние реле (0 - выключено, 1 - включено)
\ No newline at end of file
diff --git a/defrost.cfg.sample b/defrost.cfg.sample
new file mode 100644
index 0000000..3a510c1
--- /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/src/BaseListeningThread.cpp b/src/BaseListeningThread.cpp
new file mode 100644
index 0000000..199f97a
--- /dev/null
+++ b/src/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/src/BaseListeningThread.h b/src/BaseListeningThread.h
new file mode 100644
index 0000000..dd870cd
--- /dev/null
+++ b/src/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/src/ModbusServiceListenThread.cpp b/src/ModbusServiceListenThread.cpp
new file mode 100644
index 0000000..2eca157
--- /dev/null
+++ b/src/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/src/ModbusServiceListenThread.h b/src/ModbusServiceListenThread.h
new file mode 100644
index 0000000..1482f41
--- /dev/null
+++ b/src/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/src/ModbusServiceWorker.cpp b/src/ModbusServiceWorker.cpp
new file mode 100644
index 0000000..d904606
--- /dev/null
+++ b/src/ModbusServiceWorker.cpp
@@ -0,0 +1,40 @@
+#include "ModbusServiceWorker.h"
+#include "libdiff.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)
+ {
+
+ }
+ }
+ 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();
+}
+
+ModbusServiceWorker::~ModbusServiceWorker()
+{
+ s.Disconnect();
+}
diff --git a/src/ModbusServiceWorker.h b/src/ModbusServiceWorker.h
new file mode 100644
index 0000000..c1d8dab
--- /dev/null
+++ b/src/ModbusServiceWorker.h
@@ -0,0 +1,22 @@
+#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;
+protected:
+ virtual void Run();
+public:
+ ModbusServiceWorker(int socket, LogFile *log);
+ virtual ~ModbusServiceWorker();
+};
+
+#endif
diff --git a/src/SynchronousSocket.cpp b/src/SynchronousSocket.cpp
new file mode 100644
index 0000000..c51653a
--- /dev/null
+++ b/src/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/src/SynchronousSocket.h b/src/SynchronousSocket.h
new file mode 100644
index 0000000..8fd126e
--- /dev/null
+++ b/src/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/src/Thread.cpp b/src/Thread.cpp
new file mode 100644
index 0000000..f9470cf
--- /dev/null
+++ b/src/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/src/Thread.h b/src/Thread.h
new file mode 100644
index 0000000..b10529a
--- /dev/null
+++ b/src/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/src/configparser.cpp b/src/configparser.cpp
new file mode 100644
index 0000000..1e00e1b
--- /dev/null
+++ b/src/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/src/configparser.h b/src/configparser.h
new file mode 100644
index 0000000..08f2d19
--- /dev/null
+++ b/src/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/src/defrost.cpp b/src/defrost.cpp
index f1b19f9..39a3169 100644
--- a/src/defrost.cpp
+++ b/src/defrost.cpp
@@ -1,28 +1,23 @@
-
+#include "configparser.h"
#include "grove.h"
#include "jhd1313m1.h"
#include "mraa/common.hpp"
#include "mraa/gpio.hpp"
+#include "globals.h"
+#include "ModbusServiceListenThread.h"
+#include "libdiff.h"
#include
#include
#include
#include
+#include
+#include
#define GPIO_PIN_1 2
void temperature_update(upm::GroveTemp* temperature_sensor, upm::Jhd1313m1 *lcd)
{
- // minimum and maximum temperatures registered, the initial values will be
- // replaced after the first read
- static int min_temperature = INT_MAX;
- static int max_temperature = INT_MIN;
-
- // the temperature range in degrees Celsius,
- // adapt to your room temperature for a nicer effect!
- const int TEMPERATURE_RANGE_MIN_VAL = 18;
- const int TEMPERATURE_RANGE_MAX_VAL = 31;
-
// other helper variables
int temperature; // temperature sensor value in degrees Celsius
float fade; // fade value [0.0 .. 1.0]
@@ -30,31 +25,29 @@ void temperature_update(upm::GroveTemp* temperature_sensor, upm::Jhd1313m1 *lcd)
std::stringstream row_1, row_2; // LCD rows
// update the min and max temperature values
- temperature = temperature_sensor->value();
-
- if (temperature < min_temperature) {
- min_temperature = temperature;
- }
- if (temperature > max_temperature) {
- max_temperature = temperature;
- }
+ temperature = temperature_sensor->value() * 100;
+ registers[REGISTER_CUR_TEMP] = temperature;
// display the temperature values on the LCD
- row_1 << "Temp " << temperature << " ";
- row_2 << "Min " << min_temperature << " Max " << max_temperature << " ";
+ row_1 << "T " << temperature / 100 << " Mode: ";
+ int mode = registers[REGISTER_MODE];
+ if (mode == 0) row_1 << "auto";
+ else if (mode == 1) row_1 << "on";
+ else if (mode == 2) row_1 << "off";
+ row_2 << "On: " << registers[REGISTER_ON_TEMP] / 100 << " Off " << registers[REGISTER_OFF_TEMP] / 100 << " ";
lcd->setCursor(0,0);
lcd->write(row_1.str());
lcd->setCursor(1,0);
lcd->write(row_2.str());
// set the fade value depending on where we are in the temperature range
- if (temperature <= TEMPERATURE_RANGE_MIN_VAL) {
+ if (temperature <= registers[REGISTER_ON_TEMP]) {
fade = 0.0;
- } else if (temperature >= TEMPERATURE_RANGE_MAX_VAL) {
+ } else if (temperature >= registers[REGISTER_OFF_TEMP]) {
fade = 1.0;
} else {
- fade = (float)(temperature - TEMPERATURE_RANGE_MIN_VAL) /
- (TEMPERATURE_RANGE_MAX_VAL - TEMPERATURE_RANGE_MIN_VAL);
+ fade = (float)(temperature - registers[REGISTER_ON_TEMP]) /
+ (registers[REGISTER_OFF_TEMP] - registers[REGISTER_ON_TEMP]);
}
// fade the color components separately
@@ -68,45 +61,84 @@ void temperature_update(upm::GroveTemp* temperature_sensor, upm::Jhd1313m1 *lcd)
int main()
{
- // check that we are running on Galileo or Edison
- mraa_platform_t platform = mraa_get_platform_type();
- if ((platform != MRAA_INTEL_GALILEO_GEN1) &&
- (platform != MRAA_INTEL_GALILEO_GEN2) &&
- (platform != MRAA_INTEL_EDISON_FAB_C))
- {
- std::cerr << "Unsupported platform, exiting" << std::endl;
- return MRAA_ERROR_INVALID_PLATFORM;
- }
+ try {
+ // ����� lock-file, ����� ���������� ������ ���� ��������� �������
+ int lockfile = open("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;
+ }
+ // check that we are running on Galileo or Edison
+ mraa_platform_t platform = mraa_get_platform_type();
+ if ((platform != MRAA_INTEL_GALILEO_GEN1) &&
+ (platform != MRAA_INTEL_GALILEO_GEN2) &&
+ (platform != MRAA_INTEL_EDISON_FAB_C))
+ {
+ std::cerr << "Unsupported platform, exiting" << std::endl;
+ return MRAA_ERROR_INVALID_PLATFORM;
+ }
- mraa::Gpio gpio(GPIO_PIN_1);
- mraa_result_t status = gpio.dir(mraa::DIR_OUT);
- if (status != MRAA_SUCCESS)
+ mraa::Gpio gpio(GPIO_PIN_1);
+ mraa_result_t status = gpio.dir(mraa::DIR_OUT);
+ if (status != MRAA_SUCCESS)
+ {
+ return EXIT_FAILURE;
+ }
+
+ // temperature sensor connected to A0 (analog in)
+ upm::GroveTemp* temp_sensor = new upm::GroveTemp(0);
+
+ // LCD connected to the default I2C bus
+ upm::Jhd1313m1* lcd = new upm::Jhd1313m1(0);
+
+ if ((temp_sensor == NULL) || (lcd == NULL))
+
+ {
+ std::cerr << "Can't create all objects, exiting" << std::endl;
+ return MRAA_ERROR_UNSPECIFIED;
+ }
+ // load config
+ ConfigParser cfg;
+ cfg.Load("defrost.cfg");
+ mainlog.InitLogging(cfg.GetParam("LogPath"), LIBLOGSINGLEFILE);
+ 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(temp_sensor, lcd);
+ 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;
+ gpio.write(io_value);
+ ReleaseMutex(register_mutex);
+ sleep(1);
+ }
+ }
+ catch (string s)
{
+ cerr << "Exception: " << s << "\n";
return EXIT_FAILURE;
}
-
- // temperature sensor connected to A0 (analog in)
- upm::GroveTemp* temp_sensor = new upm::GroveTemp(0);
-
- // LCD connected to the default I2C bus
- upm::Jhd1313m1* lcd = new upm::Jhd1313m1(0);
-
- if ((temp_sensor == NULL) || (lcd == NULL))
-
- {
- std::cerr << "Can't create all objects, exiting" << std::endl;
- return MRAA_ERROR_UNSPECIFIED;
- }
-
- int io_value = 1;
- // loop forever updating the temperature values every second
- for (;;) {
- temperature_update(temp_sensor, lcd);
- gpio.write(io_value);
- io_value = 1 - io_value;
- std::cerr << io_value << "\n";
- sleep(1);
- }
-
return MRAA_SUCCESS;
}
diff --git a/src/globals.cpp b/src/globals.cpp
new file mode 100644
index 0000000..3e48168
--- /dev/null
+++ b/src/globals.cpp
@@ -0,0 +1,6 @@
+#include "globals.h"
+
+int registers[65536];
+pthread_mutex_t register_mutex;
+LogFile mainlog;
+
diff --git a/src/globals.h b/src/globals.h
new file mode 100644
index 0000000..06d2fba
--- /dev/null
+++ b/src/globals.h
@@ -0,0 +1,16 @@
+#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;
+
+extern int registers[65536];
+extern pthread_mutex_t register_mutex;
+extern LogFile mainlog;
+
+#endif
diff --git a/src/libdiff.cpp b/src/libdiff.cpp
new file mode 100644
index 0000000..818b281
--- /dev/null
+++ b/src/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/src/libdiff.h b/src/libdiff.h
new file mode 100644
index 0000000..fba0b3f
--- /dev/null
+++ b/src/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/src/liblog.cpp b/src/liblog.cpp
new file mode 100644
index 0000000..4b69745
--- /dev/null
+++ b/src/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/src/liblog.h b/src/liblog.h
new file mode 100644
index 0000000..b39ca65
--- /dev/null
+++ b/src/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