added read-only mode, added optional HTTP JSON port
This commit is contained in:
+23
-8
@@ -64,6 +64,7 @@ static bool isDaemon = false;
|
|||||||
static struct options opt = {
|
static struct options opt = {
|
||||||
"/dev/ttyUSB0", // device
|
"/dev/ttyUSB0", // device
|
||||||
false, // noDeviceCheck
|
false, // noDeviceCheck
|
||||||
|
false, // readonly
|
||||||
CONFIG_PATH, // configPath
|
CONFIG_PATH, // configPath
|
||||||
0, // checkConfig
|
0, // checkConfig
|
||||||
5, // pollInterval
|
5, // pollInterval
|
||||||
@@ -78,6 +79,7 @@ static struct options opt = {
|
|||||||
false, // foreground
|
false, // foreground
|
||||||
8888, // port
|
8888, // port
|
||||||
false, // localOnly
|
false, // localOnly
|
||||||
|
0, // httpPort
|
||||||
PACKAGE_LOGFILE, // logFile
|
PACKAGE_LOGFILE, // logFile
|
||||||
false, // logRaw
|
false, // logRaw
|
||||||
false, // dump
|
false, // dump
|
||||||
@@ -109,17 +111,19 @@ static const char argpdoc[] =
|
|||||||
#define O_MASCNT 9
|
#define O_MASCNT 9
|
||||||
#define O_GENSYN 10
|
#define O_GENSYN 10
|
||||||
#define O_LOCAL 11
|
#define O_LOCAL 11
|
||||||
#define O_LOGARE 12
|
#define O_HTTPPT 12
|
||||||
#define O_LOGLEV 13
|
#define O_LOGARE 13
|
||||||
#define O_LOGRAW 14
|
#define O_LOGLEV 14
|
||||||
#define O_DMPFIL 15
|
#define O_LOGRAW 15
|
||||||
#define O_DMPSIZ 16
|
#define O_DMPFIL 16
|
||||||
|
#define O_DMPSIZ 17
|
||||||
|
|
||||||
/** the definition of the known program arguments. */
|
/** the definition of the known program arguments. */
|
||||||
static const struct argp_option argpoptions[] = {
|
static const struct argp_option argpoptions[] = {
|
||||||
{NULL, 0, NULL, 0, "Device options:", 1 },
|
{NULL, 0, NULL, 0, "Device options:", 1 },
|
||||||
{"device", 'd', "DEV", 0, "Use DEV as eBUS device (serial device or ip:port) [/dev/ttyUSB0]", 0 },
|
{"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 },
|
{"nodevicecheck", 'n', NULL, 0, "Skip serial eBUS device test", 0 },
|
||||||
|
{"readonly", 'r', NULL, 0, "Only read from device, never write to it", 0 },
|
||||||
|
|
||||||
{NULL, 0, NULL, 0, "Message configuration options:", 2 },
|
{NULL, 0, NULL, 0, "Message configuration options:", 2 },
|
||||||
{"configpath", 'c', "PATH", 0, "Read CSV config files from PATH [" CONFIG_PATH "]", 0 },
|
{"configpath", 'c', "PATH", 0, "Read CSV config files from PATH [" CONFIG_PATH "]", 0 },
|
||||||
@@ -139,8 +143,9 @@ static const struct argp_option argpoptions[] = {
|
|||||||
|
|
||||||
{NULL, 0, NULL, 0, "Daemon options:", 4 },
|
{NULL, 0, NULL, 0, "Daemon options:", 4 },
|
||||||
{"foreground", 'f', NULL, 0, "Run in foreground", 0 },
|
{"foreground", 'f', NULL, 0, "Run in foreground", 0 },
|
||||||
{"port", 'p', "PORT", 0, "Listen for client connections on PORT [8888]", 0 },
|
{"port", 'p', "PORT", 0, "Listen for command line connections on PORT [8888]", 0 },
|
||||||
{"localhost", O_LOCAL, NULL, 0, "Listen on 127.0.0.1 interface only", 0 },
|
{"localhost", O_LOCAL, NULL, 0, "Listen for command line on 127.0.0.1 interface only", 0 },
|
||||||
|
{"httpport", O_HTTPPT, "PORT", 0, "Listen for HTTP connections on PORT, 0 to disable [0]", 0 },
|
||||||
|
|
||||||
{NULL, 0, NULL, 0, "Log options:", 5 },
|
{NULL, 0, NULL, 0, "Log options:", 5 },
|
||||||
{"logfile", 'l', "FILE", 0, "Write log to FILE (only for daemon) [" PACKAGE_LOGFILE "]", 0 },
|
{"logfile", 'l', "FILE", 0, "Write log to FILE (only for daemon) [" PACKAGE_LOGFILE "]", 0 },
|
||||||
@@ -179,6 +184,9 @@ error_t parse_opt(int key, char *arg, struct argp_state *state)
|
|||||||
case 'n': // --nodevicecheck
|
case 'n': // --nodevicecheck
|
||||||
opt->noDeviceCheck = true;
|
opt->noDeviceCheck = true;
|
||||||
break;
|
break;
|
||||||
|
case 'r': // --readonly
|
||||||
|
opt->readonly = true;
|
||||||
|
break;
|
||||||
|
|
||||||
// Message configuration options:
|
// Message configuration options:
|
||||||
case 'c': // --configpath=/etc/ebusd
|
case 'c': // --configpath=/etc/ebusd
|
||||||
@@ -266,6 +274,13 @@ error_t parse_opt(int key, char *arg, struct argp_state *state)
|
|||||||
case O_LOCAL: // --localhost
|
case O_LOCAL: // --localhost
|
||||||
opt->localOnly = true;
|
opt->localOnly = true;
|
||||||
break;
|
break;
|
||||||
|
case O_HTTPPT: // --httpport
|
||||||
|
opt->httpPort = (uint16_t)parseInt(arg, 10, 1, 65535, result);
|
||||||
|
if (result != RESULT_OK) {
|
||||||
|
argp_error(state, "invalid port");
|
||||||
|
return EINVAL;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
// Log options:
|
// Log options:
|
||||||
case 'l': // --logfile=/var/log/ebusd.log
|
case 'l': // --logfile=/var/log/ebusd.log
|
||||||
@@ -548,7 +563,7 @@ int main(int argc, char* argv[])
|
|||||||
}
|
}
|
||||||
|
|
||||||
// open the device
|
// open the device
|
||||||
Device *device = Device::create(opt.device, !opt.noDeviceCheck, &logRawData);
|
Device *device = Device::create(opt.device, !opt.noDeviceCheck, opt.readonly, &logRawData);
|
||||||
if (device == NULL) {
|
if (device == NULL) {
|
||||||
logError(lf_main, "unable to create device %s", opt.device);
|
logError(lf_main, "unable to create device %s", opt.device);
|
||||||
return EINVAL;
|
return EINVAL;
|
||||||
|
|||||||
+3
-1
@@ -32,6 +32,7 @@ struct options
|
|||||||
{
|
{
|
||||||
const char* device; //!< eBUS device (serial device or ip:port) [/dev/ttyUSB0]
|
const char* device; //!< eBUS device (serial device or ip:port) [/dev/ttyUSB0]
|
||||||
bool noDeviceCheck; //!< skip serial eBUS device test
|
bool noDeviceCheck; //!< skip serial eBUS device test
|
||||||
|
bool readonly; //!< read-only access to the device
|
||||||
|
|
||||||
const char* configPath; //!< path to CSV configuration files [/etc/ebusd]
|
const char* configPath; //!< path to CSV configuration files [/etc/ebusd]
|
||||||
int checkConfig; //!< check CSV config files (!=0) and optionally dump (2), then stop
|
int checkConfig; //!< check CSV config files (!=0) and optionally dump (2), then stop
|
||||||
@@ -47,8 +48,9 @@ struct options
|
|||||||
bool generateSyn; //!< enable AUTO-SYN symbol generation
|
bool generateSyn; //!< enable AUTO-SYN symbol generation
|
||||||
|
|
||||||
bool foreground; //!< run in foreground
|
bool foreground; //!< run in foreground
|
||||||
uint16_t port; //!< port to listen for client connections [8888]
|
uint16_t port; //!< port to listen for command line connections [8888]
|
||||||
bool localOnly; //!< listen on 127.0.0.1 interface only
|
bool localOnly; //!< listen on 127.0.0.1 interface only
|
||||||
|
uint16_t httpPort; //!< optional port to listen for HTTP connections, 0 to disable [0]
|
||||||
|
|
||||||
const char* logFile; //!< log file name [/var/log/ebusd.log]
|
const char* logFile; //!< log file name [/var/log/ebusd.log]
|
||||||
bool logRaw; //!< log each received/sent byte on the bus
|
bool logRaw; //!< log each received/sent byte on the bus
|
||||||
|
|||||||
+108
-18
@@ -52,7 +52,7 @@ MainLoop::MainLoop(const struct options opt, Device *device, DataFieldTemplates*
|
|||||||
m_busHandler->start("bushandler");
|
m_busHandler->start("bushandler");
|
||||||
|
|
||||||
// create network
|
// create network
|
||||||
m_network = new Network(opt.localOnly, opt.port, &m_netQueue);
|
m_network = new Network(opt.localOnly, opt.port, opt.httpPort, &m_netQueue);
|
||||||
m_network->start("network");
|
m_network->start("network");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,21 +84,18 @@ void MainLoop::run()
|
|||||||
|
|
||||||
// pick the next message to handle
|
// pick the next message to handle
|
||||||
NetMessage* message = m_netQueue.remove();
|
NetMessage* message = m_netQueue.remove();
|
||||||
string data = message->getData();
|
string request = message->getRequest();
|
||||||
|
|
||||||
time_t since, until;
|
time_t since, until;
|
||||||
time(&until);
|
time(&until);
|
||||||
bool listening = message->isListening(since);
|
bool listening = message->isListening(&since);
|
||||||
if (!listening)
|
if (!listening)
|
||||||
since = until;
|
since = until;
|
||||||
|
|
||||||
bool connected = true;
|
bool connected = true;
|
||||||
if (data.length() > 0) {
|
if (request.length() > 0) {
|
||||||
data.erase(remove(data.begin(), data.end(), '\r'), data.end());
|
logDebug(lf_main, ">>> %s", request.c_str());
|
||||||
data.erase(remove(data.begin(), data.end(), '\n'), data.end());
|
result = decodeMessage(request, message->isHttp(), connected, listening, running);
|
||||||
|
|
||||||
logDebug(lf_main, ">>> %s", data.c_str());
|
|
||||||
result = decodeMessage(data, connected, listening, running);
|
|
||||||
|
|
||||||
logDebug(lf_main, "<<< %s", result.c_str());
|
logDebug(lf_main, "<<< %s", result.c_str());
|
||||||
if (result.length() == 0)
|
if (result.length() == 0)
|
||||||
@@ -115,7 +112,7 @@ void MainLoop::run()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string MainLoop::decodeMessage(const string& data, bool& connected, bool& listening, bool& running)
|
string MainLoop::decodeMessage(const string& data, const bool isHttp, bool& connected, bool& listening, bool& running)
|
||||||
{
|
{
|
||||||
ostringstream result;
|
ostringstream result;
|
||||||
|
|
||||||
@@ -125,11 +122,16 @@ string MainLoop::decodeMessage(const string& data, bool& connected, bool& listen
|
|||||||
vector<string> args;
|
vector<string> args;
|
||||||
bool escaped = false;
|
bool escaped = false;
|
||||||
|
|
||||||
while (getline(stream, token, ' ') != 0) {
|
char delim = ' ';
|
||||||
|
while (getline(stream, token, delim) != 0) {
|
||||||
|
if (isHttp && delim == '/' && token.length() > 0 && token[0] == '?') {
|
||||||
|
token.erase(0, 1);
|
||||||
|
delim = '&';
|
||||||
|
}
|
||||||
if (escaped) {
|
if (escaped) {
|
||||||
args.pop_back();
|
args.pop_back();
|
||||||
if (token.length() > 0 && token[token.length()-1] == '"') {
|
if (token.length() > 0 && token[token.length()-1] == '"') {
|
||||||
token = token.substr(0, token.length() - 1);
|
token.erase(token.length() - 1, 1);
|
||||||
escaped = false;
|
escaped = false;
|
||||||
}
|
}
|
||||||
token = previous + " " + token;
|
token = previous + " " + token;
|
||||||
@@ -137,20 +139,26 @@ string MainLoop::decodeMessage(const string& data, bool& connected, bool& listen
|
|||||||
else if (token.length() == 0) // allow multiple space chars for a single delimiter
|
else if (token.length() == 0) // allow multiple space chars for a single delimiter
|
||||||
continue;
|
continue;
|
||||||
else if (token[0] == '"') {
|
else if (token[0] == '"') {
|
||||||
token = token.substr(1);
|
token.erase(0, 1);
|
||||||
if (token.length() > 0 && token[token.length()-1] == '"')
|
if (token.length() > 0 && token[token.length()-1] == '"')
|
||||||
token = token.substr(0, token.length() - 1);
|
token.erase(token.length() - 1, 1);
|
||||||
else
|
else
|
||||||
escaped = true;
|
escaped = true;
|
||||||
}
|
}
|
||||||
args.push_back(token);
|
args.push_back(token);
|
||||||
previous = token;
|
previous = token;
|
||||||
|
if (isHttp)
|
||||||
|
delim = '/';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.size() == 0)
|
if (args.size() == 0)
|
||||||
return executeHelp();
|
return executeHelp();
|
||||||
|
|
||||||
const char* str = args[0].c_str();
|
const char* str = args[0].c_str();
|
||||||
|
if (isHttp) {
|
||||||
|
if (strcmp(str, "GET") == 0)
|
||||||
|
return executeGet(args);
|
||||||
|
} else {
|
||||||
if (args.size() == 2) {
|
if (args.size() == 2) {
|
||||||
// check for "CMD -h"
|
// check for "CMD -h"
|
||||||
if (strcasecmp(args[1].c_str(), "-h") == 0 || strcasecmp(args[1].c_str(), "-?") == 0 || strcasecmp(args[1].c_str(), "--help") == 0)
|
if (strcasecmp(args[1].c_str(), "-h") == 0 || strcasecmp(args[1].c_str(), "-?") == 0 || strcasecmp(args[1].c_str(), "--help") == 0)
|
||||||
@@ -188,7 +196,7 @@ string MainLoop::decodeMessage(const string& data, bool& connected, bool& listen
|
|||||||
return executeQuit(args, connected);
|
return executeQuit(args, connected);
|
||||||
if (strcasecmp(str, "H") == 0 || strcasecmp(str, "HELP") == 0)
|
if (strcasecmp(str, "H") == 0 || strcasecmp(str, "HELP") == 0)
|
||||||
return executeHelp();
|
return executeHelp();
|
||||||
|
}
|
||||||
return "ERR: command not found";
|
return "ERR: command not found";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,7 +292,7 @@ string MainLoop::executeRead(vector<string> &args)
|
|||||||
cacheMessage = message; // message is newer/better
|
cacheMessage = message; // message is newer/better
|
||||||
|
|
||||||
if (cacheMessage != NULL && (cacheMessage->getLastUpdateTime() + maxAge > now || (cacheMessage->isPassive() && cacheMessage->getLastUpdateTime() != 0))) {
|
if (cacheMessage != NULL && (cacheMessage->getLastUpdateTime() + maxAge > now || (cacheMessage->isPassive() && cacheMessage->getLastUpdateTime() != 0))) {
|
||||||
result_t ret = cacheMessage->decodeLastData(result, verbose, fieldIndex==-2 ? NULL : fieldName.c_str(), fieldIndex);
|
result_t ret = cacheMessage->decodeLastData(result, false, verbose?df_verbose:df_standard, fieldIndex==-2 ? NULL : fieldName.c_str(), fieldIndex);
|
||||||
if (ret != RESULT_OK)
|
if (ret != RESULT_OK)
|
||||||
return getResultCode(ret);
|
return getResultCode(ret);
|
||||||
|
|
||||||
@@ -316,7 +324,7 @@ string MainLoop::executeRead(vector<string> &args)
|
|||||||
ret = m_busHandler->sendAndWait(master, slave);
|
ret = m_busHandler->sendAndWait(master, slave);
|
||||||
|
|
||||||
if (ret == RESULT_OK) {
|
if (ret == RESULT_OK) {
|
||||||
ret = message->decode(pt_slaveData, slave, result, false, verbose, fieldIndex==-2 ? NULL : fieldName.c_str(), fieldIndex);
|
ret = message->decode(pt_slaveData, slave, result, false, verbose?df_verbose:df_standard, fieldIndex==-2 ? NULL : fieldName.c_str(), fieldIndex);
|
||||||
}
|
}
|
||||||
if (ret < RESULT_OK) {
|
if (ret < RESULT_OK) {
|
||||||
logError(lf_main, "read: %s", getResultCode(ret));
|
logError(lf_main, "read: %s", getResultCode(ret));
|
||||||
@@ -530,7 +538,7 @@ string MainLoop::executeFind(vector<string> &args)
|
|||||||
if (lastup == 0)
|
if (lastup == 0)
|
||||||
result << "no data stored";
|
result << "no data stored";
|
||||||
else
|
else
|
||||||
message->decodeLastData(result, verbose);
|
message->decodeLastData(result, verbose?df_verbose:df_standard);
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
if (lastup == 0)
|
if (lastup == 0)
|
||||||
sprintf(str, "%02x", dstAddress);
|
sprintf(str, "%02x", dstAddress);
|
||||||
@@ -751,6 +759,88 @@ string MainLoop::executeHelp()
|
|||||||
" help|h Print help help [COMMAND]";
|
" help|h Print help help [COMMAND]";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string MainLoop::executeGet(vector<string> &args)
|
||||||
|
{
|
||||||
|
size_t argPos = 1;
|
||||||
|
bool onlyWithData = true;
|
||||||
|
|
||||||
|
deque<Message*> messages;
|
||||||
|
if (args.size() >= argPos+2)
|
||||||
|
messages = m_messages->findAll(args[argPos], args[argPos+1], -1, false, true, false, true);
|
||||||
|
else if (args.size() == argPos+1)
|
||||||
|
messages = m_messages->findAll(args[argPos], "", -1, false, true, false, true);
|
||||||
|
else
|
||||||
|
messages = m_messages->findAll("", "", -1, false, true, false, true);
|
||||||
|
|
||||||
|
bool first = true;
|
||||||
|
ostringstream result;
|
||||||
|
result << "{";
|
||||||
|
string lastCircuit = "";
|
||||||
|
result_t ret = RESULT_OK;
|
||||||
|
for (deque<Message*>::iterator it = messages.begin(); it < messages.end();) {
|
||||||
|
Message* message = *it++;
|
||||||
|
time_t lastup = message->getLastUpdateTime();
|
||||||
|
if (onlyWithData && lastup == 0)
|
||||||
|
continue;
|
||||||
|
unsigned char dstAddress = message->getDstAddress();
|
||||||
|
if (dstAddress == SYN)
|
||||||
|
continue;
|
||||||
|
if (message->getCircuit() != lastCircuit) {
|
||||||
|
if (lastCircuit.length() > 0)
|
||||||
|
result << "\n },";
|
||||||
|
lastCircuit = message->getCircuit();
|
||||||
|
result << "\n \"" << lastCircuit << "\": {";
|
||||||
|
first = true;
|
||||||
|
}
|
||||||
|
if (first)
|
||||||
|
first = false;
|
||||||
|
else
|
||||||
|
result << ",";
|
||||||
|
result << "\n \"" << message->getName() << "\": {";
|
||||||
|
result << "\n \"lastup\": " << setw(0) << dec << static_cast<unsigned>(lastup);
|
||||||
|
if (lastup != 0) {
|
||||||
|
result << ",\n \"zz\": \"" << setfill('0') << setw(2) << hex << static_cast<unsigned>(dstAddress) << "\"";
|
||||||
|
result << ",\n \"fields\": [";
|
||||||
|
ret = message->decodeLastData(result, false, df_json);
|
||||||
|
if (ret < RESULT_OK)
|
||||||
|
break;
|
||||||
|
result << "\n ]";
|
||||||
|
}
|
||||||
|
result << ",\n \"passive\": " << (message->isPassive() ? "true" : "false");
|
||||||
|
result << ",\n \"write\": " << (message->isWrite() ? "true" : "false");
|
||||||
|
result << "\n }";
|
||||||
|
}
|
||||||
|
if (lastCircuit.length() > 0)
|
||||||
|
result << "\n }";
|
||||||
|
result << "\n}";
|
||||||
|
|
||||||
|
if (ret == RESULT_OK ) {
|
||||||
|
string str = result.str();
|
||||||
|
result.str("");
|
||||||
|
result.clear();
|
||||||
|
result << "HTTP/1.0 200 OK\r\n";
|
||||||
|
result << "Content-Type: application/json\r\n";
|
||||||
|
result << "Content-Length: " << setw(0) << dec << static_cast<unsigned>(str.length()) << "\r\n";
|
||||||
|
result << "\r\n";
|
||||||
|
result << str;
|
||||||
|
} else {
|
||||||
|
result.str("");
|
||||||
|
result.clear();
|
||||||
|
result << "HTTP/1.0 ";
|
||||||
|
switch (ret) {
|
||||||
|
case RESULT_ERR_NOTFOUND:
|
||||||
|
result << "404 Not Found";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
result << "500 Internal Server Error";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
result << "\r\n";
|
||||||
|
result << "\r\n";
|
||||||
|
}
|
||||||
|
return result.str();
|
||||||
|
}
|
||||||
|
|
||||||
string MainLoop::getUpdates(time_t since, time_t until)
|
string MainLoop::getUpdates(time_t since, time_t until)
|
||||||
{
|
{
|
||||||
ostringstream result;
|
ostringstream result;
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ private:
|
|||||||
* @param running set to false when the server shall be stopped.
|
* @param running set to false when the server shall be stopped.
|
||||||
* @return result string to send back to the client.
|
* @return result string to send back to the client.
|
||||||
*/
|
*/
|
||||||
string decodeMessage(const string& data, bool& connected, bool& listening, bool& running);
|
string decodeMessage(const string& data, const bool isHttp, bool& connected, bool& listening, bool& running);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the read command.
|
* Execute the read command.
|
||||||
@@ -193,6 +193,13 @@ private:
|
|||||||
*/
|
*/
|
||||||
string executeHelp();
|
string executeHelp();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the HTTP GET command.
|
||||||
|
* @param args the arguments passed to the command (starting with the command itself).
|
||||||
|
* @return the result string.
|
||||||
|
*/
|
||||||
|
string executeGet(vector<string> &args);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the updates received since the specified time.
|
* Get the updates received since the specified time.
|
||||||
* @param since the start time from which to add updates (inclusive).
|
* @param since the start time from which to add updates (inclusive).
|
||||||
|
|||||||
Reference in New Issue
Block a user