reworked main loop, switched command line argument parsing to argp
This commit is contained in:
@@ -0,0 +1,510 @@
|
||||
/*
|
||||
* Copyright (C) John Baier 2014-2015 <ebusd@johnm.de>
|
||||
*
|
||||
* This file is part of ebusd.
|
||||
*
|
||||
* ebusd is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* ebusd is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with ebusd. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include "main.h"
|
||||
#include "mainloop.h"
|
||||
#include "bushandler.h"
|
||||
#include "log.h"
|
||||
#include <stdlib.h>
|
||||
#include <argp.h>
|
||||
#include <csignal>
|
||||
#include <iostream>
|
||||
#include <dirent.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
/** the name of the PID file. */
|
||||
#define PID_FILE_NAME "/var/run/ebusd.pid"
|
||||
|
||||
/** the opened PID file, or NULL. */
|
||||
static FILE* pidFile = NULL;
|
||||
|
||||
/** true when forked into daemon mode. */
|
||||
static bool isDaemon = false;
|
||||
|
||||
/** the program options. */
|
||||
static struct options opt = {
|
||||
"/dev/ttyUSB0", // device
|
||||
false, // noDeviceCheck
|
||||
"/etc/ebusd", // configPath
|
||||
false, // checkConfig
|
||||
5, // pollInterval
|
||||
0xFF, // address
|
||||
false, // answer
|
||||
9400, // acquireTimeout
|
||||
2, // acquireRetries
|
||||
2, // sendRetries
|
||||
15000, // receiveTimeout
|
||||
5, // numberMasters
|
||||
false, // foreground
|
||||
8888, // port
|
||||
false, // localhost
|
||||
"/var/log/ebusd.log", // logFile
|
||||
false, // logRaw
|
||||
false, // dump
|
||||
"/tmp/ebus_dump.bin", // dumpFile
|
||||
100 // dumpSize
|
||||
};
|
||||
|
||||
/** the @a MainLoop instance, or NULL. */
|
||||
static MainLoop* mainLoop = NULL;
|
||||
|
||||
/** the version string of the program. */
|
||||
const char *argp_program_version = ""PACKAGE_STRING"";
|
||||
|
||||
/** the report bugs to address of the program. */
|
||||
const char *argp_program_bug_address = ""PACKAGE_BUGREPORT"";
|
||||
|
||||
/** the documentation of the program. */
|
||||
static const char argpdoc[] =
|
||||
PACKAGE " - a daemon for access to eBUS devices.";
|
||||
|
||||
/** the definition of the known program arguments. */
|
||||
static const struct argp_option argpoptions[] = {
|
||||
{NULL, 0, NULL, 0, "Device settings:", 1 },
|
||||
{"device", 'd', "DEV", 0, "Use DEV as eBUS device (serial device or ip:port) [/dev/ttyUSB0]", 0 },
|
||||
{"nodevicecheck", 'n', NULL, 0, "Skip serial eBUS device test", 0 },
|
||||
|
||||
{NULL, 0, NULL, 0, "Message configuration settings:", 2 },
|
||||
{"configpath", 'c', "PATH", 0, "Read CSV config files from PATH [/etc/ebusd]", 0 },
|
||||
{"checkconfig", 1, NULL, 0, "Only check CSV config files, then stop", 0 },
|
||||
{"pollinterval", 2, "SEC", 0, "Poll for data every SEC seconds (0=disable) [5]", 0 },
|
||||
|
||||
{NULL, 0, NULL, 0, "E-Bus settings:", 3 },
|
||||
{"address", 'a', "ADDR", 0, "Use ADDR as own bus address [FF]", 0 },
|
||||
{"answer", 3, NULL, 0, "Actively answer to requests from other masters", 0 },
|
||||
{"acquiretimeout", 4, "USEC", 0, "Stop bus acquisition after USEC us [9400]", 0 },
|
||||
{"acquireretries", 5, "COUNT", 0, "Retry bus acquisition COUNT times [2]", 0 },
|
||||
{"sendretries", 6, "COUNT", 0, "Repeat failed sends COUNT times [2]", 0 },
|
||||
{"receivetimeout", 7, "USEC", 0, "Expect a slave to answer within USEC us [15000]", 0 },
|
||||
{"numbermasters", 8, "COUNT", 0, "Expect COUNT masters on the bus [5]", 0 },
|
||||
|
||||
{NULL, 0, NULL, 0, "Daemon settings:", 4 },
|
||||
{"foreground", 'f', NULL, 0, "Run in foreground", 0 },
|
||||
{"port", 'p', "PORT", 0, "Listen for client connections on PORT [8888]", 0 },
|
||||
{"localhost", 9, NULL, 0, "Listen on 127.0.0.1 interface only", 0 },
|
||||
|
||||
{NULL, 0, NULL, 0, "Log settings:", 5 },
|
||||
{"logfile", 'l', "FILE", 0, "Write log to FILE (only for daemon) [/var/log/ebusd.log]", 0 },
|
||||
{"logareas", 10, "AREAS", 0, "Only write log for matching AREAS: main,network,bus,update,all [all]", 0 },
|
||||
{"loglevel", 11, "LEVEL", 0, "Only write log below or equal to LEVEL: error/notice/info/debug [notice]", 0 },
|
||||
{"lograwdata", 12, NULL, 0, "Log each received/sent byte on the bus", 0 },
|
||||
|
||||
{NULL, 0, NULL, 0, "Dump settings:", 6 },
|
||||
{"dump", 'D', NULL, 0, "Enable dump of received bytes", 0 },
|
||||
{"dumpfile", 13, "FILE", 0, "Dump received bytes to FILE [/tmp/ebus_dump.bin]", 0 },
|
||||
{"dumpsize", 14, "SIZE", 0, "Make dump files no larger than SIZE kB [100]", 0 },
|
||||
|
||||
{NULL, 0, NULL, 0, NULL, 0 },
|
||||
};
|
||||
|
||||
/**
|
||||
* The program argument parsing function.
|
||||
* @param key the key from @a options.
|
||||
* @param arg the option argument, or NULL.
|
||||
* @param state the parsing state.
|
||||
*/
|
||||
error_t parse_opt(int key, char *arg, struct argp_state *state)
|
||||
{
|
||||
struct options *opt = (struct options*)state->input;
|
||||
result_t result = RESULT_OK;
|
||||
switch (key) {
|
||||
|
||||
// Device settings:
|
||||
case 'd': // --device=/dev/ttyUSB0
|
||||
if (arg == NULL || arg[0] == 0) {
|
||||
argp_error(state, "invalid device");
|
||||
return EINVAL;
|
||||
}
|
||||
opt->device = arg;
|
||||
break;
|
||||
case 'n': // --nodevicecheck
|
||||
opt->noDeviceCheck = true;
|
||||
break;
|
||||
|
||||
// Message configuration settings:
|
||||
case 'c': // --configpath=/etc/ebusd
|
||||
if (arg == NULL || arg[0] == 0 || strcmp("/", arg) == 0) {
|
||||
argp_error(state, "invalid configpath");
|
||||
return EINVAL;
|
||||
}
|
||||
opt->configPath = arg;
|
||||
break;
|
||||
case 1: // --checkconfig
|
||||
opt->checkConfig = true;
|
||||
break;
|
||||
case 2: // --pollinterval=5
|
||||
opt->pollInterval = parseInt(arg, 10, 0, 3600, result);
|
||||
if (result != RESULT_OK) {
|
||||
argp_error(state, "invalid pollinterval");
|
||||
return EINVAL;
|
||||
}
|
||||
break;
|
||||
|
||||
// E-Bus settings:
|
||||
case 'a': // --address=FF
|
||||
opt->address = parseInt(arg, 16, 0, 0xff, result);
|
||||
if (result != RESULT_OK || !isMaster(opt->address)) {
|
||||
argp_error(state, "invalid address");
|
||||
return EINVAL;
|
||||
}
|
||||
break;
|
||||
case 3: // --answer
|
||||
opt->answer = true;
|
||||
break;
|
||||
case 4: // --acquiretimeout=9400
|
||||
opt->acquireTimeout = parseInt(arg, 10, 1000, 100000, result);
|
||||
if (result != RESULT_OK) {
|
||||
argp_error(state, "invalid acquiretimeout");
|
||||
return EINVAL;
|
||||
}
|
||||
break;
|
||||
case 5: // --acquireretries=2
|
||||
opt->acquireRetries = parseInt(arg, 10, 0, 10, result);
|
||||
if (result != RESULT_OK) {
|
||||
argp_error(state, "invalid acquireretries");
|
||||
return EINVAL;
|
||||
}
|
||||
break;
|
||||
case 6: // --sendretries=2
|
||||
opt->sendRetries = parseInt(arg, 10, 0, 10, result);
|
||||
if (result != RESULT_OK) {
|
||||
argp_error(state, "invalid sendretries");
|
||||
return EINVAL;
|
||||
}
|
||||
break;
|
||||
case 7: // --receivetimeout=15000
|
||||
opt->receiveTimeout = parseInt(arg, 10, 1000, 100000, result);
|
||||
if (result != RESULT_OK) {
|
||||
argp_error(state, "invalid receivetimeout");
|
||||
return EINVAL;
|
||||
}
|
||||
break;
|
||||
case 8: // --numbermasters=5
|
||||
opt->numberMasters = parseInt(arg, 10, 1, 10, result);
|
||||
if (result != RESULT_OK) {
|
||||
argp_error(state, "invalid numbermasters");
|
||||
return EINVAL;
|
||||
}
|
||||
break;
|
||||
|
||||
// Daemon settings:
|
||||
case 'f': // --foreground
|
||||
opt->foreground = true;
|
||||
break;
|
||||
case 'p': // --port=8888
|
||||
opt->port = parseInt(arg, 10, 1, 65535, result);
|
||||
if (result != RESULT_OK) {
|
||||
argp_error(state, "invalid port");
|
||||
return EINVAL;
|
||||
}
|
||||
break;
|
||||
case 9: // --localhost
|
||||
opt->localhost = true;
|
||||
break;
|
||||
|
||||
// Log settings:
|
||||
case 'l': // --logfile=/var/log/ebusd.log
|
||||
if (arg == NULL || arg[0] == 0 || strcmp("/", arg) == 0) {
|
||||
argp_error(state, "invalid logfile");
|
||||
return EINVAL;
|
||||
}
|
||||
opt->logFile = arg;
|
||||
break;
|
||||
case 10: // --logareas=all
|
||||
if (!setLogFacilities(arg)) {
|
||||
argp_error(state, "invalid logareas");
|
||||
return EINVAL;
|
||||
}
|
||||
break;
|
||||
case 11: // --loglevel=event
|
||||
if (!setLogLevel(arg)) {
|
||||
argp_error(state, "invalid loglevel");
|
||||
return EINVAL;
|
||||
}
|
||||
break;
|
||||
case 12: // --lograwdata
|
||||
opt->logRaw = true;
|
||||
break;
|
||||
|
||||
// Dump settings:
|
||||
case 'D': // --dump
|
||||
opt->dump = true;
|
||||
break;
|
||||
case 13: // --dumpfile=/tmp/ebus_dump.bin
|
||||
if (arg == NULL || arg[0] == 0 || strcmp("/", arg) == 0) {
|
||||
argp_error(state, "invalid dumpfile");
|
||||
return EINVAL;
|
||||
}
|
||||
opt->dumpFile = arg;
|
||||
break;
|
||||
case 14: // --dumpsize=100
|
||||
opt->dumpSize = parseInt(arg, 10, 1, 1000000, result);
|
||||
if (result != RESULT_OK) {
|
||||
argp_error(state, "invalid dumpsize");
|
||||
return EINVAL;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return ARGP_ERR_UNKNOWN;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void daemonize()
|
||||
{
|
||||
// fork off the parent process
|
||||
pid_t pid = fork();
|
||||
|
||||
if (pid < 0) {
|
||||
logError(lf_main, "fork() failed");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// If we got a good PID, then we can exit the parent process
|
||||
if (pid > 0)
|
||||
exit(EXIT_SUCCESS);
|
||||
|
||||
// At this point we are executing as the child process
|
||||
|
||||
// Create a new SID for the child process and
|
||||
// detach the process from the parent (normally a shell)
|
||||
if (setsid() < 0) {
|
||||
logError(lf_main, "setsid() failed");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Change the current working directory. This prevents the current
|
||||
// directory from being locked; hence not being able to remove it.
|
||||
if (chdir("/tmp") < 0) { // TODO
|
||||
logError(lf_main, "daemon chdir() failed");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Close stdin, stdout and stderr
|
||||
close(STDIN_FILENO);
|
||||
close(STDOUT_FILENO);
|
||||
close(STDERR_FILENO);
|
||||
|
||||
// create pid file and try to lock it
|
||||
umask(077); // leads to pidFile created in mode 0600
|
||||
pidFile = fopen(PID_FILE_NAME, "w");
|
||||
umask(027); // Set file permissions 750
|
||||
|
||||
if (pidFile != NULL) {
|
||||
if (lockf(fileno(pidFile), F_TLOCK, 0) < 0
|
||||
|| fprintf(pidFile, "%d\n", getpid()) <=0) {
|
||||
fclose(pidFile);
|
||||
pidFile = NULL;
|
||||
}
|
||||
}
|
||||
if (pidFile == NULL) {
|
||||
logError(lf_main, "can't open pidfile: " PID_FILE_NAME);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
isDaemon = true;
|
||||
}
|
||||
|
||||
void closePidFile()
|
||||
{
|
||||
if (pidFile != NULL) {
|
||||
if (fclose(pidFile) != 0)
|
||||
return;
|
||||
|
||||
remove(PID_FILE_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method performing shutdown.
|
||||
*/
|
||||
void shutdown()
|
||||
{
|
||||
// stop main loop and all dependent components
|
||||
if (mainLoop != NULL) {
|
||||
delete mainLoop;
|
||||
mainLoop = NULL;
|
||||
}
|
||||
|
||||
// reset all signal handlers to default
|
||||
signal(SIGHUP, SIG_DFL);
|
||||
signal(SIGINT, SIG_DFL);
|
||||
signal(SIGTERM, SIG_DFL);
|
||||
|
||||
// delete daemon pid file if necessary
|
||||
closePidFile();
|
||||
|
||||
logNotice(lf_main, "ebusd stopped");
|
||||
closeLogFile();
|
||||
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
/**
|
||||
* The signal handling function.
|
||||
* @param sig the received signal.
|
||||
*/
|
||||
void signalHandler(int sig)
|
||||
{
|
||||
switch (sig) {
|
||||
case SIGHUP:
|
||||
logNotice(lf_main, "SIGHUP received");
|
||||
break;
|
||||
case SIGINT:
|
||||
logNotice(lf_main, "SIGINT received");
|
||||
shutdown();
|
||||
break;
|
||||
case SIGTERM:
|
||||
logNotice(lf_main, "SIGTERM received");
|
||||
shutdown();
|
||||
break;
|
||||
default:
|
||||
logNotice(lf_main, "undefined signal %s", strsignal(sig));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the configuration files from the specified path.
|
||||
* @param path the path from which to read the files.
|
||||
* @param extension the filename extension of the files to read.
|
||||
* @param logFunc the function to call for logging, or @a NULL to be silent.
|
||||
* @param templates the available @a DataFieldTemplates.
|
||||
* @param messages the @a MessageMap to load the messages into.
|
||||
* @param verbose whether to verbosely log problems.
|
||||
* @return the result code.
|
||||
*/
|
||||
static result_t readConfigFiles(const string path, const string extension, DataFieldTemplates* templates, MessageMap* messages, bool verbose)
|
||||
{
|
||||
DIR* dir = opendir(path.c_str());
|
||||
|
||||
if (dir == NULL)
|
||||
return RESULT_ERR_NOTFOUND;
|
||||
|
||||
dirent* d = readdir(dir);
|
||||
|
||||
while (d != NULL) {
|
||||
if (d->d_type == DT_DIR) {
|
||||
string fn = d->d_name;
|
||||
|
||||
if (fn != "." && fn != "..") {
|
||||
const string p = path + "/" + d->d_name;
|
||||
result_t result = readConfigFiles(p, extension, templates, messages, verbose);
|
||||
if (result != RESULT_OK)
|
||||
return result;
|
||||
}
|
||||
}
|
||||
else if (d->d_type == DT_REG || d->d_type == DT_LNK) {
|
||||
string fn = d->d_name;
|
||||
|
||||
if (fn.find(extension, (fn.length() - extension.length())) != string::npos
|
||||
&& fn != "_templates" + extension) {
|
||||
const string p = path + "/" + d->d_name;
|
||||
result_t result = messages->readFromFile(p, templates, verbose);
|
||||
if (result != RESULT_OK)
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
d = readdir(dir);
|
||||
}
|
||||
closedir(dir);
|
||||
|
||||
return RESULT_OK;
|
||||
};
|
||||
|
||||
result_t loadConfigFiles(DataFieldTemplates* templates, MessageMap* messages, bool verbose) {
|
||||
logInfo(lf_main, "path to ebus configuration files: %s", opt.configPath);
|
||||
string path = string(opt.configPath);
|
||||
messages->clear();
|
||||
templates->clear();
|
||||
result_t result = templates->readFromFile(path+"/_templates.csv", NULL, verbose);
|
||||
if (result == RESULT_OK)
|
||||
logInfo(lf_main, "read templates");
|
||||
else
|
||||
logError(lf_main, "error reading templates: %s", getResultCode(result));
|
||||
|
||||
result = readConfigFiles(path, ".csv", templates, messages, verbose);
|
||||
if (result == RESULT_OK)
|
||||
logInfo(lf_main, "read config files");
|
||||
else
|
||||
logError(lf_main, "error reading config files: %s", getResultCode(result));
|
||||
|
||||
logNotice(lf_main, "message DB: %d ", messages->size());
|
||||
logNotice(lf_main, "updates DB: %d ", messages->size(true));
|
||||
logNotice(lf_main, "polling DB: %d ", messages->sizePoll());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Main method.
|
||||
*
|
||||
* @param argc the number of command line arguments.
|
||||
* @param argv the command line arguments.
|
||||
*/
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
struct argp argp = { argpoptions, parse_opt, NULL, argpdoc, NULL, NULL, NULL };
|
||||
if (argp_parse(&argp, argc, argv, 0, 0, &opt) != 0)
|
||||
return EINVAL;
|
||||
|
||||
|
||||
DataFieldTemplates templates;
|
||||
MessageMap messages;
|
||||
if (opt.checkConfig == true) {
|
||||
logNotice(lf_main, "Performing configuration check...");
|
||||
|
||||
loadConfigFiles(&templates, &messages, true);
|
||||
|
||||
messages.clear();
|
||||
templates.clear();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (opt.foreground == false) {
|
||||
setLogFile(opt.logFile);
|
||||
daemonize(); // make me daemon
|
||||
}
|
||||
|
||||
// trap signals that we expect to receive
|
||||
signal(SIGHUP, signalHandler);
|
||||
signal(SIGINT, signalHandler);
|
||||
signal(SIGTERM, signalHandler);
|
||||
|
||||
logNotice(lf_main, "ebusd started");
|
||||
|
||||
// load configuration files
|
||||
loadConfigFiles(&templates, &messages);
|
||||
|
||||
// create the MainLoop and run it
|
||||
mainLoop = new MainLoop(opt, &templates, &messages);
|
||||
mainLoop->run();
|
||||
|
||||
// shutdown
|
||||
shutdown();
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (C) John Baier 2014-2015 <ebusd@johnm.de>
|
||||
*
|
||||
* This file is part of ebusd.
|
||||
*
|
||||
* ebusd is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* ebusd is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with ebusd. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
#ifndef MAIN_H_
|
||||
#define MAIN_H_
|
||||
|
||||
#include "result.h"
|
||||
#include "data.h"
|
||||
#include "message.h"
|
||||
|
||||
/** \file main.h */
|
||||
|
||||
/** A structure holding all program options. */
|
||||
struct options
|
||||
{
|
||||
const char* device; //!< eBUS device (serial device or ip:port) [/dev/ttyUSB0]
|
||||
bool noDeviceCheck; //!< skip serial eBUS device test
|
||||
|
||||
const char* configPath; //!< path to CSV configuration files [/etc/ebusd]
|
||||
bool checkConfig; //!< only check CSV config files, then stop
|
||||
int pollInterval; //!< poll interval in seconds, 0 to disable [5]
|
||||
|
||||
unsigned char address; //!< own bus address [FF]
|
||||
bool answer; //!< answer to requests from other masters
|
||||
int acquireTimeout; //!< bus acquisition timeout in us [9400]
|
||||
int acquireRetries; //!< number of retries for bus acquisition [2]
|
||||
int sendRetries; //!< number of retries for failed sends [2]
|
||||
int receiveTimeout; //!< timeout for receiving answer from slave in us [15000]
|
||||
int numberMasters; //!< expected number of masters for arbitration [5]
|
||||
|
||||
bool foreground; //!< run in foreground
|
||||
int port; //!< port to listen for client connections [8888]
|
||||
bool localhost; //!< listen on 127.0.0.1 interface only
|
||||
|
||||
const char* logFile; //!< log file name [/var/log/ebusd.log]
|
||||
bool logRaw; //!< log each received/sent byte on the bus
|
||||
|
||||
bool dump; //!< dump received bytes
|
||||
const char* dumpFile; //!< dump file name [/tmp/ebus_dump.bin]
|
||||
int dumpSize; //!< maximum size of dump file in kB [100]
|
||||
};
|
||||
|
||||
/**
|
||||
* Load the message definitions from the configuration files.
|
||||
* @param templates the @a DataFieldTemplates to load the templates into.
|
||||
* @param messages the @a MessageMap to load the messages into.
|
||||
* @param verbose whether to verbosely log problems.
|
||||
* @return the result code.
|
||||
*/
|
||||
result_t loadConfigFiles(DataFieldTemplates* templates, MessageMap* messages, bool verbose=false);
|
||||
|
||||
#endif // MAIN_H_
|
||||
@@ -0,0 +1,597 @@
|
||||
/*
|
||||
* Copyright (C) John Baier 2014-2015 <ebusd@johnm.de>
|
||||
*
|
||||
* This file is part of ebusd.
|
||||
*
|
||||
* ebusd is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* ebusd is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with ebusd. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
#include "mainloop.h"
|
||||
#include <iomanip>
|
||||
#include <algorithm>
|
||||
#include "main.h"
|
||||
#include "log.h"
|
||||
#include "data.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
MainLoop::MainLoop(const struct options opt, DataFieldTemplates* templates, MessageMap* messages)
|
||||
: m_templates(templates), m_messages(messages), m_address(opt.address)
|
||||
{
|
||||
// create Port
|
||||
m_port = new Port(opt.device, opt.noDeviceCheck, opt.logRaw, &logRaw, opt.dump, opt.dumpFile, opt.dumpSize);
|
||||
m_port->open();
|
||||
|
||||
if (m_port->isOpen() == false)
|
||||
logError(lf_bus, "can't open %s", m_port->getDeviceName());
|
||||
|
||||
// create BusHandler
|
||||
m_busHandler = new BusHandler(m_port, m_messages,
|
||||
m_address, opt.answer,
|
||||
opt.acquireRetries, opt.sendRetries,
|
||||
opt.acquireTimeout, opt.receiveTimeout,
|
||||
opt.numberMasters, opt.pollInterval);
|
||||
m_busHandler->start("bushandler");
|
||||
|
||||
// create network
|
||||
m_network = new Network(opt.localhost, opt.port, &m_netQueue);
|
||||
m_network->start("network");
|
||||
}
|
||||
|
||||
MainLoop::~MainLoop()
|
||||
{
|
||||
if (m_network != NULL) {
|
||||
delete m_network;
|
||||
m_network = NULL;
|
||||
}
|
||||
|
||||
if (m_busHandler != NULL) {
|
||||
m_busHandler->stop();
|
||||
m_busHandler->join();
|
||||
delete m_busHandler;
|
||||
m_busHandler = NULL;
|
||||
}
|
||||
|
||||
if (m_port != NULL) {
|
||||
delete m_port;
|
||||
m_port = NULL;
|
||||
}
|
||||
|
||||
m_messages->clear();
|
||||
m_templates->clear();
|
||||
}
|
||||
|
||||
void MainLoop::run()
|
||||
{
|
||||
for (;;) {
|
||||
string result;
|
||||
|
||||
// recv new message from client
|
||||
NetMessage* message = m_netQueue.remove();
|
||||
string data = message->getData();
|
||||
|
||||
time_t since, until;
|
||||
time(&until);
|
||||
bool listening = message->isListening(since);
|
||||
if (listening == false)
|
||||
since = until;
|
||||
|
||||
if (data.length() > 0) {
|
||||
data.erase(remove(data.begin(), data.end(), '\r'), data.end());
|
||||
data.erase(remove(data.begin(), data.end(), '\n'), data.end());
|
||||
|
||||
logNotice(lf_main, ">>> %s", data.c_str());
|
||||
|
||||
// decode message
|
||||
if (strcasecmp(data.c_str(), "STOP") != 0)
|
||||
result = decodeMessage(data, listening);
|
||||
else
|
||||
result = "done";
|
||||
|
||||
logNotice(lf_main, "<<< %s", result.c_str());
|
||||
result += "\n\n";
|
||||
}
|
||||
if (listening == true) {
|
||||
result += getUpdates(since, until);
|
||||
}
|
||||
|
||||
// send result to client
|
||||
message->setResult(result, listening, until);
|
||||
|
||||
// stop daemon
|
||||
if (strcasecmp(data.c_str(), "STOP") == 0)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void MainLoop::logRaw(const unsigned char byte, bool received) {
|
||||
if (received == true)
|
||||
logNotice(lf_bus, "<%02x", byte);
|
||||
else
|
||||
logNotice(lf_bus, ">%02x", byte);
|
||||
}
|
||||
|
||||
string MainLoop::decodeMessage(const string& data, bool& listening)
|
||||
{
|
||||
ostringstream result;
|
||||
|
||||
// prepare data
|
||||
string token, previous;
|
||||
istringstream stream(data);
|
||||
vector<string> args;
|
||||
bool escaped = false;
|
||||
|
||||
while (getline(stream, token, ' ') != 0) {
|
||||
if (escaped == true) {
|
||||
args.pop_back();
|
||||
if (token.length() > 0 && token[token.length()-1] == '"') {
|
||||
token = token.substr(0, token.length() - 1);
|
||||
escaped = false;
|
||||
}
|
||||
token = previous + " " + token;
|
||||
}
|
||||
else if (token.length() == 0) // allow multiple space chars for a single delimiter
|
||||
continue;
|
||||
else if (token[0] == '"') {
|
||||
token = token.substr(1);
|
||||
if (token.length() > 0 && token[token.length()-1] == '"')
|
||||
token = token.substr(0, token.length() - 1);
|
||||
else
|
||||
escaped = true;
|
||||
}
|
||||
args.push_back(token);
|
||||
previous = token;
|
||||
}
|
||||
|
||||
if (args.size() == 0)
|
||||
return "command missing";
|
||||
|
||||
|
||||
const char* str = args[0].c_str();
|
||||
if (strcasecmp(str, "R") == 0 || strcasecmp(str, "READ") == 0)
|
||||
return executeRead(args);
|
||||
if (strcasecmp(str, "W") == 0 || strcasecmp(str, "WRITE") == 0)
|
||||
return executeWrite(args);
|
||||
if (strcasecmp(str, "F") == 0 || strcasecmp(str, "FIND") == 0)
|
||||
return executeFind(args);
|
||||
if (strcasecmp(str, "L") == 0 || strcasecmp(str, "LISTEN") == 0)
|
||||
return executeListen(args, listening);
|
||||
if (strcasecmp(str, "SCAN") == 0)
|
||||
return executeScan(args);
|
||||
if (strcasecmp(str, "LOG") == 0)
|
||||
return executeLog(args);
|
||||
if (strcasecmp(str, "RAW") == 0)
|
||||
return executeRaw(args);
|
||||
if (strcasecmp(str, "DUMP") == 0)
|
||||
return executeDump(args);
|
||||
if (strcasecmp(str, "RELOAD") == 0)
|
||||
return executeReload(args);
|
||||
if (strcasecmp(str, "H") == 0 || strcasecmp(str, "HELP") == 0)
|
||||
return executeHelp();
|
||||
|
||||
return "command not found";
|
||||
}
|
||||
|
||||
string MainLoop::executeRead(vector<string> &args)
|
||||
{
|
||||
size_t argPos = 1;
|
||||
time_t maxAge = 5*60;
|
||||
bool verbose = false;
|
||||
string clazz;
|
||||
while (args.size() > argPos && args[argPos][0] == '-') {
|
||||
if (args[argPos] == "-f") {
|
||||
maxAge = 0;
|
||||
}
|
||||
else if (args[argPos] == "-v") {
|
||||
verbose = true;
|
||||
}
|
||||
else if (args[argPos] == "-m") {
|
||||
argPos++;
|
||||
if (args.size() > argPos) {
|
||||
result_t result;
|
||||
maxAge = parseInt(args[argPos].c_str(), 10, 0, 24*60*60, result);
|
||||
if (result != RESULT_OK) {
|
||||
argPos = 0; // print usage
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
argPos = 0; // print usage
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (args[argPos] == "-c") {
|
||||
argPos++;
|
||||
if (argPos >= args.size()) {
|
||||
argPos = 0; // print usage
|
||||
break;
|
||||
}
|
||||
clazz = args[argPos];
|
||||
}
|
||||
else {
|
||||
argPos = 0; // print usage
|
||||
break;
|
||||
}
|
||||
argPos++;
|
||||
}
|
||||
if (argPos == 0 || args.size() < argPos + 1 || args.size() > argPos + 2)
|
||||
return "usage: 'read [-v] [-f] [-m seconds] [-c class] name [field]'";
|
||||
|
||||
if (args.size() == argPos + 2)
|
||||
maxAge = 0; // force refresh to filter single field
|
||||
|
||||
time_t now;
|
||||
time(&now);
|
||||
|
||||
Message* updateMessage = NULL;
|
||||
if (maxAge > 0 && verbose == false) {
|
||||
updateMessage = m_messages->find(clazz, args[argPos], false, true);
|
||||
|
||||
if (updateMessage != NULL && updateMessage->getLastUpdateTime() + maxAge > now)
|
||||
return updateMessage->getLastValue(); // TODO switch from last value to last master/slave to support verbose cached/polled values as well
|
||||
// else: check poll data or read directly from bus
|
||||
}
|
||||
|
||||
Message* message = m_messages->find(clazz, args[argPos], false);
|
||||
|
||||
if (message == NULL) {
|
||||
if (updateMessage != NULL)
|
||||
return "no data stored";
|
||||
else
|
||||
return "message not defined";
|
||||
}
|
||||
if (maxAge > 0 && message->getPollPriority() > 0
|
||||
&& message->getLastUpdateTime() + maxAge > now) {
|
||||
// get poll data
|
||||
return message->getLastValue();
|
||||
} // else: read directly from bus
|
||||
|
||||
SymbolString master;
|
||||
istringstream input;
|
||||
result_t ret = message->prepareMaster(m_address, master, input);
|
||||
if (ret != RESULT_OK) {
|
||||
logError(lf_main, "prepare read: %s", getResultCode(ret));
|
||||
return getResultCode(ret);
|
||||
}
|
||||
logInfo(lf_main, "read cmd: %s", master.getDataStr().c_str());
|
||||
|
||||
// send message
|
||||
SymbolString slave;
|
||||
ret = m_busHandler->sendAndWait(master, slave);
|
||||
|
||||
ostringstream result;
|
||||
if (ret == RESULT_OK) {
|
||||
if (args.size() == argPos + 2)
|
||||
ret = message->decode(pt_slaveData, slave, result, false, verbose, args[argPos + 1].c_str());
|
||||
else
|
||||
ret = message->decode(pt_slaveData, slave, result, false, verbose); // decode data
|
||||
}
|
||||
if (ret != RESULT_OK) {
|
||||
logError(lf_main, "read: %s", getResultCode(ret));
|
||||
return getResultCode(ret);
|
||||
}
|
||||
return result.str();
|
||||
}
|
||||
|
||||
string MainLoop::executeWrite(vector<string> &args)
|
||||
{
|
||||
size_t argPos = 1;
|
||||
if (args.size() > argPos && args[argPos] == "-h") {
|
||||
argPos++;
|
||||
|
||||
if (args.size() < argPos + 1)
|
||||
return "usage: 'write -h ZZPBSBNNDx'";
|
||||
|
||||
ostringstream msg;
|
||||
msg << hex << setw(2) << setfill('0') << static_cast<unsigned>(m_address) << setw(0);
|
||||
while (argPos < args.size()) {
|
||||
if ((args[argPos].length() % 2) != 0) {
|
||||
return "invalid hex string";
|
||||
}
|
||||
msg << args[argPos++];
|
||||
}
|
||||
|
||||
SymbolString master(msg.str());
|
||||
if (isValidAddress(master[1]) == false)
|
||||
return "invalid destination";
|
||||
|
||||
logNotice(lf_main, "write hex cmd: %s", master.getDataStr().c_str());
|
||||
|
||||
// send message
|
||||
SymbolString slave;
|
||||
result_t ret = m_busHandler->sendAndWait(master, slave);
|
||||
|
||||
if (ret == RESULT_OK) {
|
||||
if (master[1] == BROADCAST || isMaster(master[1]))
|
||||
return "done";
|
||||
return slave.getDataStr();
|
||||
}
|
||||
logError(lf_main, "write hex: %s", getResultCode(ret));
|
||||
return getResultCode(ret);
|
||||
}
|
||||
|
||||
if (args.size() != argPos + 3)
|
||||
return "usage: 'write class name value[;value]*' or 'write -h ZZPBSBNNDx'";
|
||||
|
||||
Message* message = m_messages->find(args[argPos], args[argPos + 1], true);
|
||||
|
||||
if (message == NULL)
|
||||
return "message not defined";
|
||||
|
||||
SymbolString master;
|
||||
istringstream input(args[argPos + 2]);
|
||||
result_t ret = message->prepareMaster(m_address, master, input);
|
||||
if (ret != RESULT_OK) {
|
||||
logError(lf_main, "prepare write: %s", getResultCode(ret));
|
||||
return getResultCode(ret);
|
||||
}
|
||||
logInfo(lf_main, "write cmd: %s", master.getDataStr().c_str());
|
||||
|
||||
// send message
|
||||
SymbolString slave;
|
||||
ret = m_busHandler->sendAndWait(master, slave);
|
||||
|
||||
ostringstream result;
|
||||
if (ret == RESULT_OK) {
|
||||
if (master[1] == BROADCAST || isMaster(master[1]))
|
||||
return "done";
|
||||
|
||||
ret = message->decode(pt_slaveData, slave, result); // decode data
|
||||
if (ret == RESULT_OK && result.str().empty() == true)
|
||||
return "done";
|
||||
}
|
||||
if (ret != RESULT_OK) {
|
||||
logError(lf_main, "write: %s", getResultCode(ret));
|
||||
return getResultCode(ret);
|
||||
}
|
||||
return result.str();
|
||||
}
|
||||
|
||||
string MainLoop::executeFind(vector<string> &args)
|
||||
{
|
||||
size_t argPos = 1;
|
||||
bool verbose = false, withRead = true, withWrite = true, withPassive = true, first = true, onlyWithData = false;
|
||||
string clazz;
|
||||
while (args.size() > argPos && args[argPos][0] == '-') {
|
||||
if (args[argPos] == "-v")
|
||||
verbose = true;
|
||||
else if (args[argPos] == "-r") {
|
||||
if (first == true) {
|
||||
first = false;
|
||||
withWrite = withPassive = false;
|
||||
}
|
||||
withRead = true;
|
||||
}
|
||||
else if (args[argPos] == "-w") {
|
||||
if (first == true) {
|
||||
first = false;
|
||||
withRead = withPassive = false;
|
||||
}
|
||||
withWrite = true;
|
||||
}
|
||||
else if (args[argPos] == "-p") {
|
||||
if (first == true) {
|
||||
first = false;
|
||||
withRead = withWrite = false;
|
||||
}
|
||||
withPassive = true;
|
||||
}
|
||||
else if (args[argPos] == "-d") {
|
||||
onlyWithData = true;
|
||||
}
|
||||
else if (args[argPos] == "-c") {
|
||||
argPos++;
|
||||
if (argPos >= args.size()) {
|
||||
argPos = 0; // print usage
|
||||
break;
|
||||
}
|
||||
clazz = args[argPos];
|
||||
}
|
||||
else {
|
||||
argPos = 0; // print usage
|
||||
break;
|
||||
}
|
||||
argPos++;
|
||||
}
|
||||
if (argPos == 0 || args.size() < argPos || args.size() > argPos + 1)
|
||||
return "usage: 'find [-v] [-r] [-w] [-p] [-d] [-c class] [name]'";
|
||||
|
||||
deque<Message*> messages;
|
||||
if (args.size() == argPos)
|
||||
messages = m_messages->findAll(clazz, "", -1, false, withRead, withWrite, withPassive);
|
||||
else
|
||||
messages = m_messages->findAll(clazz, args[argPos], -1, false, withRead, withWrite, withPassive);
|
||||
|
||||
bool found = false;
|
||||
ostringstream result;
|
||||
char str[34];
|
||||
for (deque<Message*>::iterator it = messages.begin(); it < messages.end();) {
|
||||
Message* message = *it++;
|
||||
unsigned char dstAddress = message->getDstAddress();
|
||||
if (dstAddress == SYN)
|
||||
continue;
|
||||
time_t lastup = message->getLastUpdateTime();
|
||||
if (onlyWithData == true && lastup == 0)
|
||||
continue;
|
||||
if (found == true)
|
||||
result << endl;
|
||||
result << message->getClass() << " " << message->getName() << " = ";
|
||||
if (lastup == 0)
|
||||
result << "no data stored";
|
||||
else
|
||||
result << message->getLastValue();
|
||||
if (verbose == true) {
|
||||
if (lastup == 0)
|
||||
sprintf(str, "ZZ=%02x", dstAddress);
|
||||
else {
|
||||
struct tm* td = localtime(&lastup);
|
||||
sprintf(str, "ZZ=%02x, lastup=%04d-%02d-%02d %02d:%02d:%02d",
|
||||
dstAddress, td->tm_year+1900, td->tm_mon+1, td->tm_mday,
|
||||
td->tm_hour, td->tm_min, td->tm_sec);
|
||||
}
|
||||
result << " [" << str << "]";
|
||||
}
|
||||
found = true;
|
||||
}
|
||||
if (found == false)
|
||||
return "no message found";
|
||||
|
||||
return result.str();
|
||||
}
|
||||
|
||||
string MainLoop::executeListen(vector<string> &args, bool& listening)
|
||||
{
|
||||
if (args.size() <= 1) {
|
||||
if (listening == true)
|
||||
return "listen continued";
|
||||
|
||||
listening = true;
|
||||
return "listen started";
|
||||
}
|
||||
|
||||
if (args.size() != 2 || args[1] != "stop")
|
||||
return "usage: 'listen [stop]'";
|
||||
|
||||
listening = false;
|
||||
return "listen stopped";
|
||||
}
|
||||
|
||||
string MainLoop::executeScan(vector<string> &args)
|
||||
{
|
||||
if (args.size() <= 1) {
|
||||
result_t result = m_busHandler->startScan();
|
||||
if (result == RESULT_OK)
|
||||
return "scan initiated";
|
||||
|
||||
logError(lf_main, "scan: %s", getResultCode(result));
|
||||
return getResultCode(result);
|
||||
}
|
||||
|
||||
if (strcasecmp(args[1].c_str(), "FULL") == 0) {
|
||||
result_t result = m_busHandler->startScan(true);
|
||||
if (result == RESULT_OK)
|
||||
return "done";
|
||||
|
||||
logError(lf_main, "full scan: %s", getResultCode(result));
|
||||
return getResultCode(result);
|
||||
}
|
||||
|
||||
if (strcasecmp(args[1].c_str(), "RESULT") == 0) {
|
||||
ostringstream result;
|
||||
m_busHandler->formatScanResult(result);
|
||||
return result.str();
|
||||
}
|
||||
|
||||
return "usage: 'scan'\n"
|
||||
" 'scan full'\n"
|
||||
" 'scan result'";
|
||||
}
|
||||
|
||||
string MainLoop::executeLog(vector<string> &args)
|
||||
{
|
||||
if (args.size() != 3)
|
||||
return "usage: 'log areas area,area,..' (areas: bas|net|bus|upd|all)\n" // TODO
|
||||
" 'log level level' (level: error|event|trace|debug)";
|
||||
|
||||
bool result;
|
||||
if (strcasecmp(args[1].c_str(), "AREAS") == 0)
|
||||
result = setLogFacilities(args[2].c_str());
|
||||
else if (strcasecmp(args[1].c_str(), "LEVEL") == 0)
|
||||
result = setLogLevel(args[2].c_str());
|
||||
else
|
||||
return "usage: 'log areas area,area,..' (areas: bas|net|bus|upd|all)\n" // TODO
|
||||
" 'log level level' (level: error|event|trace|debug)";
|
||||
|
||||
if (result == true)
|
||||
return "done";
|
||||
|
||||
return "invalid area/level";
|
||||
}
|
||||
|
||||
string MainLoop::executeRaw(vector<string> &args)
|
||||
{
|
||||
if (args.size() != 1)
|
||||
return "usage: 'raw'";
|
||||
|
||||
bool enabled = !m_port->getLogRaw();
|
||||
m_port->setLogRaw(enabled);
|
||||
|
||||
return enabled ? "raw output enabled" : "raw output disabled";
|
||||
}
|
||||
|
||||
string MainLoop::executeDump(vector<string> &args)
|
||||
{
|
||||
if (args.size() != 1)
|
||||
return "usage: 'dump'";
|
||||
|
||||
bool enabled = !m_port->getDumpRaw();
|
||||
m_port->setDumpRaw(enabled);
|
||||
|
||||
return enabled ? "dump enabled" : "dump disabled";
|
||||
}
|
||||
|
||||
string MainLoop::executeReload(vector<string> &args)
|
||||
{
|
||||
if (args.size() != 1)
|
||||
return "usage: 'reload'";
|
||||
|
||||
// reload commands
|
||||
result_t result = loadConfigFiles(m_templates, m_messages);
|
||||
if (result == RESULT_OK)
|
||||
return "done";
|
||||
|
||||
return getResultCode(result);
|
||||
}
|
||||
|
||||
string MainLoop::executeHelp()
|
||||
{
|
||||
return "commands:\n"
|
||||
" read - read ebus values 'read [-v] [-f] [-m seconds] [-c class] name [field]'\n"
|
||||
" write - write ebus values 'write class name value[;value]*' or 'write -h ZZPBSBNNDx'\n"
|
||||
" find - find ebus values 'find [-v] [-r] [-w] [-p] [-d] [-c class] [name]'\n"
|
||||
" listen - listen for updates 'listen [stop]'\n"
|
||||
" scan - scan ebus known addresses 'scan'\n"
|
||||
" - scan ebus all addresses 'scan full'\n"
|
||||
" - show scan results 'scan result'\n"
|
||||
" log - change log areas 'log areas area,area,..' (areas: bas|net|bus|upd|all)\n"//TODO
|
||||
" - change log level 'log level level' (level: error|event|trace|debug)\n"
|
||||
" raw - toggle log raw data 'raw'\n"
|
||||
" dump - toggle dump state 'dump'\n"
|
||||
" reload - reload ebus configuration 'reload'\n"
|
||||
" stop - stop daemon 'stop'\n"
|
||||
" quit - close connection 'quit'\n"
|
||||
" help - print this page 'help'";
|
||||
}
|
||||
|
||||
string MainLoop::getUpdates(time_t since, time_t until)
|
||||
{
|
||||
ostringstream result;
|
||||
|
||||
deque<Message*> messages;
|
||||
messages = m_messages->findAll("", "", -1, false, true, true, true);
|
||||
|
||||
for (deque<Message*>::iterator it = messages.begin(); it < messages.end();) {
|
||||
Message* message = *it++;
|
||||
unsigned char dstAddress = message->getDstAddress();
|
||||
if (dstAddress == SYN)
|
||||
continue;
|
||||
time_t lastchg = message->getLastChangeTime();
|
||||
if (lastchg < since || lastchg >= until)
|
||||
continue;
|
||||
result << message->getClass() << " " << message->getName() << " = ";
|
||||
result << message->getLastValue() << endl;
|
||||
}
|
||||
|
||||
return result.str();
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Copyright (C) John Baier 2014-2015 <ebusd@johnm.de>
|
||||
*
|
||||
* This file is part of ebusd.
|
||||
*
|
||||
* ebusd is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* ebusd is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with ebusd. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
#ifndef MAINLOOP_H_
|
||||
#define MAINLOOP_H_
|
||||
|
||||
#include "message.h"
|
||||
#include "network.h"
|
||||
#include "bushandler.h"
|
||||
|
||||
/** \file mainloop.h */
|
||||
|
||||
using namespace std;
|
||||
|
||||
/**
|
||||
* The main loop handling requests from connected clients.
|
||||
*/
|
||||
class MainLoop
|
||||
{
|
||||
|
||||
public:
|
||||
/**
|
||||
* Construct the main loop and create network and bus handling components.
|
||||
* @param opt the program options.
|
||||
* @param templates the @a DataFieldTemplates instance.
|
||||
* @param messages the @a MessageMap instance.
|
||||
*/
|
||||
MainLoop(const struct options opt, DataFieldTemplates* templates, MessageMap* messages);
|
||||
|
||||
/**
|
||||
* Destructor.
|
||||
*/
|
||||
~MainLoop();
|
||||
|
||||
/**
|
||||
* Run the main loop.
|
||||
*/
|
||||
void run();
|
||||
|
||||
/**
|
||||
* Add a client @a NetMessage to the queue.
|
||||
* @param message the client @a NetMessage to handle.
|
||||
*/
|
||||
void addMessage(NetMessage* message) { m_netQueue.add(message); }
|
||||
|
||||
/**
|
||||
* Create a log message for a received/sent raw data byte.
|
||||
* @param byte the raw data byte.
|
||||
* @param received true if the byte was received, false if it was sent.
|
||||
*/
|
||||
static void logRaw(const unsigned char byte, bool received);
|
||||
|
||||
private:
|
||||
|
||||
/** the @a DataFieldTemplates instance. */
|
||||
DataFieldTemplates* m_templates;
|
||||
|
||||
/** the @a MessageMap instance. */
|
||||
MessageMap* m_messages;
|
||||
|
||||
/** the own master address for sending on the bus. */
|
||||
unsigned char m_address;
|
||||
|
||||
/** the created @a Port instance. */
|
||||
Port* m_port;
|
||||
|
||||
/** the created @a BusHandler instance. */
|
||||
BusHandler* m_busHandler;
|
||||
|
||||
/** the created @a Network instance. */
|
||||
Network* m_network;
|
||||
|
||||
/** the queue for @a NetMessage instances. */
|
||||
WQueue<NetMessage*> m_netQueue;
|
||||
|
||||
/**
|
||||
* Decode and execute client message.
|
||||
* @param data the data string to decode (may be empty).
|
||||
* @param listening set to true when the client is in listening mode.
|
||||
* @return result string to send back to the client.
|
||||
*/
|
||||
string decodeMessage(const string& data, bool& listening);
|
||||
|
||||
/**
|
||||
* Execute the read command.
|
||||
* @param args the arguments passed to the command (starting with the command itself).
|
||||
* @return the result string.
|
||||
*/
|
||||
string executeRead(vector<string> &args);
|
||||
|
||||
/**
|
||||
* Execute the write command.
|
||||
* @param args the arguments passed to the command (starting with the command itself).
|
||||
* @return the result string.
|
||||
*/
|
||||
string executeWrite(vector<string> &args);
|
||||
|
||||
/**
|
||||
* Execute the find command.
|
||||
* @param args the arguments passed to the command (starting with the command itself).
|
||||
* @return the result string.
|
||||
*/
|
||||
string executeFind(vector<string> &args);
|
||||
|
||||
/**
|
||||
* Execute the listen command.
|
||||
* @param args the arguments passed to the command (starting with the command itself).
|
||||
* @param listening set to true when the client is in listening mode.
|
||||
* @return the result string.
|
||||
*/
|
||||
string executeListen(vector<string> &args, bool& listening);
|
||||
|
||||
/**
|
||||
* Execute the scan command.
|
||||
* @param args the arguments passed to the command (starting with the command itself).
|
||||
* @return the result string.
|
||||
*/
|
||||
string executeScan(vector<string> &args);
|
||||
|
||||
/**
|
||||
* Execute the log command.
|
||||
* @param args the arguments passed to the command (starting with the command itself).
|
||||
* @return the result string.
|
||||
*/
|
||||
string executeLog(vector<string> &args);
|
||||
|
||||
/**
|
||||
* Execute the raw command.
|
||||
* @param args the arguments passed to the command (starting with the command itself).
|
||||
* @return the result string.
|
||||
*/
|
||||
string executeRaw(vector<string> &args);
|
||||
|
||||
/**
|
||||
* Execute the dump command.
|
||||
* @param args the arguments passed to the command (starting with the command itself).
|
||||
* @return the result string.
|
||||
*/
|
||||
string executeDump(vector<string> &args);
|
||||
|
||||
/**
|
||||
* Execute the reload command.
|
||||
* @param args the arguments passed to the command (starting with the command itself).
|
||||
* @return the result string.
|
||||
*/
|
||||
string executeReload(vector<string> &args);
|
||||
|
||||
/**
|
||||
* Execute the help command.
|
||||
* @return the result string.
|
||||
*/
|
||||
string executeHelp();
|
||||
|
||||
/**
|
||||
* Get the updates received since the specified time.
|
||||
* @param since the start time from which to add updates (inclusive).
|
||||
* @param until the end time to which to add updates (exclusive).
|
||||
* @return result string to send back to client.
|
||||
*/
|
||||
string getUpdates(time_t since, time_t until);
|
||||
|
||||
};
|
||||
|
||||
#endif // MAINLOOP_H_
|
||||
Reference in New Issue
Block a user