Skeleton of application, at least compiles

This commit is contained in:
ftc
2020-11-24 22:48:54 +03:00
parent 988adcec4a
commit bd5a629fde
24 changed files with 1788 additions and 3 deletions
+2
View File
@@ -0,0 +1,2 @@
build/
defrost.cfg
+100
View File
@@ -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);
}
}
}
+21
View File
@@ -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
+33
View File
@@ -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)
+15
View File
@@ -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);
}
+17
View File
@@ -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
+100
View File
@@ -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();
}
+24
View File
@@ -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
+16 -2
View File
@@ -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градусах
Новая версия размораживалки (для работы на обычном ПК)
+161
View File
@@ -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);
}
+29
View File
@@ -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
View File
@@ -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();
}
+27
View File
@@ -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
+463
View File
@@ -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();
}
+85
View File
@@ -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
+2
View File
@@ -0,0 +1,2 @@
ListenPort = 15000
LogPath = "/var/log/defrost.log"
+88
View File
@@ -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(&register_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;
}
+13
View File
@@ -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
+6
View File
@@ -0,0 +1,6 @@
#include "globals.h"
int registers[65536];
pthread_mutex_t register_mutex;
LogFile mainlog;
+17
View File
@@ -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
View File
@@ -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;
}
+64
View File
@@ -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
View File
@@ -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);
}
+49
View File
@@ -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