Files
ebusd/src/ebusd/mainloop.cpp
T

2476 lines
85 KiB
C++

/*
* ebusd - daemon for communication with eBUS heating systems.
* Copyright (C) 2014-2023 John Baier <ebusd@ebusd.eu>
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "ebusd/mainloop.h"
#include <iomanip>
#include <deque>
#include <algorithm>
#include "ebusd/main.h"
#include "ebusd/scan.h"
#include "lib/utils/log.h"
#include "lib/ebus/data.h"
namespace ebusd {
using std::dec;
using std::hex;
using std::setfill;
using std::setw;
using std::endl;
using std::ifstream;
/** the number of seconds of permanent missing signal after which to reconnect the device. */
#define RECONNECT_MISSING_SIGNAL 60
result_t UserList::getFieldMap(const string& preferLanguage, vector<string>* row, string* errorDescription) const {
// name,secret,level[,level]*
if (row->empty()) {
row->push_back("name");
row->push_back("secret");
row->push_back("*level");
return RESULT_OK;
}
map<string, string> seen;
for (auto& name : *row) {
tolower(&name);
if (name == "name" || name == "secret") {
if (seen.find(name) != seen.end()) {
*errorDescription = "duplicate field " + name;
return RESULT_ERR_INVALID_ARG;
}
} else if (name == "level") {
name = "*level";
} else {
*errorDescription = "unknown field " + name;
return RESULT_ERR_INVALID_ARG;
}
seen[name] = name;
}
if (seen.find("name") == seen.end() || seen.find("secret") == seen.end()) {
return RESULT_ERR_EOF; // require at least name and secret
}
return RESULT_OK;
}
result_t UserList::addFromFile(const string& filename, unsigned int lineNo, map<string, string>* row,
vector< map<string, string> >* subRows, string* errorDescription, bool replace) {
string name = (*row)["name"];
string secret = (*row)["secret"];
if (name.empty()) {
return RESULT_ERR_INVALID_ARG;
}
if (name == "*") {
name = ""; // default levels
}
string levels;
for (const auto& entry : *subRows) {
const auto it = entry.find("level");
if (it != entry.end() && !it->second.empty()) {
if (!levels.empty()) {
levels += VALUE_SEPARATOR;
}
levels += it->second;
}
}
m_userSecrets[name] = secret;
m_userLevels[name] = levels;
return RESULT_OK;
}
#define VERBOSITY_0 OF_NONE
#define VERBOSITY_1 OF_NAMES
#define VERBOSITY_2 (VERBOSITY_1 | OF_UNITS)
#define VERBOSITY_3 (VERBOSITY_2 | OF_COMMENTS)
#define VERBOSITY_4 (VERBOSITY_3 | OF_ALL_ATTRS)
MainLoop::MainLoop(const struct options& opt, Device *device, MessageMap* messages, ScanHelper* scanHelper,
Queue<Request*>* requestQueue)
: Thread(), m_device(device), m_reconnectCount(0), m_userList(opt.accessLevel), m_messages(messages),
m_scanHelper(scanHelper), m_address(opt.address), m_scanConfig(opt.scanConfig),
m_initialScan(opt.readOnly ? ESC : opt.initialScan), m_scanRetries(opt.scanRetries),
m_scanStatus(SCAN_STATUS_NONE), m_polling(opt.pollInterval > 0), m_enableHex(opt.enableHex),
m_shutdown(false), m_runUpdateCheck(opt.updateCheck), m_httpClient(), m_requestQueue(requestQueue) {
m_device->setListener(this);
// open Device
result_t result = m_device->open();
if (result != RESULT_OK) {
logError(lf_bus, "unable to open %s: %s", m_device->getName(), getResultCode(result));
} else if (!m_device->isValid()) {
logError(lf_bus, "device %s not available", m_device->getName());
}
if (opt.dumpFile[0]) {
m_dumpFile = new RotateFile(opt.dumpFile, opt.dumpSize, false, opt.dumpFlush ? 1 : 16);
m_dumpFile->setEnabled(opt.dump);
} else {
m_dumpFile = nullptr;
}
m_logRawEnabled = opt.logRaw != 0;
if (opt.logRawFile[0] && strcmp(opt.logRawFile, opt.logFile) != 0) {
m_logRawFile = new RotateFile(opt.logRawFile, opt.logRawSize, true);
m_logRawFile->setEnabled(m_logRawEnabled);
} else {
m_logRawFile = nullptr;
}
m_logRawBytes = opt.logRaw == 2;
m_logRawLastReceived = true;
m_logRawLastSymbol = SYN;
if (opt.aclFile[0]) {
string errorDescription;
time_t mtime = 0;
istream* stream = FileReader::openFile(opt.aclFile, &errorDescription, &mtime);
if (stream) {
result = m_userList.readFromStream(stream, opt.aclFile, mtime, false, nullptr, &errorDescription);
delete(stream);
} else {
result = RESULT_ERR_NOTFOUND;
}
if (result != RESULT_OK) {
logError(lf_main, "error reading ACL file \"%s\": %s", opt.aclFile, getResultCode(result));
}
}
// create BusHandler
ebus_protocol_config_t config = {
.readOnly = opt.readOnly,
.ownAddress = m_address,
.answer = opt.answer,
.busLostRetries = opt.acquireRetries,
.failedSendRetries = opt.sendRetries,
.busAcquireTimeout = opt.acquireTimeout,
.slaveRecvTimeout = opt.receiveTimeout,
.lockCount = opt.masterCount,
.generateSyn = opt.generateSyn,
.initialSend = opt.initialSend,
};
m_busHandler = new BusHandler(m_device, m_messages, scanHelper,
config, opt.pollInterval);
m_protocol = m_busHandler->getProtocol();
// create network
m_htmlPath = opt.htmlPath;
logInfo(lf_main, "registering data handlers");
if (datahandler_register(&m_userList, m_busHandler, messages, &m_dataHandlers)) {
logInfo(lf_main, "registered data handlers");
} else {
logError(lf_main, "error registering data handlers");
}
if (opt.enableDefine) {
m_newlyDefinedMessages = new MessageMap(true, "", false);
m_newlyDefinedMessages->setResolver(scanHelper);
} else {
m_newlyDefinedMessages = nullptr;
}
}
MainLoop::~MainLoop() {
m_shutdown = true;
join();
for (const auto dataHandler : m_dataHandlers) {
delete dataHandler;
}
m_dataHandlers.clear();
if (m_dumpFile) {
delete m_dumpFile;
m_dumpFile = nullptr;
}
if (m_logRawFile) {
delete m_logRawFile;
m_logRawFile = nullptr;
}
if (m_busHandler != nullptr) {
delete m_busHandler;
m_busHandler = nullptr;
m_protocol = nullptr; // ProtocolHandler is freed by BusHandler
}
if (m_device != nullptr) {
delete m_device;
m_device = nullptr;
}
if (m_newlyDefinedMessages) {
delete m_newlyDefinedMessages;
m_newlyDefinedMessages = nullptr;
}
}
void MainLoop::shutdown() {
m_shutdown = true;
if (m_requestQueue != nullptr) {
m_requestQueue->push(nullptr); // just to notify potentially waiting thread
}
}
/** the delay for running the update check. */
#define CHECK_DELAY (24*3600)
/** the initial delay for running the update check. */
#define CHECK_INITIAL_DELAY (2*60)
/** the number of completed scan runs after which to try again failed ones. */
#define SCAN_REPEAT_COUNT 6
void MainLoop::run() {
bool reload = true;
time_t lastTaskRun, now, start, lastSignal = 0, since, sinkSince = 1, nextCheckRun;
int taskDelay = 5;
symbol_t lastScanAddress = 0; // 0 is known to be a master
scanStatus_t lastScanStatus = m_scanStatus;
int scanCompleted = 0;
int scanRetry = 0;
time(&now);
start = now;
lastTaskRun = now;
nextCheckRun = now + CHECK_INITIAL_DELAY;
ostringstream updates;
list<DataSink*> dataSinks;
deque<Message*> messages;
for (const auto dataHandler : m_dataHandlers) {
if (dataHandler->isDataSink()) {
dataSinks.push_back(dynamic_cast<DataSink*>(dataHandler));
}
dataHandler->startHandler();
}
while (!m_shutdown) {
// pick the next request to handle
Request* req = m_requestQueue->pop(taskDelay);
time(&now);
if (now < lastTaskRun) {
// clock skew
if (now < lastSignal) {
lastSignal -= lastTaskRun-now;
}
lastTaskRun = now;
} else if (!m_shutdown && now > lastTaskRun+taskDelay) {
logDebug(lf_main, "performing regular tasks");
if (m_protocol->hasSignal()) {
lastSignal = now;
} else if (lastSignal && now > lastSignal+RECONNECT_MISSING_SIGNAL) {
lastSignal = 0;
m_protocol->reconnect();
m_reconnectCount++;
}
if (m_scanConfig && scanRetry <= m_scanRetries) {
bool loadDelay = false;
if (m_initialScan != ESC && reload && m_protocol->hasSignal()) {
loadDelay = true;
result_t result;
if (m_initialScan == SYN) {
logNotice(lf_main, "starting initial full scan");
result = m_busHandler->startScan(true, "*");
if (result == RESULT_OK) {
m_scanStatus = SCAN_STATUS_RUNNING;
}
} else if (m_initialScan == BROADCAST) {
logNotice(lf_main, "starting initial broadcast scan");
MasterSymbolString master;
SlaveSymbolString slave;
master.push_back(m_address);
master.push_back(BROADCAST);
master.push_back(0x07);
master.push_back(0xfe); // query existance message
master.adjustHeader();
result = m_protocol->sendAndWait(master, &slave);
} else {
logNotice(lf_main, "starting initial scan for %2.2x", m_initialScan);
result = m_busHandler->scanAndWait(m_initialScan, true);
if (result == RESULT_OK) {
ostringstream ret;
if (m_busHandler->formatScanResult(m_initialScan, false, &ret)) {
logNotice(lf_main, "initial scan result: %s", ret.str().c_str());
}
m_scanStatus = SCAN_STATUS_RUNNING;
}
}
if (result != RESULT_OK) {
logError(lf_main, "initial scan failed: %s", getResultCode(result));
} else {
reload = false;
}
}
if (!loadDelay && m_protocol->hasSignal()) {
lastScanAddress = m_busHandler->getNextScanAddress(lastScanAddress, scanCompleted >= SCAN_REPEAT_COUNT);
if (lastScanAddress == SYN) {
taskDelay = 5;
lastScanAddress = 0;
m_scanStatus = SCAN_STATUS_FINISHED;
scanCompleted++;
if (scanCompleted > SCAN_REPEAT_COUNT) { // repeat failed scan only every Nth time
scanCompleted = 0;
scanRetry++;
logNotice(lf_main, "scan completed %d time(s), %s", scanRetry,
scanRetry <= m_scanRetries ? "check again" : "end");
}
} else {
m_scanStatus = SCAN_STATUS_RUNNING;
result_t result = m_busHandler->scanAndWait(lastScanAddress, true);
taskDelay = (result == RESULT_ERR_NO_SIGNAL) ? 10 : 1;
if (result != RESULT_OK) {
logError(lf_main, "scan config %2.2x: %s", lastScanAddress, getResultCode(result));
} else {
logInfo(lf_main, "scan config %2.2x message received", lastScanAddress);
nextCheckRun = now + CHECK_INITIAL_DELAY; // delay update check due to new scan data
}
}
}
if (lastScanStatus != m_scanStatus && !dataSinks.empty()) {
lastScanStatus = m_scanStatus;
for (const auto dataSink : dataSinks) {
dataSink->notifyScanStatus(lastScanStatus);
}
}
} else if (reload && m_protocol->hasSignal()) {
reload = false;
// execute initial instructions
m_scanHelper->executeInstructions(m_busHandler);
if (m_messages->sizeConditions() > 0 && !m_polling) {
logError(lf_main, "conditions require a poll interval > 0");
}
// notify data sinks to make them update the messages
for (const auto dataSink : dataSinks) {
dataSink->notifyScanStatus(SCAN_STATUS_FINISHED);
}
}
if (m_runUpdateCheck && !m_shutdown && now > nextCheckRun) {
if (!m_httpClient.connect("upd.ebusd.eu",
#ifdef HAVE_SSL
443, true,
#else
80, false,
#endif
PACKAGE_NAME "/" PACKAGE_VERSION)) {
logError(lf_main, "update check connect error");
nextCheckRun = now + CHECK_INITIAL_DELAY;
} else {
ostringstream ostr;
ostr << "{\"v\":\"" PACKAGE_VERSION "\",\"r\":\"" REVISION << "\""
#if defined(__amd64__) || defined(__x86_64__) || defined(__ia64__) || defined(__IA64__)
<< ",\"a\":\"amd64\""
#elif defined(__aarch64__)
<< ",\"a\":\"aarch64\""
#elif defined(__arm__)
<< ",\"a\":\"arm\""
#elif defined(__i386__) || defined(__i686__)
<< ",\"a\":\"i386\""
#elif defined(__mips__)
<< ",\"a\":\"mips\""
#else
<< ",\"a\":\"other\""
#endif
<< ",\"u\":" << (now-start);
m_protocol->formatInfoJson(&ostr);
if (m_reconnectCount) {
ostr << ",\"rc\":" << m_reconnectCount;
}
m_busHandler->formatUpdateInfo(&ostr);
ostr << "}";
string response;
bool repeat = false;
if (!m_httpClient.post("/", ostr.str(), &response, &repeat)) {
logError(lf_main, "update check error: %s", response.c_str());
nextCheckRun = now + (repeat ? CHECK_INITIAL_DELAY : CHECK_DELAY);
} else {
m_updateCheck = response.empty() ? "unknown" : response;
logNotice(lf_main, "update check: %s", response.c_str());
if (!dataSinks.empty()) {
for (const auto dataSink : dataSinks) {
dataSink->notifyUpdateCheckResult(response == "OK" ? "" : m_updateCheck);
}
}
nextCheckRun = now + CHECK_DELAY;
}
}
}
time(&lastTaskRun);
}
time(&now);
if (!dataSinks.empty()) {
messages.clear();
m_messages->lock();
m_messages->findAll("", "", "*", false, true, true, true, true, true, sinkSince, now, false, &messages);
for (const auto message : messages) {
for (const auto dataSink : dataSinks) {
dataSink->notifyUpdate(message);
}
}
m_messages->unlock();
sinkSince = now;
}
if (req == nullptr) {
continue;
}
if (m_shutdown) {
req->setResult("ERR: shutdown", "", nullptr, now, true);
break;
}
string user = req->getUser();
RequestMode reqMode = req->getMode(&since);
if (reqMode.listenMode == lm_none) {
since = now;
}
ostringstream ostream;
bool connected = true;
if (!req->empty()) {
req->log();
bool currentReload = reload;
result_t result = decodeRequest(req, &connected, &reqMode, &user, &reload, &ostream);
if (reload && !currentReload) {
scanRetry = 0; // restart scan counting
}
if (!req->isHttp() && (ostream.tellp() == 0 || result != RESULT_OK)) {
if (reqMode.listenMode != lm_direct) {
ostream.str("");
}
ostream << getResultCode(result);
}
const auto resp = ostream.str();
req->log(&resp);
if (ostream.tellp() == 0) {
ostream << "\n"; // only for HTTP
} else if (!req->isHttp()) {
ostream << (reqMode.listenMode == lm_direct ? "\n" : "\n\n");
}
}
if (reqMode.listenMode == lm_listen) {
if (!reqMode.listenOnlyUnknown) {
string levels = getUserLevels(user);
messages.clear();
m_messages->findAll("", "", levels, false, true, true, true, true, true, since, now, true, &messages);
for (const auto message : messages) {
ostream << message->getCircuit() << " " << message->getName() << " = " << dec;
message->decodeLastData(false, nullptr, -1, reqMode.format, &ostream);
ostream << endl;
}
}
if (reqMode.listenWithUnknown || reqMode.listenOnlyUnknown) {
if (m_busHandler->isGrabEnabled()) {
m_busHandler->formatGrabResult(true, OF_NONE, &ostream, true, since, now);
} else {
m_busHandler->enableGrab(true); // needed for listening to all messages
}
}
} else if (reqMode.listenMode == lm_direct) {
if (m_busHandler->isGrabEnabled()) {
m_busHandler->formatGrabResult(false, OF_NONE, &ostream, true, since, now);
}
}
// send result to client
req->setResult(ostream.str(), user, &reqMode, now, !connected);
}
}
void MainLoop::notifyDeviceData(symbol_t symbol, bool received) {
if (received && m_dumpFile) {
m_dumpFile->write(&symbol, 1);
}
if (!m_logRawFile && !m_logRawEnabled) {
return;
}
if (m_logRawBytes) {
if (m_logRawFile) {
m_logRawFile->write(&symbol, 1, received);
} else if (m_logRawEnabled) {
if (received) {
logNotice(lf_bus, "<%02x", symbol);
} else {
logNotice(lf_bus, ">%02x", symbol);
}
}
return;
}
if (symbol != SYN) {
if (received && !m_logRawLastReceived && symbol == m_logRawLastSymbol) {
return; // skip received echo of previously sent symbol
}
if (m_logRawBuffer.tellp() == 0 || received != m_logRawLastReceived) {
m_logRawLastReceived = received;
if (m_logRawBuffer.tellp() == 0 && m_logRawLastSymbol != SYN) {
m_logRawBuffer << "...";
}
m_logRawBuffer << (received ? "<" : ">");
}
m_logRawBuffer << setw(2) << setfill('0') << hex << static_cast<unsigned>(symbol);
}
m_logRawLastSymbol = symbol;
if (m_logRawBuffer.tellp() > (symbol == SYN ? 0 : 64)) { // flush: direction+5 hdr+24 max data+crc+direction+ack+1
if (symbol != SYN) {
m_logRawBuffer << "...";
}
const string bufStr = m_logRawBuffer.str();
const char* str = bufStr.c_str();
if (m_logRawFile) {
m_logRawFile->write((const unsigned char*)str, strlen(str), received, false);
} else {
logNotice(lf_bus, str);
}
m_logRawBuffer.str("");
}
}
void MainLoop::notifyStatus(bool error, const char* message) {
if (error) {
logError(lf_bus, "device status: %s", message);
} else {
logNotice(lf_bus, "device status: %s", message);
}
}
result_t MainLoop::decodeRequest(Request* req, bool* connected, RequestMode* reqMode,
string* user, bool* reload, ostringstream* ostream) {
vector<string> args;
req->split(&args);
string cmd = args.size() > 0 ? args[0] : "";
if (req->isHttp()) {
if (args.size() < 2) {
*connected = false;
*ostream << "HTTP/1.0 400 Bad Request\r\n\r\n";
return RESULT_OK;
}
if (cmd == "GET") {
return executeGet(args, connected, ostream);
}
*connected = false;
*ostream << "HTTP/1.0 405 Method Not Allowed\r\n\r\n";
return RESULT_OK;
}
transform(cmd.begin(), cmd.end(), cmd.begin(), ::toupper);
if (cmd == "?" || cmd == "H" || cmd == "HELP") {
// found "HELP CMD"
cmd = args.size() > 1 ? args[1] : "";
transform(cmd.begin(), cmd.end(), cmd.begin(), ::toupper);
args.clear(); // empty args is used as command help indicator
}
if (reqMode->listenMode == lm_direct) {
return executeDirect(args, reqMode, ostream);
}
if (cmd.empty() && args.size() == 0) {
return executeHelp(ostream);
}
if (args.size() == 2) {
string arg = args[1];
if (arg == "?" || arg == "-?" || arg == "--help") {
// found "CMD HELP"
args.clear(); // empty args is used as command help indicator
}
}
if (cmd == "AUTH" || cmd == "A") {
return executeAuth(args, user, ostream);
}
if (cmd == "R" || cmd == "READ") {
return executeRead(args, getUserLevels(*user), ostream);
}
if (cmd == "W" || cmd == "WRITE") {
return executeWrite(args, getUserLevels(*user), ostream);
}
if (cmd == "HEX") {
if (m_enableHex) {
return executeHex(args, ostream);
}
*ostream << "ERR: command not enabled";
return RESULT_OK;
}
if (cmd == "F" || cmd == "FIND") {
return executeFind(args, getUserLevels(*user), ostream);
}
if (cmd == "L" || cmd == "LISTEN") {
return executeListen(args, reqMode, ostream);
}
if (cmd == "DIRECT") {
return executeDirect(args, reqMode, ostream);
}
if (cmd == "S" || cmd == "STATE") {
return executeState(args, ostream);
}
if (cmd == "G" || cmd == "GRAB") {
return executeGrab(args, ostream);
}
if (cmd == "DEF" || cmd == "DEFINE") {
if (m_newlyDefinedMessages) {
return executeDefine(args, ostream);
}
*ostream << "ERR: command not enabled";
return RESULT_OK;
}
if (cmd == "D" || cmd == "DECODE") {
return executeDecode(args, ostream);
}
if (cmd == "E" || cmd == "ENCODE") {
return executeEncode(args, ostream);
}
if (cmd == "SCAN") {
return executeScan(args, getUserLevels(*user), ostream);
}
if (cmd == "LOG") {
return executeLog(args, ostream);
}
if (cmd == "RAW") {
return executeRaw(args, ostream);
}
if (cmd == "DUMP") {
return executeDump(args, ostream);
}
if (cmd == "RELOAD") {
*reload = true;
return executeReload(args, ostream);
}
if (cmd == "Q" || cmd == "QUIT") {
return executeQuit(args, connected, ostream);
}
if (cmd == "I" || cmd == "INFO") {
return executeInfo(args, *user, ostream);
}
if (cmd == "?" || cmd == "H" || cmd == "HELP") {
return executeHelp(ostream);
}
*ostream << "ERR: command not found";
return RESULT_OK;
}
result_t MainLoop::parseHexMaster(const vector<string>& args, size_t argPos, symbol_t srcAddress, bool autoLength,
MasterSymbolString* master) {
ostringstream msg;
while (argPos < args.size()) {
if ((args[argPos].length() % 2) != 0) {
return RESULT_ERR_INVALID_NUM;
}
msg << args[argPos++];
}
string str = msg.str();
unsigned int length = static_cast<unsigned int>(str.size());
if (length < (autoLength ? 3 : 4)*2) { // at least ZZ, PB, SB[, NN]
return RESULT_ERR_INVALID_ARG;
}
result_t ret;
if (autoLength) {
length = length/2 - 3;
} else {
length = parseInt(str.substr(3*2, 2).c_str(), 16, 0, MAX_POS, &ret);
if (ret != RESULT_OK) {
return ret;
}
}
if (((autoLength ? 3 : 4)+length)*2 != str.size()) {
return RESULT_ERR_INVALID_ARG;
}
master->push_back(srcAddress == SYN ? m_address : srcAddress);
ret = master->parseHex(str.substr(0, 3*2));
if (ret == RESULT_OK && !isValidAddress((*master)[1])) {
ret = RESULT_ERR_INVALID_ADDR;
}
if (ret != RESULT_OK) {
return ret;
}
if (autoLength) {
master->push_back(static_cast<symbol_t>(length));
}
ret = master->parseHex(str.substr(3*2));
return ret;
}
result_t MainLoop::executeAuth(const vector<string>& args, string* user, ostringstream* ostream) {
if (args.size() != 3) {
*ostream << "usage: auth USER SECRET\n"
" Authenticate with USER name and SECRET.\n"
" USER the user name\n"
" SECRET the secret string of the user";
return RESULT_OK;
}
if (m_userList.checkSecret(args[1], args[2])) {
*user = args[1];
return RESULT_OK;
}
*ostream << "ERR: invalid user name or secret";
return RESULT_OK;
}
result_t MainLoop::executeRead(const vector<string>& args, const string& levels, ostringstream* ostream) {
size_t argPos = 1;
bool hex = false, newDefinition = false;
OutputFormat verbosity = OF_NONE;
time_t maxAge = 5*60;
string circuit, params;
symbol_t srcAddress = SYN, dstAddress = SYN;
size_t pollPriority = 0;
while (args.size() > argPos && args[argPos][0] == '-') {
if (args[argPos] == "-h") {
hex = true;
} else if (args[argPos] == "-def") {
if (!m_newlyDefinedMessages) {
*ostream << "ERR: option not enabled";
return RESULT_OK;
}
newDefinition = true;
} else if (args[argPos] == "-f") {
maxAge = 0;
} 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] == "-v") {
if ((verbosity & VERBOSITY_3) == VERBOSITY_0) {
verbosity |= VERBOSITY_1;
} else if ((verbosity & VERBOSITY_3) == VERBOSITY_1) {
verbosity |= VERBOSITY_2;
} else {
verbosity |= VERBOSITY_3;
}
} else if (args[argPos] == "-vv") {
verbosity |= VERBOSITY_2;
} else if (args[argPos] == "-vvv" || args[argPos] == "-V") {
verbosity |= VERBOSITY_3;
} else if (args[argPos] == "-n") {
verbosity = (verbosity & ~OF_VALUENAME) | OF_NUMERIC;
} else if (args[argPos] == "-N") {
verbosity = (verbosity & ~OF_NUMERIC) | OF_VALUENAME;
} else if (args[argPos] == "-c") {
argPos++;
if (argPos >= args.size()) {
argPos = 0; // print usage
break;
}
circuit = args[argPos];
} else if (args[argPos] == "-s" || args[argPos] == "-d") {
bool dest = args[argPos] == "-d";
argPos++;
if (argPos >= args.size()) {
argPos = 0; // print usage
break;
}
result_t ret;
symbol_t address = (symbol_t)parseInt(args[argPos].c_str(), 16, 0, 0xff, &ret);
if (ret != RESULT_OK || !isValidAddress(address, dest) || (dest == isMaster(address))) {
return RESULT_ERR_INVALID_ADDR; // deny send from slave address and to master addresses
}
if (dest) {
dstAddress = address;
} else {
srcAddress = address == m_address ? SYN : address;
}
} else if (args[argPos] == "-p") {
argPos++;
if (argPos >= args.size()) {
argPos = 0; // print usage
break;
}
result_t ret;
pollPriority = (size_t)parseInt(args[argPos].c_str(), 10, 1, 9, &ret);
if (ret != RESULT_OK) {
return RESULT_ERR_INVALID_NUM;
}
} else if (args[argPos] == "-i") {
argPos++;
if (argPos >= args.size()) {
argPos = 0; // print usage
break;
}
params = args[argPos];
} else {
argPos = 0; // print usage
break;
}
argPos++;
}
if ((hex && (newDefinition || verbosity != OF_NONE || !circuit.empty() || !params.empty() || dstAddress != SYN
|| pollPriority > 0 || args.size() < argPos + 1))
|| (newDefinition && (hex || !circuit.empty() || pollPriority > 0 || args.size() != argPos + 1))) {
argPos = 0; // print usage
}
if (argPos == 0 || args.size() < argPos + 1 || args.size() > argPos + 2) {
*ostream <<
"usage: read [-f] [-m SECONDS] [-s QQ] [-d ZZ] [-c CIRCUIT] [-p PRIO] [-v|-V] [-n|-N] [-i VALUE[;VALUE]*]"
" NAME [FIELD[.N]]\n"
" or: read [-f] [-m SECONDS] [-s QQ] [-d ZZ] [-v|-V] [-n|-N] [-i VALUE[;VALUE]*] -def DEFINITION "
"(only if enabled)\n"
" or: read [-f] [-m SECONDS] [-s QQ] [-c CIRCUIT] -h ZZPBSBNN[DD]*\n"
" Read value(s) or hex message.\n"
" -f force reading from the bus (same as '-m 0')\n"
" -m SECONDS only return cached value if age is less than SECONDS [300]\n"
" -c CIRCUIT limit to messages of CIRCUIT\n"
" -s QQ override source address QQ\n"
" -d ZZ override destination address ZZ\n"
" -p PRIO set the message poll priority (1-9)\n"
" -v increase verbosity (include names/units/comments)\n"
" -V be very verbose (include names, units, and comments)\n"
" -n use numeric value of value=name pairs\n"
" -N use numeric and named value of value=name pairs\n"
" -i VALUE read additional message parameters from VALUE\n"
" NAME NAME of the message to send\n"
" FIELD only retrieve the field named FIELD\n"
" N only retrieve the N'th field named FIELD (0-based)\n"
" -def read with explicit message definition (only if enabled):\n"
" DEFINITION message definition to use instead of known definition\n"
" -h send hex read message (or answer from cache):\n"
" ZZ destination address\n"
" PB SB primary/secondary command byte\n"
" NN number of following data bytes\n"
" DD data byte(s) to send";
return RESULT_OK;
}
time_t now;
time(&now);
if (hex) {
MasterSymbolString master;
result_t ret = parseHexMaster(args, argPos, srcAddress, false, &master);
if (ret != RESULT_OK) {
return ret;
}
if (master[1] == BROADCAST || isMaster(master[1])) {
return RESULT_ERR_INVALID_ARG;
}
logNotice(lf_main, "read hex cmd: %s", master.getStr().c_str());
// find message
Message* message = m_messages->find(master, false, true, false, false);
if (message == nullptr) {
return RESULT_ERR_NOTFOUND;
}
if (!message->hasLevel(levels)) {
return RESULT_ERR_NOTAUTHORIZED;
}
if (message->isWrite()) {
return RESULT_ERR_INVALID_ARG;
}
if (circuit.length() > 0 && circuit != message->getCircuit()) {
return RESULT_ERR_INVALID_ARG; // non-matching circuit
}
if (srcAddress == SYN
&& (message->getLastUpdateTime() + maxAge > now
|| (message->isPassive() && message->getLastUpdateTime() != 0))) {
const SlaveSymbolString& slave = message->getLastSlaveData();
logNotice(lf_main, "hex read %s %s from cache", message->getCircuit().c_str(), message->getName().c_str());
*ostream << slave.getStr();
return RESULT_OK;
}
// send message
SlaveSymbolString slave;
ret = m_protocol->sendAndWait(master, &slave);
if (ret == RESULT_OK) {
ret = message->storeLastData(master, slave);
ostringstream result;
if (ret == RESULT_OK) {
ret = message->decodeLastData(false, nullptr, -1, OF_NONE, &result);
}
if (ret >= RESULT_OK) {
logInfo(lf_main, "read hex %s %s cache update: %s", message->getCircuit().c_str(), message->getName().c_str(),
result.str().c_str());
} else {
logError(lf_main, "read hex %s %s cache update: %s", message->getCircuit().c_str(), message->getName().c_str(),
getResultCode(ret));
}
*ostream << slave.getStr();
return RESULT_OK;
}
logError(lf_main, "read hex %s %s: %s", message->getCircuit().c_str(), message->getName().c_str(),
getResultCode(ret));
return ret;
}
string fieldName;
ssize_t fieldIndex = -2;
if (!newDefinition && args.size() == argPos + 2) {
fieldName = args[argPos + 1];
fieldIndex = -1;
size_t pos = fieldName.find_last_of('.');
if (pos != string::npos) {
result_t result = RESULT_OK;
fieldIndex = static_cast<ssize_t>(parseInt(fieldName.substr(pos+1).c_str(), 10, 0, MAX_POS, &result));
if (result == RESULT_OK) {
fieldName = fieldName.substr(0, pos);
}
}
}
string name;
Message* message;
result_t ret;
if (newDefinition) {
string errorDescription;
istringstream defstr("#\n" + args[argPos]); // ensure first line is not used for determining col names
m_newlyDefinedMessages->clear();
ret = m_newlyDefinedMessages->readFromStream(&defstr, "temporary", now, true, nullptr, &errorDescription);
if (ret != RESULT_OK) {
*ostream << "ERR: bad definition: " << errorDescription;
return RESULT_OK;
}
deque<Message*> messages;
m_newlyDefinedMessages->findAll("", "", levels, false, true, false, false, true, false, 0, 0, false, &messages);
if (messages.empty()) {
*ostream << "ERR: bad definition: no read message";
return RESULT_OK;
}
message = *messages.begin();
} else {
name = args[argPos];
message = m_messages->find(circuit, name, levels, false);
}
// adjust poll priority
if (!newDefinition && message != nullptr && pollPriority > 0 && message->setPollPriority(pollPriority)) {
m_messages->addPollMessage(false, message);
}
bool allowCache = !newDefinition && srcAddress == SYN && dstAddress == SYN && maxAge > 0 && params.length() == 0;
Message* cacheMessage = allowCache ? m_messages->find(circuit, name, levels, false, true) : nullptr;
bool hasCache = cacheMessage != nullptr;
if (!hasCache || (allowCache && message && message->getLastUpdateTime() > cacheMessage->getLastUpdateTime())) {
cacheMessage = message; // message is newer/better
}
if (cacheMessage && (cacheMessage->getLastUpdateTime() + maxAge > now
|| (cacheMessage->isPassive() && cacheMessage->getLastUpdateTime() != 0))) {
if (verbosity & OF_NAMES) {
*ostream << cacheMessage->getCircuit() << " " << cacheMessage->getName() << " ";
}
ret = cacheMessage->decodeLastData(false, fieldIndex == -2 ? nullptr : fieldName.c_str(), fieldIndex, verbosity,
ostream);
if (ret != RESULT_OK) {
if (ret < RESULT_OK) {
logError(lf_main, "read %s %s cached: %s", cacheMessage->getCircuit().c_str(),
cacheMessage->getName().c_str(), getResultCode(ret));
}
return ret;
}
logInfo(lf_main, "read %s %s cached: %s", cacheMessage->getCircuit().c_str(), cacheMessage->getName().c_str(),
ostream->str().c_str());
return RESULT_OK;
}
if (!message && hasCache) {
*ostream << "ERR: no data stored";
return RESULT_OK;
} // else: read directly from bus
if (message == nullptr) {
return RESULT_ERR_NOTFOUND;
}
if (message->getDstAddress() == SYN && dstAddress == SYN) {
return RESULT_ERR_INVALID_ADDR;
}
// read directly from bus
ret = m_busHandler->readFromBus(message, params, dstAddress, srcAddress);
if (ret != RESULT_OK) {
return ret;
}
if (verbosity & OF_NAMES) {
*ostream << message->getCircuit() << " " << message->getName() << " ";
}
ret = message->decodeLastData(false, false, fieldIndex == -2 ? nullptr : fieldName.c_str(), fieldIndex, verbosity,
ostream);
if (ret < RESULT_OK) {
logError(lf_main, "read %s %s: decode %s", message->getCircuit().c_str(), message->getName().c_str(),
getResultCode(ret));
ostream->str("");
*ostream << getResultCode(ret) << " in decode";
return RESULT_OK;
}
if (ret > RESULT_OK) {
return ret;
}
logInfo(lf_main, "read %s %s: %s", message->getCircuit().c_str(), message->getName().c_str(), ostream->str().c_str());
return ret;
}
result_t MainLoop::executeWrite(const vector<string>& args, const string levels, ostringstream* ostream) {
size_t argPos = 1;
bool hex = false, newDefinition = false;
string circuit;
symbol_t srcAddress = SYN, dstAddress = SYN;
while (args.size() > argPos && args[argPos][0] == '-') {
if (args[argPos] == "-h") {
hex = true;
} else if (args[argPos] == "-def") {
if (!m_newlyDefinedMessages) {
*ostream << "ERR: option not enabled";
return RESULT_OK;
}
newDefinition = true;
} else if (args[argPos] == "-s" || args[argPos] == "-d") {
bool dest = args[argPos] == "-d";
argPos++;
if (argPos >= args.size()) {
argPos = 0; // print usage
break;
}
result_t ret;
symbol_t address = (symbol_t)parseInt(args[argPos].c_str(), 16, 0, 0xff, &ret);
if (ret != RESULT_OK || !isValidAddress(address, dest) || (!dest && !isMaster(address))) {
return RESULT_ERR_INVALID_ADDR; // deny send from slave address
}
if (dest) {
dstAddress = address;
} else {
srcAddress = address == m_address ? SYN : address;
}
} else if (args[argPos] == "-c") {
argPos++;
if (argPos >= args.size()) {
argPos = 0; // print usage
break;
}
circuit = args[argPos];
} else {
argPos = 0; // print usage
break;
}
argPos++;
}
if ((args.size() < argPos + 1)
|| (hex && (newDefinition || dstAddress != SYN))
|| (newDefinition && (hex || !circuit.empty() || args.size() > argPos + 2))
|| (!newDefinition && !hex && (circuit.empty() || args.size() > argPos + 2))) {
argPos = 0; // print usage
}
if (argPos == 0) {
*ostream << "usage: write [-s QQ] [-d ZZ] -c CIRCUIT NAME [VALUE[;VALUE]*]\n"
" or: write [-s QQ] [-d ZZ] -def DEFINITION [VALUE[;VALUE]*] (only if enabled)\n"
" or: write [-s QQ] [-c CIRCUIT] -h ZZPBSBNN[DD]*\n"
" Write value(s) or hex message.\n"
" -s QQ override source address QQ\n"
" -d ZZ override destination address ZZ\n"
" -c CIRCUIT CIRCUIT of the message to send\n"
" NAME NAME of the message to send\n"
" VALUE a single field VALUE\n"
" -def write with explicit message definition (only if enabled):\n"
" DEFINITION message definition to use instead of known definition\n"
" -h send hex write message:\n"
" ZZ destination address\n"
" PB SB primary/secondary command byte\n"
" NN number of following data bytes\n"
" DD data byte(s) to send";
return RESULT_OK;
}
if (hex && argPos > 0) {
MasterSymbolString master;
result_t ret = parseHexMaster(args, argPos, srcAddress, false, &master);
if (ret != RESULT_OK) {
return ret;
}
logNotice(lf_main, "write hex cmd: %s", master.getStr().c_str());
// find message
Message* message = m_messages->find(master, false, false, true, false);
if (message == nullptr) {
return RESULT_ERR_NOTFOUND;
}
if (!message->hasLevel(levels)) {
return RESULT_ERR_NOTAUTHORIZED;
}
if (!message->isWrite()) {
return RESULT_ERR_INVALID_ARG;
}
if (!circuit.empty() && circuit != message->getCircuit()) {
return RESULT_ERR_INVALID_ARG; // non-matching circuit
}
// send message
SlaveSymbolString slave;
ret = m_protocol->sendAndWait(master, &slave);
if (ret == RESULT_OK) {
// also update read messages
ret = message->storeLastData(master, slave);
ostringstream result;
if (ret == RESULT_OK) {
ret = message->decodeLastData(false, nullptr, -1, OF_NONE, &result);
}
if (ret >= RESULT_OK) {
logInfo(lf_main, "write hex %s %s cache update: %s", message->getCircuit().c_str(),
message->getName().c_str(), result.str().c_str());
} else {
logError(lf_main, "write hex %s %s cache update: %s", message->getCircuit().c_str(),
message->getName().c_str(), getResultCode(ret));
}
if (master[1] == BROADCAST) {
*ostream << "done broadcast";
return RESULT_OK;
}
if (isMaster(master[1])) {
return RESULT_OK;
}
*ostream << slave.getStr();
return RESULT_OK;
}
logError(lf_main, "write hex %s %s: %s", message->getCircuit().c_str(), message->getName().c_str(),
getResultCode(ret));
return ret;
}
Message* message;
result_t ret;
if (newDefinition) {
time_t now;
time(&now);
string errorDescription;
istringstream defstr("#\n" + args[argPos]); // ensure first line is not used for determining col names
m_newlyDefinedMessages->clear();
ret = m_newlyDefinedMessages->readFromStream(&defstr, "temporary", now, true, nullptr, &errorDescription);
if (ret != RESULT_OK) {
*ostream << "ERR: bad definition: " << errorDescription;
return RESULT_OK;
}
deque<Message*> messages;
m_newlyDefinedMessages->findAll("", "", levels, false, false, true, false, true, false, 0, 0, false, &messages);
if (messages.empty()) {
*ostream << "ERR: bad definition: no write message";
return RESULT_OK;
}
message = *messages.begin();
} else {
message = m_messages->find(circuit, args[argPos], levels, true);
}
if (message == nullptr) {
return RESULT_ERR_NOTFOUND;
}
if (message->getDstAddress() == SYN && dstAddress == SYN) {
return RESULT_ERR_INVALID_ADDR;
}
// allow missing values
ret = m_busHandler->readFromBus(message, args.size() == argPos + 1 ? "" : args[argPos + 1], dstAddress, srcAddress);
if (ret != RESULT_OK) {
logError(lf_main, "write %s %s: %s", message->getCircuit().c_str(), message->getName().c_str(),
getResultCode(ret));
return ret;
}
dstAddress = message->getLastMasterData().dataAt(1);
if (dstAddress == BROADCAST || isMaster(dstAddress)) {
logNotice(lf_main, "write %s %s: %s", message->getCircuit().c_str(), message->getName().c_str(),
getResultCode(ret));
if (dstAddress == BROADCAST) {
*ostream << "done broadcast";
}
return RESULT_OK;
}
ret = message->decodeLastData(false, false, nullptr, -1, OF_NONE, ostream); // decode data
if (ret >= RESULT_OK && ostream->str().empty()) {
logNotice(lf_main, "write %s %s: decode %s", message->getCircuit().c_str(), message->getName().c_str(),
getResultCode(ret));
return RESULT_OK;
}
if (ret != RESULT_OK) {
logError(lf_main, "write %s %s: decode %s", message->getCircuit().c_str(), message->getName().c_str(),
getResultCode(ret));
ostream->str("");
*ostream << getResultCode(ret) << " in decode";
return RESULT_OK;
}
logNotice(lf_main, "write %s %s: %s", message->getCircuit().c_str(), message->getName().c_str(),
ostream->str().c_str());
return RESULT_OK;
}
result_t MainLoop::parseHexAndSend(const vector<string>& args, size_t& argPos, bool isDirectMode,
ostringstream* ostream) {
symbol_t srcAddress = SYN;
bool autoLength = false;
while (args.size() > argPos && args[argPos][0] == '-') {
if (args[argPos] == "-s" && argPos + 1 < args.size()) {
result_t ret;
argPos++;
symbol_t address = (symbol_t)parseInt(args[argPos].c_str(), 16, 0, 0xff, &ret);
if (ret != RESULT_OK || !isValidAddress(address, false) || !isMaster(address)) {
return RESULT_ERR_INVALID_ADDR;
}
srcAddress = address == m_address ? SYN : address;
} else if (args[argPos] == "-n") {
autoLength = true;
} else {
argPos = 0; // print usage
return RESULT_OK;
}
argPos++;
}
if (argPos >= args.size()) {
argPos = 0; // print usage
return RESULT_OK;
}
MasterSymbolString master;
result_t ret = parseHexMaster(args, argPos, srcAddress, autoLength, &master);
argPos = args.size(); // mark as successfully parsed
if (ret != RESULT_OK) {
return ret;
}
logNotice(lf_main, isDirectMode ? "direct cmd: %s" : "hex cmd: %s", master.getStr().c_str());
// send message
SlaveSymbolString slave;
ret = m_protocol->sendAndWait(master, &slave);
if (ret == RESULT_OK) {
if (master[1] == BROADCAST) {
*ostream << "done broadcast";
return RESULT_OK;
}
if (isMaster(master[1])) {
*ostream << "done";
return RESULT_OK;
}
*ostream << slave.getStr();
return RESULT_OK;
}
logError(lf_main, isDirectMode ? "direct: %s" : "hex: %s", getResultCode(ret));
return ret;
}
result_t MainLoop::executeHex(const vector<string>& args, ostringstream* ostream) {
size_t argPos = 1;
result_t ret = parseHexAndSend(args, argPos, false, ostream);
if (argPos != 0 && argPos == args.size()) {
return ret;
}
*ostream << "usage: hex [-s QQ] ZZPBSBNN[DD]*\n"
" or: hex [-s QQ] -n ZZPBSB[DD]*\n"
" Send arbitrary data in hex.\n"
" -s QQ override source address QQ\n"
" -n automatically determine the number of data bytes\n"
" ZZ destination address\n"
" PB SB primary/secondary command byte\n"
" NN number of following data bytes\n"
" DD data byte(s) to send";
return RESULT_OK;
}
result_t MainLoop::executeDirect(const vector<string>& args, RequestMode* reqMode, ostringstream* ostream) {
if (reqMode->listenMode != lm_direct) {
if (args.size() == 1) {
reqMode->listenMode = lm_direct;
m_busHandler->enableGrab(true); // needed for listening to all messages
*ostream << "direct mode started";
return RESULT_OK;
}
*ostream << "usage: direct\n"
" Enter direct mode.";
return RESULT_OK;
}
if (args.size() > 0) {
string firstArg = args[0];
if (firstArg == "stop") {
reqMode->listenMode = lm_none;
*ostream << "direct mode stopped";
return RESULT_OK;
}
if (firstArg != "") {
for (size_t argPos = 0; argPos < args.size(); argPos++) {
if (argPos > 0) {
*ostream << " ";
}
*ostream << args[argPos];
}
*ostream << ":";
if (!m_enableHex) {
*ostream << "ERR: hex command not enabled";
return RESULT_OK;
}
size_t argPos = 0;
result_t ret = parseHexAndSend(args, argPos, true, ostream);
if (ret == RESULT_OK && argPos != args.size()) {
ret = RESULT_ERR_INVALID_ARG;
}
return ret;
}
}
*ostream << "usage: [-s QQ] ZZPBSBNN[DD]*\n"
" or: [-s QQ] -n ZZPBSB[DD]*\n"
" or: stop\n"
" Send arbitrary data in hex (only if enabled) or stop direct mode.\n"
" -s QQ override source address QQ\n"
" -n automatically determine the number of data bytes\n"
" ZZ destination address\n"
" PB SB primary/secondary command byte\n"
" NN number of following data bytes\n"
" DD data byte(s) to send";
return RESULT_OK;
}
result_t MainLoop::executeFind(const vector<string>& args, const string& levels, ostringstream* ostream) {
size_t argPos = 1;
bool configFormat = false, exact = false, withRead = true, withWrite = false, withPassive = true, first = true,
onlyWithData = false, hexFormat = false, userLevel = true, withConditions = false;
string useLevels = levels;
OutputFormat verbosity = OF_NONE;
vector<string> fieldNames;
string circuit;
vector<symbol_t> id;
while (args.size() > argPos && args[argPos][0] == '-') {
if (args[argPos] == "-v") {
if ((verbosity & VERBOSITY_4) == VERBOSITY_0) {
verbosity |= VERBOSITY_1;
} else if ((verbosity & VERBOSITY_4) == VERBOSITY_1) {
verbosity |= VERBOSITY_2;
} else if ((verbosity & VERBOSITY_4) == VERBOSITY_2) {
verbosity |= VERBOSITY_3;
} else {
verbosity |= VERBOSITY_4;
}
} else if (args[argPos] == "-vv") {
verbosity |= VERBOSITY_2;
} else if (args[argPos] == "-vvv") {
verbosity |= VERBOSITY_3;
} else if (args[argPos] == "-vvvv" || args[argPos] == "-V") {
verbosity |= VERBOSITY_4;
} else if (args[argPos] == "-f") {
configFormat = true;
if (hexFormat) {
argPos = 0; // print usage
break;
}
} else if (args[argPos] == "-F") {
argPos++;
if (hexFormat || (argPos >= args.size())) {
argPos = 0; // print usage
break;
}
if (!Message::extractFieldNames(args[argPos], true, &fieldNames)) {
argPos = 0; // print usage
break;
}
} else if (args[argPos] == "-e") {
exact = true;
} else if (args[argPos] == "-r") {
if (first) {
first = false;
withWrite = withPassive = false;
}
withRead = true;
} else if (args[argPos] == "-w") {
if (first) {
first = false;
withRead = withPassive = false;
}
withWrite = true;
} else if (args[argPos] == "-p") {
if (first) {
first = false;
withRead = withWrite = false;
}
withPassive = true;
} else if (args[argPos] == "-a") {
withRead = withWrite = withPassive = withConditions = true;
} else if (args[argPos] == "-d") {
onlyWithData = true;
} else if (args[argPos] == "-h") {
hexFormat = true;
if (configFormat) {
argPos = 0; // print usage
break;
}
} else if (args[argPos] == "-i") {
argPos++;
if (argPos >= args.size() || !id.empty()) {
argPos = 0; // print usage
break;
}
result_t result = Message::parseId(args[argPos], &id);
if (result != RESULT_OK) {
return result;
}
if (id.empty()) {
argPos = 0; // print usage
break;
}
} else if (args[argPos] == "-c") {
argPos++;
if (argPos >= args.size()) {
argPos = 0; // print usage
break;
}
circuit = args[argPos];
} else if (args[argPos] == "-l") {
argPos++;
if (argPos >= args.size()) {
argPos = 0; // print usage
break;
}
useLevels = args[argPos];
userLevel = false;
} else {
argPos = 0; // print usage
break;
}
argPos++;
}
if (argPos == 0 || args.size() < argPos || args.size() > argPos + 1) {
*ostream <<
"usage: find [-v|-V] [-r] [-w] [-p] [-a] [-d] [-h] [-i ID] [-f] [-F COL[,COL]*] [-e] [-c CIRCUIT]"
" [-l LEVEL] [NAME]\n"
" Find message(s).\n"
" -v increase verbosity (include names/units/comments+destination address+update time)\n"
" -V be very verbose (include everything)\n"
" -r limit to active read messages (default: read + passive)\n"
" -w limit to active write messages (default: read + passive)\n"
" -p limit to passive messages (default: read + passive)\n"
" -a include all message types (read, passive, and write) and all conditional\n"
" -d only include messages with actual data\n"
" -h show hex data instead of decoded values\n"
" -i ID limit to messages with ID (in hex, PB, SB and further ID bytes)\n"
" -f list messages in CSV configuration file format (including conditions with '-a')\n"
" -F COL[,COL]* list messages in the specified format (including conditions with '-a')\n"
" (COL: type|circuit|level|name|comment|qq|zz|pbsb|id|fields or custom fields)\n"
" -e match NAME and optional CIRCUIT exactly (ignoring case)\n"
" -c CIRCUIT limit to messages of CIRCUIT (or a part thereof without '-e')\n"
" -l LEVEL limit to messages with access LEVEL (\"*\" for any, default: current level)\n"
" NAME NAME of the messages to find (or a part thereof without '-e')";
return RESULT_OK;
}
deque<Message*> messages;
m_messages->findAll(circuit, args.size() == argPos ? "" : args[argPos], useLevels,
exact, withRead, withWrite, withPassive, userLevel, !withConditions, 0, 0, false, &messages);
bool found = false;
char str[32];
for (const auto message : messages) {
if (!id.empty() && !message->checkIdPrefix(id)) {
continue;
}
time_t lastup = message->getLastUpdateTime();
if (onlyWithData && lastup == 0) {
continue;
}
if (configFormat) {
if (found) {
*ostream << endl;
}
message->dump(nullptr, withConditions, verbosity, ostream);
} else if (!fieldNames.empty()) {
if (found) {
*ostream << endl;
}
message->dump(&fieldNames, withConditions, verbosity, ostream);
} else {
if (found) {
*ostream << endl;
}
*ostream << message->getCircuit() << " " << message->getName() << " = ";
if (lastup == 0) {
*ostream << "no data stored";
if (!message->isAvailable()) {
*ostream << " (message not available due to condition)";
}
} else if (hexFormat) {
*ostream << message->getLastMasterData().getStr() << " / " << message->getLastSlaveData().getStr();
} else {
result_t ret = message->decodeLastData(false, nullptr, -1, verbosity, ostream);
if (ret != RESULT_OK) {
*ostream << " (" << getResultCode(ret)
<< " for " << message->getLastMasterData().getStr()
<< " / " << message->getLastSlaveData().getStr() << ")";
}
}
if ((verbosity & VERBOSITY_3) == VERBOSITY_3) {
symbol_t dstAddress = message->getDstAddress();
if (dstAddress != SYN) {
snprintf(str, sizeof(str), "%02x", dstAddress);
} else if (lastup != 0 && message->getLastMasterData().size() > 1) {
snprintf(str, sizeof(str), "%02x", message->getLastMasterData().dataAt(1));
} else {
snprintf(str, sizeof(str), "any");
}
if (lastup != 0) {
struct tm td;
localtime_r(&lastup, &td);
size_t len = strlen(str);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-truncation"
snprintf(str+len, sizeof(str)-len, ", lastup=%04d-%02d-%02d %02d:%02d:%02d",
td.tm_year+1900, td.tm_mon+1, td.tm_mday,
td.tm_hour, td.tm_min, td.tm_sec);
#pragma GCC diagnostic pop
}
*ostream << " [ZZ=" << str;
if (message->isPassive()) {
*ostream << ", passive";
} else {
*ostream << ", active";
}
if (message->isWrite()) {
*ostream << " write]";
} else {
*ostream << " read]";
}
}
}
found = true;
}
if (!found) {
return RESULT_ERR_NOTFOUND;
}
return RESULT_OK;
}
result_t MainLoop::executeListen(const vector<string>& args, RequestMode* reqMode, ostringstream* ostream) {
size_t argPos = 1;
OutputFormat verbosity = OF_NONE;
bool listenWithUnknown = false;
bool listenOnlyUnknown = false;
while (args.size() > argPos && args[argPos][0] == '-') {
if (args[argPos] == "-v") {
if ((verbosity & VERBOSITY_3) == VERBOSITY_0) {
verbosity |= VERBOSITY_1;
} else if ((verbosity & VERBOSITY_3) == VERBOSITY_1) {
verbosity |= VERBOSITY_2;
} else {
verbosity |= VERBOSITY_3;
}
} else if (args[argPos] == "-vv") {
verbosity |= VERBOSITY_2;
} else if (args[argPos] == "-vvv" || args[argPos] == "-V") {
verbosity |= VERBOSITY_3;
} else if (args[argPos] == "-n") {
verbosity = (verbosity & ~OF_VALUENAME) | OF_NUMERIC;
} else if (args[argPos] == "-N") {
verbosity = (verbosity & ~OF_NUMERIC) | OF_VALUENAME;
} else if (args[argPos] == "-u") {
listenWithUnknown = true;
listenOnlyUnknown = false;
} else if (args[argPos] == "-U") {
listenOnlyUnknown = true;
} else {
argPos = 0; // print usage
break;
}
argPos++;
}
if (argPos > 0 && args.size() == argPos) {
reqMode->format = verbosity;
reqMode->listenWithUnknown = listenWithUnknown;
reqMode->listenOnlyUnknown = listenOnlyUnknown;
if (listenWithUnknown || listenOnlyUnknown) {
m_busHandler->enableGrab(true); // needed for listening to all messages
}
if (reqMode->listenMode == lm_listen) {
*ostream << "listen continued";
return RESULT_OK;
}
reqMode->listenMode = lm_listen;
*ostream << "listen started";
return RESULT_OK;
}
if (argPos == 0 || args.size() != argPos + 1 || args[argPos] != "stop") {
*ostream << "usage: listen [-v|-V] [-n|-N] [-u|-U] [stop]\n"
" Listen for updates or stop it.\n"
" -v increase verbosity (include names/units/comments)\n"
" -V be very verbose (include names, units, and comments)\n"
" -n use numeric value of value=name pairs\n"
" -N use numeric and named value of value=name pairs\n"
" -u include unknown messages\n"
" -U only show unknown messages";
return RESULT_OK;
}
reqMode->listenMode = lm_none;
*ostream << "listen stopped";
return RESULT_OK;
}
result_t MainLoop::executeState(const vector<string>& args, ostringstream* ostream) {
if (args.size() == 0) {
*ostream << "usage: state\n"
" Report bus state.";
return RESULT_OK;
}
if (m_protocol->hasSignal()) {
*ostream << "signal acquired, "
<< m_protocol->getSymbolRate() << " symbols/sec ("
<< m_protocol->getMaxSymbolRate() << " max), "
<< m_protocol->getMasterCount() << " masters";
return RESULT_OK;
}
return RESULT_ERR_NO_SIGNAL;
}
result_t MainLoop::executeGrab(const vector<string>& args, ostringstream* ostream) {
if (args.size() == 1) {
*ostream << (m_busHandler->enableGrab(true) ? "grab started" : "grab continued");
return RESULT_OK;
}
if (args.size() == 2 && args[1] == "stop") {
*ostream << (m_busHandler->enableGrab(false) ? "grab stopped" : "grab not running");
return RESULT_OK;
}
if (args.size() >= 2 && args[1] == "result") {
bool onlyUnknown = true;
bool decode = false;
bool invalid = false;
for (size_t i = args.size() - 1; i >= 2; i--) {
string arg = args[i];
if (arg == "all") {
onlyUnknown = false;
} else if (arg == "decode") {
decode = true;
} else {
invalid = true;
break;
}
}
if (!invalid) {
m_busHandler->formatGrabResult(onlyUnknown, decode ? OF_DEFINITION : OF_NONE, ostream);
return RESULT_OK;
}
}
*ostream << "usage: grab [stop]\n"
" or: grab result [all|decode]*\n"
" Start or stop grabbing, or report/decode unknown or all grabbed messages.";
return RESULT_OK;
}
result_t MainLoop::executeDefine(const vector<string>& args, ostringstream* ostream) {
size_t argPos = 1;
bool replace = false;
while (args.size() > argPos && args[argPos][0] == '-') {
if (args[argPos] == "-r") {
replace = true;
} else {
argPos = 0; // print usage
break;
}
argPos++;
}
if (argPos == 0 || args.size() != argPos + 1) {
*ostream <<
"usage: define [-r] DEFINITION\n"
" Define a new message.\n"
" -r replace an already existing definition\n"
" DEFINITION message definition to add";
return RESULT_OK;
}
time_t now;
time(&now);
string errorDescription;
istringstream defstr("#\n" + args[argPos]); // ensure first line is not used for determining col names
return m_messages->readFromStream(&defstr, "tcp", now, true, nullptr, &errorDescription, replace);
}
result_t MainLoop::executeDecode(const vector<string>& args, ostringstream* ostream) {
size_t argPos = 1;
OutputFormat verbosity = OF_NONE;
while (args.size() > argPos && args[argPos][0] == '-') {
if (args[argPos] == "-v") {
if ((verbosity & VERBOSITY_3) == VERBOSITY_0) {
verbosity |= VERBOSITY_1;
} else if ((verbosity & VERBOSITY_3) == VERBOSITY_1) {
verbosity |= VERBOSITY_2;
} else {
verbosity |= VERBOSITY_3;
}
} else if (args[argPos] == "-vv") {
verbosity |= VERBOSITY_2;
} else if (args[argPos] == "-vvv" || args[argPos] == "-V") {
verbosity |= VERBOSITY_3;
} else if (args[argPos] == "-n") {
verbosity = (verbosity & ~OF_VALUENAME) | OF_NUMERIC;
} else if (args[argPos] == "-N") {
verbosity = (verbosity & ~OF_NUMERIC) | OF_VALUENAME;
} else {
argPos = 0; // print usage
break;
}
argPos++;
}
if (args.size() < argPos + 2) {
argPos = 0; // print usage
}
if (argPos == 0 || args.size() != argPos + 2) {
*ostream <<
"usage: decode [-v|-V] [-n|-N] DEFINITION DD[DD]*\n"
" Decode field(s) by definition and hex data.\n"
" -v increase verbosity (include names/units/comments)\n"
" -V be very verbose (include names, units, and comments)\n"
" -n use numeric value of value=name pairs\n"
" -N use numeric and named value of value=name pairs\n"
" DEFINITION field definition (type,divisor/values,unit,comment,...)\n"
" DD data byte(s) to decode";
return RESULT_OK;
}
time_t now;
time(&now);
istringstream defstr("#\n" + args[argPos]); // ensure first line is not used for determining col names
string errorDescription;
DataFieldTemplates* templates = m_scanHelper->getTemplates("*");
LoadableDataFieldSet fields("", templates);
result_t ret = fields.readFromStream(&defstr, "temporary", now, true, nullptr, &errorDescription);
if (ret != RESULT_OK) {
return ret;
}
SlaveSymbolString slave;
slave.push_back(0); // dummy length
ret = slave.parseHex(args[argPos+1]);
if (ret != RESULT_OK) {
return ret;
}
slave.adjustHeader();
return fields.read(slave, 0, false, nullptr, -1, verbosity, -1, ostream);
}
result_t MainLoop::executeEncode(const vector<string>& args, ostringstream* ostream) {
size_t argPos = 1;
if (argPos == 0 || args.size() != argPos + 2) {
*ostream <<
"usage: encode DEFINITION VALUE[;VALUE]*\n"
" Encode field(s) by definition and decoded value(s).\n"
" DEFINITION field definition (type,divisor/values,unit,comment,...)\n"
" VALUE single field VALUE to encode";
return RESULT_OK;
}
time_t now;
time(&now);
istringstream defstr("#\n" + args[argPos]); // ensure first line is not used for determining col names
string errorDescription;
DataFieldTemplates* templates = m_scanHelper->getTemplates("*");
LoadableDataFieldSet fields("", templates);
result_t ret = fields.readFromStream(&defstr, "temporary", now, true, nullptr, &errorDescription);
if (ret != RESULT_OK) {
return ret;
}
istringstream datastr(args[argPos+1]);
SlaveSymbolString slave;
ret = fields.write(UI_FIELD_SEPARATOR, 0, &datastr, &slave, nullptr);
if (ret != RESULT_OK) {
return ret;
}
*ostream << slave.getStr(1);
return ret;
}
result_t MainLoop::executeScan(const vector<string>& args, const string& levels, ostringstream* ostream) {
if (args.size() == 1) {
result_t result = m_busHandler->startScan(false, levels);
if (result == RESULT_ERR_DUPLICATE) {
*ostream << "ERR: scan already running";
return RESULT_OK;
}
if (result != RESULT_OK) {
logError(lf_main, "scan: %s", getResultCode(result));
}
return result;
}
if (args.size() == 2) {
if (args[1] == "full") {
result_t result = m_busHandler->startScan(true, levels);
if (result != RESULT_OK) {
logError(lf_main, "full scan: %s", getResultCode(result));
}
return result;
}
if (args[1] == "result") {
m_busHandler->formatScanResult(ostream);
return RESULT_OK;
}
if (args[1] == "status") {
switch (m_scanStatus) {
case SCAN_STATUS_RUNNING:
*ostream << "running";
break;
case SCAN_STATUS_FINISHED:
*ostream << "finished";
break;
default:
*ostream << "unused";
break;
}
unsigned int running = m_busHandler->getRunningScans();
if (running > 0) {
*ostream << ", some messages pending";
}
return RESULT_OK;
}
if (!m_protocol->hasSignal()) {
return RESULT_ERR_NO_SIGNAL;
}
result_t result;
symbol_t dstAddress = (symbol_t)parseInt(args[1].c_str(), 16, 0, 0xff, &result);
if (result == RESULT_OK && !isValidAddress(dstAddress, false)) {
result = RESULT_ERR_INVALID_ADDR;
}
if (result != RESULT_OK) {
return result;
}
result = m_busHandler->scanAndWait(dstAddress);
if (result != RESULT_OK) {
return result;
}
if (!m_busHandler->formatScanResult(dstAddress, false, ostream)) {
return RESULT_EMPTY;
}
return RESULT_OK;
}
*ostream << "usage: scan [full|ZZ]\n"
" or: scan result\n"
" or: scan status\n"
" Scan seen slaves, all slaves (full), a single slave (address ZZ), or report scan result or status.";
return RESULT_OK;
}
result_t MainLoop::executeLog(const vector<string>& args, ostringstream* ostream) {
if (args.size() == 1) {
for (int val = 0; val < lf_COUNT; val++) {
LogFacility facility = (LogFacility)val;
*ostream << getLogFacilityStr(facility) << ": " << getLogLevelStr(getFacilityLogLevel(facility)) << "\n";
}
return RESULT_OK;
}
if (args.size() != 3) {
*ostream << "usage: log [AREA[,AREA]* LEVEL]\n"
" Set log level for the specified area(s) or get current settings.\n"
" AREA the area to set the level for (main|network|bus|update|other|all)\n"
" LEVEL the log level to set (error|notice|info|debug)";
return RESULT_OK;
}
int facilities = parseLogFacilities(args[1].c_str());
LogLevel level = parseLogLevel(args[2].c_str());
if (facilities != -1 && level != ll_COUNT) {
if (setFacilitiesLogLevel(facilities, level)) {
return RESULT_OK;
}
*ostream << "same";
return RESULT_OK;
}
return RESULT_ERR_INVALID_ARG;
}
result_t MainLoop::executeRaw(const vector<string>& args, ostringstream* ostream) {
bool bytes = args.size() == 2 && args[1] == "bytes";
if (args.size() != 1 && !bytes) {
*ostream << "usage: raw [bytes]\n"
" Toggle logging of messages or each byte.";
return RESULT_OK;
}
bool enabled;
m_logRawBytes = bytes;
if (m_logRawFile) {
enabled = !m_logRawFile->isEnabled();
m_logRawFile->setEnabled(enabled);
} else {
enabled = !m_logRawEnabled;
m_logRawEnabled = enabled;
}
*ostream << (enabled ? "raw logging enabled" : "raw logging disabled");
return RESULT_OK;
}
result_t MainLoop::executeDump(const vector<string>& args, ostringstream* ostream) {
if (args.size() != 1) {
*ostream << "usage: dump\n"
" Toggle binary dump of received bytes.";
return RESULT_OK;
}
if (!m_dumpFile) {
*ostream << "dump not configured";
return RESULT_OK;
}
bool enabled = !m_dumpFile->isEnabled();
m_dumpFile->setEnabled(enabled);
*ostream << (enabled ? "dump enabled" : "dump disabled");
return RESULT_OK;
}
result_t MainLoop::executeReload(const vector<string>& args, ostringstream* ostream) {
if (args.size() != 1) {
*ostream << "usage: reload\n"
" Reload CSV config files.";
return RESULT_OK;
}
m_busHandler->clear();
m_scanHelper->loadConfigFiles(!m_scanConfig);
return RESULT_OK;
}
result_t MainLoop::executeInfo(const vector<string>& args, const string& user, ostringstream* ostream) {
bool verbose = args.size() == 2 && args[1] == "verbose";
if (args.size() != 1 && !verbose) {
*ostream << "usage: info [verbose]\n"
" Report information about the daemon, configuration, seen participants, and the device.";
return RESULT_OK;
}
*ostream << "version: " << PACKAGE_STRING "." REVISION "\n";
if (!m_updateCheck.empty()) {
*ostream << "update check: " << m_updateCheck << "\n";
}
*ostream << "device: ";
m_protocol->formatInfo(ostream, verbose, false);
*ostream << "\n";
if (!user.empty()) {
*ostream << "user: " << user << "\n";
}
string levels = getUserLevels(user);
if (!user.empty() || !levels.empty()) {
*ostream << "access: " << levels << "\n";
}
if (m_protocol->hasSignal()) {
*ostream << "signal: acquired\n"
<< "symbol rate: " << m_protocol->getSymbolRate() << "\n"
<< "max symbol rate: " << m_protocol->getMaxSymbolRate() << "\n";
if (m_protocol->getMinArbitrationDelay() >= 0) {
*ostream << "min arbitration micros: " << m_protocol->getMinArbitrationDelay() << "\n"
<< "max arbitration micros: " << m_protocol->getMaxArbitrationDelay() << "\n";
}
if (m_protocol->getMinSymbolLatency() >= 0) {
*ostream << "min symbol latency: " << m_protocol->getMinSymbolLatency() << "\n"
<< "max symbol latency: " << m_protocol->getMaxSymbolLatency() << "\n";
}
if (m_scanStatus != SCAN_STATUS_NONE) {
*ostream << "scan: " << (m_scanStatus == SCAN_STATUS_FINISHED ? "finished" : "running");
unsigned int running = m_busHandler->getRunningScans();
if (running > 0) {
*ostream << ", some messages pending";
}
*ostream << "\n";
}
} else {
*ostream << "signal: no signal\n";
}
*ostream << "reconnects: " << m_reconnectCount << "\n"
<< "masters: " << m_protocol->getMasterCount() << "\n"
<< "messages: " << m_messages->size() << "\n"
<< "conditional: " << m_messages->sizeConditional() << "\n"
<< "poll: " << m_messages->sizePoll() << "\n"
<< "update: " << m_messages->sizePassive();
m_busHandler->formatSeenInfo(ostream);
return RESULT_OK;
}
result_t MainLoop::executeQuit(const vector<string>& args, bool *connected, ostringstream* ostream) {
if (args.size() == 1) {
*connected = false;
*ostream << "connection closed";
return RESULT_OK;
}
*ostream << "usage: quit\n"
" Close client connection.";
return RESULT_OK;
}
result_t MainLoop::executeHelp(ostringstream* ostream) {
*ostream << "usage:\n"
" read|r Read value(s): read [-f] [-m SECONDS] [-s QQ] [-d ZZ] [-c CIRCUIT] [-p PRIO] [-v|-V] [-n|-N]"
" [-i VALUE[;VALUE]*] NAME [FIELD[.N]]\n"
" Read by new defintion: read [-f] [-m SECONDS] [-s QQ] [-d ZZ] [-v|-V] [-n|-N]"
" [-i VALUE[;VALUE]*] -def DEFINITION (if enabled)\n"
" Read hex message: read [-f] [-m SECONDS] [-s QQ] [-c CIRCUIT] -h ZZPBSBNN[DD]*\n"
" write|w Write value(s): write [-s QQ] [-d ZZ] -c CIRCUIT NAME [VALUE[;VALUE]*]\n"
" Write by new def.: write [-s QQ] [-d ZZ] -def DEFINITION [VALUE[;VALUE]*] (if enabled)\n"
" Write hex message: write [-s QQ] [-c CIRCUIT] -h ZZPBSBNN[DD]*\n"
" auth|a Authenticate user: auth USER SECRET\n"
" hex Send hex data: hex [-s QQ] [-n] ZZPBSB[NN][DD]* (if enabled)\n"
" find|f Find message(s): find [-v|-V] [-r] [-w] [-p] [-a] [-d] [-h] [-i ID] [-f] [-F COL[,COL]*] [-e]"
" [-c CIRCUIT] [-l LEVEL] [NAME]\n"
" listen|l Listen for updates: listen [-v|-V] [-n|-N] [-u|-U] [stop]\n"
" direct Enter direct mode\n"
" state|s Report bus state\n"
" info|i Report information about the daemon, configuration, seen participants, and the device.\n"
" grab|g Grab messages: grab [stop]\n"
" Report the messages: grab result [all|decode]\n"
" define Define new message: define [-r] DEFINITION\n"
" decode|d Decode field(s): decode [-v|-V] [-n|-N] DEFINITION DD[DD]*\n"
" encode|e Encode field(s): encode DEFINITION VALUE[;VALUE]*\n"
" scan Scan slaves: scan [full|ZZ]\n"
" Report scan result: scan result\n"
" Report scan status: scan status\n"
" log Set log area level: log [AREA[,AREA]* LEVEL]\n"
" raw Toggle logging of messages or each byte.\n"
" dump Toggle binary dump of received bytes\n"
" reload Reload CSV config files\n"
" quit|q Close connection\n"
" help|? Print help help [COMMAND], COMMMAND ?";
return RESULT_OK;
}
/**
* Parse a boolean query value.
* @param value the query value.
* @return the parsed boolean.
*/
bool parseBoolQuery(const string& value) {
return value.length() == 0 || value == "1" || value == "true";
}
result_t MainLoop::executeGet(const vector<string>& args, bool* connected, ostringstream* ostream) {
time_t maxAge = -1;
size_t argPos = 1;
string uri = args[argPos++];
int type = -1;
result_t ret = RESULT_OK;
if (uri.substr(0, 5) == "/data" && (uri.length() == 5 || uri[5] == '/')) {
string circuit, name;
size_t pos = uri.find('/', 6);
if (pos == string::npos) {
circuit = uri.length() == 5 ? "" : uri.substr(6);
} else {
circuit = uri.substr(6, pos - 6);
name = uri.substr(pos + 1);
}
bool required = false, full = false, withWrite = false, raw = false;
bool withDefinition = false;
string newDefinition;
OutputFormat verbosity = OF_NAMES;
time_t since = 0;
size_t pollPriority = 0;
bool exact = false;
string user;
if (args.size() > argPos) {
string secret;
string query = args[argPos];
istringstream stream(query);
string token;
while (getline(stream, token, '&')) {
pos = token.find('=');
string qname, value;
if (pos != string::npos) {
qname = token.substr(0, pos);
value = token.substr(pos + 1);
} else {
qname = token;
}
if (qname == "since") {
since = parseInt(value.c_str(), 10, 0, 0xffffffff, &ret);
} else if (qname == "poll") {
pollPriority = (size_t)parseInt(value.c_str(), 10, 1, 9, &ret);
} else if (qname == "exact") {
exact = parseBoolQuery(value);
} else if (qname == "verbose") {
if (parseBoolQuery(value)) {
verbosity |= OF_UNITS | OF_COMMENTS;
}
} else if (qname == "indexed") {
if (parseBoolQuery(value)) {
verbosity &= ~OF_NAMES;
}
} else if (qname == "numeric") {
if (parseBoolQuery(value)) {
verbosity = (verbosity & ~OF_VALUENAME) | OF_NUMERIC;
}
} else if (qname == "valuename") {
if (parseBoolQuery(value)) {
verbosity = (verbosity & ~OF_NUMERIC) | OF_VALUENAME;
}
} else if (qname == "full") {
full = parseBoolQuery(value);
} else if (qname == "required") {
required = parseBoolQuery(value);
} else if (qname == "maxage") {
maxAge = parseInt(value.c_str(), 10, 0, 24*60*60, &ret);
required = true;
} else if (qname == "write") {
withWrite = parseBoolQuery(value);
} else if (qname == "raw") {
raw = parseBoolQuery(value);
} else if (qname == "def") {
withDefinition = parseBoolQuery(value);
} else if (qname == "define") {
if (!m_newlyDefinedMessages || circuit.empty() || name.empty() || value.empty()) {
ret = RESULT_ERR_INVALID_ARG;
break;
}
size_t comma = value.find(',');
if (comma == string::npos || comma == 0
|| value.find(circuit+","+name+",") != comma+1) { // ensure same circuit+name
ret = RESULT_ERR_INVALID_ARG;
break;
}
newDefinition = value;
} else if (qname == "user") {
user = value;
} else if (qname == "secret") {
secret = value;
}
if (ret != RESULT_OK) {
break;
}
}
if ((!user.empty() || !secret.empty()) && !m_userList.checkSecret(user, secret)) {
ret = RESULT_ERR_NOTAUTHORIZED;
}
}
*ostream << "{";
string lastCircuit;
time_t now;
time(&now);
time_t maxLastUp = 0;
if (ret == RESULT_OK && !newDefinition.empty()) {
string errorDescription;
istringstream defstr("#\n" + newDefinition); // ensure first line is not used for determining col names
ret = m_messages->readFromStream(&defstr, "http", now, true, nullptr, &errorDescription, true);
}
if (ret == RESULT_OK) {
bool first = true;
verbosity |= OF_JSON | (full ? OF_ALL_ATTRS : OF_NONE) | (withDefinition ? OF_DEFINITION : OF_NONE);
deque<Message*> messages;
m_messages->findAll(circuit, name, getUserLevels(user), exact, true, withWrite, true, true, true, 0, 0, false,
&messages);
string lastName;
for (deque<Message*>::iterator it = messages.begin(); it != messages.end(); it++) {
Message* message = *it;
symbol_t dstAddress = message->getDstAddress();
if (dstAddress == SYN) {
continue;
}
if (pollPriority > 0 && message->setPollPriority(pollPriority)) {
m_messages->addPollMessage(false, message);
}
time_t lastup = message->getLastUpdateTime();
if (required && (lastup == 0 || (maxAge >=0 && lastup + maxAge <= now))) {
// read directly from bus
if (message->isPassive()) {
continue; // not possible to actively read this message
}
if (m_busHandler->readFromBus(message, "") != RESULT_OK) {
continue;
}
} else {
if (since > 0 && lastup <= since) {
continue;
}
if (lastup > maxLastUp) {
maxLastUp = lastup;
}
}
bool sameCircuit = message->getCircuit() == lastCircuit;
if (!sameCircuit) {
if (lastCircuit.length() > 0) {
*ostream << "\n }\n },";
}
lastCircuit = message->getCircuit();
*ostream << "\n \"" << lastCircuit << "\": {";
if (full && m_messages->decodeCircuit(lastCircuit, verbosity, ostream)) { // add circuit specific values
*ostream << ",";
}
*ostream << "\n \"messages\": {";
lastName = "";
first = true;
}
name = message->getName();
bool same = sameCircuit && name == lastName;
if (!same && it+1 != messages.end()) {
Message* next = *(it+1);
same = next->getCircuit() == lastCircuit && next->getName() == name;
}
message->decodeJson(!first, same, true, raw, verbosity, ostream);
lastName = name;
first = false;
}
if (lastCircuit.length() > 0) {
*ostream << "\n }\n },";
}
*ostream << "\n \"global\": {"
<< "\n \"version\": \"" << PACKAGE_VERSION "." REVISION "\"" << setw(0) << dec;
if (!m_updateCheck.empty()) {
*ostream << ",\n \"updatecheck\": \"" << m_updateCheck << "\"";
}
if (!user.empty()) {
*ostream << ",\n \"user\": \"" << user << "\"";
}
string levels = getUserLevels(user);
if (!user.empty() || !levels.empty()) {
*ostream << ",\n \"access\": \"" << levels << "\"";
}
*ostream << ",\n \"signal\": " << (m_protocol->hasSignal() ? "true" : "false");
if (m_protocol->hasSignal()) {
*ostream << ",\n \"symbolrate\": " << m_protocol->getSymbolRate()
<< ",\n \"maxsymbolrate\": " << m_protocol->getMaxSymbolRate();
if (m_protocol->getMinArbitrationDelay() >= 0) {
*ostream << ",\n \"minarbitrationmicros\": " << m_protocol->getMinArbitrationDelay()
<< ",\n \"maxarbitrationmicros\": " << m_protocol->getMaxArbitrationDelay();
}
if (m_protocol->getMinSymbolLatency() >= 0) {
*ostream << ",\n \"minsymbollatency\": " << m_protocol->getMinSymbolLatency()
<< ",\n \"maxsymbollatency\": " << m_protocol->getMaxSymbolLatency();
}
}
if (!m_protocol->isReadOnly()) {
*ostream << ",\n \"qq\": " << static_cast<unsigned>(m_address);
}
*ostream << ",\n \"reconnects\": " << m_reconnectCount
<< ",\n \"masters\": " << m_protocol->getMasterCount()
<< ",\n \"messages\": " << m_messages->size()
<< ",\n \"lastup\": " << static_cast<unsigned>(maxLastUp)
<< "\n }"
<< "\n}";
type = 6;
}
*connected = false;
return formatHttpResult(ret, type, ostream);
} // request for "/data..."
if (uri == "/datatypes") {
*ostream << "[";
OutputFormat verbosity = OF_NAMES|OF_JSON|OF_ALL_ATTRS;
DataTypeList::getInstance()->dump(verbosity, true, ostream);
*ostream << "\n]";
type = 6;
*connected = false;
return formatHttpResult(ret, type, ostream);
}
if (uri == "/templates" || uri.substr(0, 11) == "/templates/") {
*ostream << "[";
OutputFormat verbosity = OF_NAMES|OF_JSON|OF_ALL_ATTRS;
string name = uri == "/templates" ? "" : uri.substr(11) + "/";
const auto tmpl = m_scanHelper->getTemplates(name);
tmpl->dump(verbosity, ostream);
*ostream << "\n]";
type = 6;
*connected = false;
return formatHttpResult(ret, type, ostream);
}
if (uri == "/raw") {
time_t since = 0, until = 0;
bool onlyUnknown = false;
if (args.size() > argPos) {
string query = args[argPos];
istringstream stream(query);
string token;
while (getline(stream, token, '&')) {
size_t pos = token.find('=');
string qname, value;
if (pos != string::npos) {
qname = token.substr(0, pos);
value = token.substr(pos + 1);
} else {
qname = token;
}
if (qname == "since") {
since = parseInt(value.c_str(), 10, 0, 0xffffffff, &ret);
} else if (qname == "unknown") {
onlyUnknown = parseBoolQuery(value);
}
if (ret != RESULT_OK) {
break;
}
}
}
if (ret == RESULT_OK) {
*ostream << "[";
if (since > 0) {
time_t now;
time(&now);
until = now-1;
}
m_busHandler->formatGrabResult(onlyUnknown, OF_JSON, ostream, false, since, until);
*ostream << "\n]";
type = 6;
}
*connected = false;
return formatHttpResult(ret, type, ostream);
}
if (uri == "/decode") {
string def;
string raw;
if (args.size() > argPos) {
string query = args[argPos];
istringstream stream(query);
string token;
while (getline(stream, token, '&')) {
size_t pos = token.find('=');
string qname, value;
if (pos != string::npos) {
qname = token.substr(0, pos);
value = token.substr(pos + 1);
} else {
qname = token;
}
if (qname == "def") {
def = value;
} else if (qname == "raw") {
raw = value;
} else {
ret = RESULT_ERR_INVALID_ARG;
}
if (ret != RESULT_OK) {
break;
}
}
}
if (def.empty() || raw.empty()) {
ret = RESULT_ERR_INVALID_ARG;
} else if (ret == RESULT_OK) {
time_t now;
time(&now);
istringstream defstr("#\n" + def); // ensure first line is not used for determining col names
string errorDescription;
DataFieldTemplates* templates = m_scanHelper->getTemplates("*");
LoadableDataFieldSet fields("", templates);
ret = fields.readFromStream(&defstr, "temporary", now, true, nullptr, &errorDescription);
if (ret == RESULT_OK && fields.size()) {
SlaveSymbolString slave;
slave.push_back(0); // dummy length
ret = slave.parseHex(raw);
const SingleDataField* field = fields[0];
if (ret == RESULT_OK && field) {
slave.adjustHeader();
ret = field->read(slave, 0, false, nullptr, 0, OF_JSON|OF_SHORT, 0, ostream);
}
} else {
ret = RESULT_ERR_INVALID_ARG;
}
type = 6;
}
*connected = false;
return formatHttpResult(ret, type, ostream);
}
if (uri.length() < 1 || uri[0] != '/' || uri.find("//") != string::npos || uri.find("..") != string::npos) {
ret = RESULT_ERR_INVALID_ARG;
} else {
string filename = m_htmlPath + uri;
if (uri[uri.length() - 1] == '/') {
filename += "index.html";
}
size_t pos = filename.find_last_of('.');
if (pos != string::npos && pos != filename.length() - 1 && pos >= filename.length() - 5) {
string ext = filename.substr(pos + 1);
if (ext == "html") {
type = 0;
} else if (ext == "css") {
type = 1;
} else if (ext == "js") {
type = 2;
} else if (ext == "png") {
type = 3;
} else if (ext == "jpg" || ext == "jpeg") {
type = 4;
} else if (ext == "svg") {
type = 5;
} else if (ext == "json") {
type = 6;
} else if (ext == "yaml") {
type = 7;
} else if (ext == "csv") {
type = 8;
}
}
if (type < 0) {
ret = RESULT_ERR_NOTFOUND;
} else {
ifstream ifs;
ifs.open(filename.c_str(), ifstream::in | ifstream::binary);
if (!ifs.is_open()) {
ret = RESULT_ERR_NOTFOUND;
} else {
ifs >> ostream->rdbuf();
ifs.close();
}
}
}
*connected = false;
return formatHttpResult(ret, type, ostream);
}
result_t MainLoop::formatHttpResult(result_t ret, int type, ostringstream* ostream) {
string data = ret == RESULT_OK ? ostream->str() : "";
ostream->str("");
ostream->clear();
*ostream << "HTTP/1.0 ";
switch (ret) {
case RESULT_OK:
*ostream << "200 OK\r\nContent-Type: ";
switch (type) {
case 1:
*ostream << "text/css";
break;
case 2:
*ostream << "application/javascript";
break;
case 3:
*ostream << "image/png";
break;
case 4:
*ostream << "image/jpeg";
break;
case 5:
*ostream << "image/svg+xml";
break;
case 6:
*ostream << "application/json;charset=utf-8";
break;
case 7:
*ostream << "application/yaml;charset=utf-8";
break;
case 9:
*ostream << "text/comma-separated-values";
break;
default:
*ostream << "text/html";
break;
}
*ostream << "\r\nContent-Length: " << setw(0) << dec << static_cast<unsigned>(data.length());
break;
case RESULT_ERR_NOTFOUND:
*ostream << "404 Not Found";
break;
case RESULT_ERR_INVALID_ARG:
case RESULT_ERR_INVALID_NUM:
case RESULT_ERR_INVALID_POS:
case RESULT_ERR_OUT_OF_RANGE:
case RESULT_ERR_INVALID_PART:
case RESULT_ERR_MISSING_ARG:
case RESULT_ERR_INVALID_LIST:
*ostream << "400 Bad Request";
break;
case RESULT_ERR_NOTAUTHORIZED:
*ostream << "403 Forbidden";
break;
default:
*ostream << "500 Internal Server Error";
break;
}
*ostream << "\r\nServer: " PACKAGE_NAME "/" PACKAGE_VERSION "\r\n\r\n" << data;
return RESULT_OK;
}
} // namespace ebusd