Skeleton of application, at least compiles
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
build/
|
||||
defrost.cfg
|
||||
@@ -0,0 +1,100 @@
|
||||
#include "BaseListeningThread.h"
|
||||
|
||||
#include <typeinfo>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/file.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,100 @@
|
||||
#include "ModbusServiceWorker.h"
|
||||
#include "libdiff.h"
|
||||
#include "globals.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -1,3 +1,17 @@
|
||||
# defrost_v2
|
||||
|
||||
Новая версия размораживалки (для работы на обычном ПК)
|
||||
Простенькое управляемое реле.
|
||||
|
||||
При запуске поднимает 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градусах
|
||||
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
#include "SynchronousSocket.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
#include <cstring>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
#ifndef SYNCHRONOUS_SOCKET_HEADER_FILE_INCLUDED
|
||||
#define SYNCHRONOUS_SOCKET_HEADER_FILE_INCLUDED
|
||||
|
||||
#include <string>
|
||||
|
||||
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
|
||||
+48
@@ -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();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#ifndef CORE_THREAD_HEADER_FILE_INCLUDED
|
||||
#define CORE_THREAD_HEADER_FILE_INCLUDED
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
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
|
||||
@@ -0,0 +1,463 @@
|
||||
#include "configparser.h"
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
|
||||
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<ConfigParam>::iterator t = p.val[0].pval.begin(); t != p.val[0].pval.end(); t++)
|
||||
{
|
||||
vl.pval.insert(*t);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
set<ConfigParam>::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<ConfigParam>::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<ConfigParam>::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<ConfigParam>::iterator p = this->pval.find((ConfigParam)(pname));
|
||||
if (p == pval.end()) return "";
|
||||
return p->GetSingleVal();
|
||||
}
|
||||
|
||||
@@ -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 <string>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
|
||||
using namespace std;
|
||||
|
||||
const char CONFIGVALUETYPESTRING = 0;
|
||||
const char CONFIGVALUETYPESET = 1;
|
||||
|
||||
class ConfigValue;
|
||||
|
||||
// Класс описывающий параметр из конфигурационного файла
|
||||
class ConfigParam
|
||||
{
|
||||
public:
|
||||
string name;
|
||||
vector<ConfigValue> 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<ConfigParam> 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
|
||||
@@ -0,0 +1,2 @@
|
||||
ListenPort = 15000
|
||||
LogPath = "/var/log/defrost.log"
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
#include "configparser.h"
|
||||
#include "globals.h"
|
||||
#include "ModbusServiceListenThread.h"
|
||||
#include "libdiff.h"
|
||||
|
||||
#include <climits>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <unistd.h>
|
||||
#include <sys/file.h>
|
||||
#include <errno.h>
|
||||
#include <cmath>
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,6 @@
|
||||
#include "globals.h"
|
||||
|
||||
int registers[65536];
|
||||
pthread_mutex_t register_mutex;
|
||||
LogFile mainlog;
|
||||
|
||||
@@ -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
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
#include "libdiff.h"
|
||||
|
||||
#include <sys/timeb.h>
|
||||
#include <errno.h>
|
||||
#include <iconv.h>
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
#include <sys/resource.h>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
#ifndef FELIX_THE_CAT_DIFFERENT_LIBRARY_INCLUDED_AKJGNKJABVKALJGLKAJGLAKJG
|
||||
#define FELIX_THE_CAT_DIFFERENT_LIBRARY_INCLUDED_AKJGNKJABVKALJGLKAJGLAKJG
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
template <class T>
|
||||
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
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
#include "liblog.h"
|
||||
#include "libdiff.h"
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <cstdlib>
|
||||
#include <unistd.h>
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -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 <pthread.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user