separate resolving+scanning to own interface+class and avoid use of extern
This commit is contained in:
@@ -5,6 +5,7 @@ set(ebusd_SOURCES
|
|||||||
datahandler.h datahandler.cpp
|
datahandler.h datahandler.cpp
|
||||||
network.h network.cpp
|
network.h network.cpp
|
||||||
mainloop.h mainloop.cpp
|
mainloop.h mainloop.cpp
|
||||||
|
scan.h scan.cpp
|
||||||
main.h main.cpp
|
main.h main.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
|
|
||||||
#include "ebusd/bushandler.h"
|
#include "ebusd/bushandler.h"
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include "ebusd/main.h"
|
|
||||||
#include "lib/utils/log.h"
|
#include "lib/utils/log.h"
|
||||||
|
|
||||||
namespace ebusd {
|
namespace ebusd {
|
||||||
@@ -1637,14 +1636,14 @@ result_t BusHandler::scanAndWait(symbol_t dstAddress, bool loadScanConfig, bool
|
|||||||
bool timedOut = result == RESULT_ERR_TIMEOUT;
|
bool timedOut = result == RESULT_ERR_TIMEOUT;
|
||||||
bool loadFailed = false;
|
bool loadFailed = false;
|
||||||
if (timedOut || result == RESULT_OK) {
|
if (timedOut || result == RESULT_OK) {
|
||||||
result = loadScanConfigFile(m_messages, dstAddress, false, &file); // try to load even if one message timed out
|
result = m_scanHelper->loadScanConfigFile(dstAddress, &file); // try to load even if one message timed out
|
||||||
loadFailed = result != RESULT_OK;
|
loadFailed = result != RESULT_OK;
|
||||||
if (timedOut && loadFailed) {
|
if (timedOut && loadFailed) {
|
||||||
result = RESULT_ERR_TIMEOUT; // back to previous result
|
result = RESULT_ERR_TIMEOUT; // back to previous result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (result == RESULT_OK) {
|
if (result == RESULT_OK) {
|
||||||
executeInstructions(m_messages);
|
m_scanHelper->executeInstructions(this);
|
||||||
setScanConfigLoaded(dstAddress, file);
|
setScanConfigLoaded(dstAddress, file);
|
||||||
if (!hasAdditionalScanMessages && m_messages->hasAdditionalScanMessages()) {
|
if (!hasAdditionalScanMessages && m_messages->hasAdditionalScanMessages()) {
|
||||||
// additional scan messages now available
|
// additional scan messages now available
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
|
#include "ebusd/scan.h"
|
||||||
#include "lib/ebus/message.h"
|
#include "lib/ebus/message.h"
|
||||||
#include "lib/ebus/data.h"
|
#include "lib/ebus/data.h"
|
||||||
#include "lib/ebus/symbol.h"
|
#include "lib/ebus/symbol.h"
|
||||||
@@ -367,6 +368,7 @@ class BusHandler : public WaitThread {
|
|||||||
* Construct a new instance.
|
* Construct a new instance.
|
||||||
* @param device the @a Device instance for accessing the bus.
|
* @param device the @a Device instance for accessing the bus.
|
||||||
* @param messages the @a MessageMap instance with all known @a Message instances.
|
* @param messages the @a MessageMap instance with all known @a Message instances.
|
||||||
|
* @param scanHelper the @a ScanHelper instance.
|
||||||
* @param ownAddress the own master address.
|
* @param ownAddress the own master address.
|
||||||
* @param answer whether to answer queries for the own master/slave address.
|
* @param answer whether to answer queries for the own master/slave address.
|
||||||
* @param busLostRetries the number of times a send is repeated due to lost arbitration.
|
* @param busLostRetries the number of times a send is repeated due to lost arbitration.
|
||||||
@@ -377,13 +379,13 @@ class BusHandler : public WaitThread {
|
|||||||
* @param generateSyn whether to enable AUTO-SYN symbol generation.
|
* @param generateSyn whether to enable AUTO-SYN symbol generation.
|
||||||
* @param pollInterval the interval in seconds in which poll messages are cycled, or 0 if disabled.
|
* @param pollInterval the interval in seconds in which poll messages are cycled, or 0 if disabled.
|
||||||
*/
|
*/
|
||||||
BusHandler(Device* device, MessageMap* messages,
|
BusHandler(Device* device, MessageMap* messages, ScanHelper* scanHelper,
|
||||||
symbol_t ownAddress, bool answer,
|
symbol_t ownAddress, bool answer,
|
||||||
unsigned int busLostRetries, unsigned int failedSendRetries,
|
unsigned int busLostRetries, unsigned int failedSendRetries,
|
||||||
unsigned int busAcquireTimeout, unsigned int slaveRecvTimeout,
|
unsigned int busAcquireTimeout, unsigned int slaveRecvTimeout,
|
||||||
unsigned int lockCount, bool generateSyn,
|
unsigned int lockCount, bool generateSyn,
|
||||||
unsigned int pollInterval)
|
unsigned int pollInterval)
|
||||||
: WaitThread(), m_device(device), m_reconnect(false), m_messages(messages),
|
: WaitThread(), m_device(device), m_reconnect(false), m_messages(messages), m_scanHelper(scanHelper),
|
||||||
m_ownMasterAddress(ownAddress), m_ownSlaveAddress(getSlaveAddress(ownAddress)),
|
m_ownMasterAddress(ownAddress), m_ownSlaveAddress(getSlaveAddress(ownAddress)),
|
||||||
m_answer(answer), m_addressConflict(false),
|
m_answer(answer), m_addressConflict(false),
|
||||||
m_busLostRetries(busLostRetries), m_failedSendRetries(failedSendRetries),
|
m_busLostRetries(busLostRetries), m_failedSendRetries(failedSendRetries),
|
||||||
@@ -676,6 +678,9 @@ class BusHandler : public WaitThread {
|
|||||||
/** the @a MessageMap instance with all known @a Message instances. */
|
/** the @a MessageMap instance with all known @a Message instances. */
|
||||||
MessageMap* m_messages;
|
MessageMap* m_messages;
|
||||||
|
|
||||||
|
/** the @a ScanHelper instance. */
|
||||||
|
ScanHelper* m_scanHelper;
|
||||||
|
|
||||||
/** the own master address. */
|
/** the own master address. */
|
||||||
const symbol_t m_ownMasterAddress;
|
const symbol_t m_ownMasterAddress;
|
||||||
|
|
||||||
|
|||||||
+32
-549
@@ -33,6 +33,7 @@
|
|||||||
#include "ebusd/mainloop.h"
|
#include "ebusd/mainloop.h"
|
||||||
#include "lib/utils/log.h"
|
#include "lib/utils/log.h"
|
||||||
#include "lib/utils/httpclient.h"
|
#include "lib/utils/httpclient.h"
|
||||||
|
#include "ebusd/scan.h"
|
||||||
|
|
||||||
|
|
||||||
/** the version string of the program. */
|
/** the version string of the program. */
|
||||||
@@ -139,24 +140,15 @@ static struct options s_opt = {
|
|||||||
/** the @a MessageMap instance, or nullptr. */
|
/** the @a MessageMap instance, or nullptr. */
|
||||||
static MessageMap* s_messageMap = nullptr;
|
static MessageMap* s_messageMap = nullptr;
|
||||||
|
|
||||||
|
/** the @a ScanHelper instance, or nullptr. */
|
||||||
|
static ScanHelper* s_scanHelper = nullptr;
|
||||||
|
|
||||||
/** the @a MainLoop instance, or nullptr. */
|
/** the @a MainLoop instance, or nullptr. */
|
||||||
static MainLoop* s_mainLoop = nullptr;
|
static MainLoop* s_mainLoop = nullptr;
|
||||||
|
|
||||||
/** the (optionally corrected) config path for retrieving configuration files from. */
|
/** the (optionally corrected) config path for retrieving configuration files from. */
|
||||||
static string s_configPath = CONFIG_PATH;
|
static string s_configPath = CONFIG_PATH;
|
||||||
|
|
||||||
/** the path prefix (including trailing "/") for retrieving configuration files from local files (empty for HTTPS). */
|
|
||||||
static string s_configLocalPrefix = "";
|
|
||||||
|
|
||||||
/** the URI prefix (including trailing "/") for retrieving configuration files from HTTPS (empty for local files). */
|
|
||||||
static string s_configUriPrefix = "";
|
|
||||||
|
|
||||||
/** the optional language query part for retrieving configuration files from HTTPS (empty for local files). */
|
|
||||||
static string s_configLangQuery = "";
|
|
||||||
|
|
||||||
/** the @a HttpClient for retrieving configuration files from HTTPS. */
|
|
||||||
static HttpClient* s_configHttpClient = nullptr;
|
|
||||||
|
|
||||||
/** the documentation of the program. */
|
/** the documentation of the program. */
|
||||||
static const char argpdoc[] =
|
static const char argpdoc[] =
|
||||||
"A daemon for communication with eBUS heating systems.";
|
"A daemon for communication with eBUS heating systems.";
|
||||||
@@ -278,15 +270,6 @@ static const struct argp_option argpoptions[] = {
|
|||||||
{nullptr, 0, nullptr, 0, nullptr, 0 },
|
{nullptr, 0, nullptr, 0, nullptr, 0 },
|
||||||
};
|
};
|
||||||
|
|
||||||
/** the global @a DataFieldTemplates. */
|
|
||||||
static DataFieldTemplates s_globalTemplates;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the loaded @a DataFieldTemplates by relative path (may also carry
|
|
||||||
* @a globalTemplates as replacement for missing file).
|
|
||||||
*/
|
|
||||||
static map<string, DataFieldTemplates*> s_templatesByPath;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The program argument parsing function.
|
* The program argument parsing function.
|
||||||
* @param key the key from @a argpoptions.
|
* @param key the key from @a argpoptions.
|
||||||
@@ -760,16 +743,9 @@ void cleanup() {
|
|||||||
delete s_messageMap;
|
delete s_messageMap;
|
||||||
s_messageMap = nullptr;
|
s_messageMap = nullptr;
|
||||||
}
|
}
|
||||||
// free templates
|
if (s_scanHelper) {
|
||||||
for (const auto& it : s_templatesByPath) {
|
delete s_scanHelper;
|
||||||
if (it.second != &s_globalTemplates) {
|
s_scanHelper = nullptr;
|
||||||
delete it.second;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s_templatesByPath.clear();
|
|
||||||
if (s_configHttpClient) {
|
|
||||||
delete s_configHttpClient;
|
|
||||||
s_configHttpClient = nullptr;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -833,503 +809,6 @@ void signalHandler(int sig) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Lazy create the s_configHttpClient if not already done.
|
|
||||||
* @return true (always).
|
|
||||||
*/
|
|
||||||
bool lazyHttpClient() {
|
|
||||||
if (!s_configHttpClient) {
|
|
||||||
s_configHttpClient = new HttpClient(s_opt.caFile, s_opt.caPath);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Collect configuration files matching the prefix and extension from the specified path.
|
|
||||||
* @param relPath the relative path from which to collect the files (without trailing "/").
|
|
||||||
* @param prefix the filename prefix the files have to match, or empty.
|
|
||||||
* @param extension the filename extension the files have to match.
|
|
||||||
* @param files the @a vector to which to add the matching files.
|
|
||||||
* @param query the query string suffix for HTTPS retrieval starting with "&", or empty.
|
|
||||||
* @param dirs the @a vector to which to add found directories (without any name check), or nullptr to ignore.
|
|
||||||
* @param hasTemplates the bool to set when the templates file was found in the path, or nullptr to ignore.
|
|
||||||
* @return the result code.
|
|
||||||
*/
|
|
||||||
static result_t collectConfigFiles(const string& relPath, const string& prefix, const string& extension,
|
|
||||||
vector<string>* files,
|
|
||||||
bool ignoreAddressPrefix = false, const string& query = "",
|
|
||||||
vector<string>* dirs = nullptr, bool* hasTemplates = nullptr) {
|
|
||||||
const string relPathWithSlash = relPath.empty() ? "" : relPath + "/";
|
|
||||||
if (!s_configUriPrefix.empty()) {
|
|
||||||
string uri = s_configUriPrefix + relPathWithSlash + s_configLangQuery + (s_configLangQuery.empty() ? "?" : "&")
|
|
||||||
+ "t=" + extension.substr(1) + query;
|
|
||||||
string names;
|
|
||||||
if (!lazyHttpClient() || !s_configHttpClient->get(uri, "", &names)) {
|
|
||||||
return RESULT_ERR_NOTFOUND;
|
|
||||||
}
|
|
||||||
istringstream stream(names);
|
|
||||||
string name;
|
|
||||||
while (getline(stream, name)) {
|
|
||||||
if (name.empty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (name == "_templates"+extension) {
|
|
||||||
if (hasTemplates) {
|
|
||||||
*hasTemplates = true;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (prefix.length() == 0 ? (!ignoreAddressPrefix || name.length() < 3 || name.find_first_of('.') != 2)
|
|
||||||
: (name.length() >= prefix.length() && name.substr(0, prefix.length()) == prefix)) {
|
|
||||||
files->push_back(relPathWithSlash + name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return RESULT_OK;
|
|
||||||
}
|
|
||||||
const string path = s_configLocalPrefix + relPathWithSlash;
|
|
||||||
logDebug(lf_main, "reading directory %s", path.c_str());
|
|
||||||
DIR* dir = opendir(path.c_str());
|
|
||||||
if (dir == nullptr) {
|
|
||||||
return RESULT_ERR_NOTFOUND;
|
|
||||||
}
|
|
||||||
dirent* d;
|
|
||||||
while ((d = readdir(dir)) != nullptr) {
|
|
||||||
string name = d->d_name;
|
|
||||||
if (name == "." || name == "..") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const string p = path + name;
|
|
||||||
struct stat stat_buf = {};
|
|
||||||
if (stat(p.c_str(), &stat_buf) != 0) {
|
|
||||||
logError(lf_main, "unable to stat file %s", p.c_str());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
logDebug(lf_main, "file type of %s is %s", p.c_str(),
|
|
||||||
S_ISDIR(stat_buf.st_mode) ? "dir" : S_ISREG(stat_buf.st_mode) ? "file" : "other");
|
|
||||||
if (S_ISDIR(stat_buf.st_mode)) {
|
|
||||||
if (dirs != nullptr) {
|
|
||||||
dirs->push_back(relPathWithSlash + name);
|
|
||||||
}
|
|
||||||
} else if (S_ISREG(stat_buf.st_mode) && name.length() >= extension.length()
|
|
||||||
&& name.substr(name.length()-extension.length()) == extension) {
|
|
||||||
if (name == "_templates"+extension) {
|
|
||||||
if (hasTemplates) {
|
|
||||||
*hasTemplates = true;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (prefix.length() == 0 ? (!ignoreAddressPrefix || name.length() < 3 || name.find_first_of('.') != 2)
|
|
||||||
: (name.length() >= prefix.length() && name.substr(0, prefix.length()) == prefix)) {
|
|
||||||
files->push_back(relPathWithSlash + name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
closedir(dir);
|
|
||||||
|
|
||||||
return RESULT_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
DataFieldTemplates* getTemplates(const string& filename) {
|
|
||||||
if (filename == "*") {
|
|
||||||
unsigned long maxLength = 0;
|
|
||||||
DataFieldTemplates* best = nullptr;
|
|
||||||
for (auto it : s_templatesByPath) {
|
|
||||||
if (it.first.size() > maxLength) {
|
|
||||||
best = it.second;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (best) {
|
|
||||||
return best;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
string path;
|
|
||||||
size_t pos = filename.find_last_of('/');
|
|
||||||
if (pos != string::npos) {
|
|
||||||
path = filename.substr(0, pos);
|
|
||||||
}
|
|
||||||
const auto it = s_templatesByPath.find(path);
|
|
||||||
if (it != s_templatesByPath.end()) {
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &s_globalTemplates;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read the @a DataFieldTemplates for the specified path if necessary.
|
|
||||||
* @param relPath the relative path from which to read the files (without trailing "/").
|
|
||||||
* @param extension the filename extension of the files to read.
|
|
||||||
* @param available whether the templates file is available in the path.
|
|
||||||
* @param verbose whether to verbosely log problems.
|
|
||||||
* @return false when the templates for the path were already loaded before, true when the templates for the path were added (independent from @a available).
|
|
||||||
* @return the @a DataFieldTemplates.
|
|
||||||
*/
|
|
||||||
static bool readTemplates(const string relPath, const string extension, bool available, bool verbose = false) {
|
|
||||||
const auto it = s_templatesByPath.find(relPath);
|
|
||||||
if (it != s_templatesByPath.end()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
DataFieldTemplates* templates;
|
|
||||||
if (relPath.empty() || !available) {
|
|
||||||
templates = &s_globalTemplates;
|
|
||||||
} else {
|
|
||||||
templates = new DataFieldTemplates(s_globalTemplates);
|
|
||||||
}
|
|
||||||
s_templatesByPath[relPath] = templates;
|
|
||||||
if (!available) {
|
|
||||||
// global templates are stored as replacement in order to determine whether the directory was already loaded
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
string errorDescription;
|
|
||||||
string logPath = relPath.empty() ? "/" : relPath;
|
|
||||||
logInfo(lf_main, "reading templates %s", logPath.c_str());
|
|
||||||
string file = (relPath.empty() ? "" : relPath + "/") + "_templates" + extension;
|
|
||||||
result_t result = loadDefinitionsFromConfigPath(templates, file, verbose, nullptr, &errorDescription, true);
|
|
||||||
if (result == RESULT_OK) {
|
|
||||||
logInfo(lf_main, "read templates in %s", logPath.c_str());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
logError(lf_main, "error reading templates in %s: %s, last error: %s", logPath.c_str(), getResultCode(result),
|
|
||||||
errorDescription.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read the configuration files from the specified path.
|
|
||||||
* @param relPath the relative path from which to read the files (without trailing "/").
|
|
||||||
* @param extension the filename extension of the files to read.
|
|
||||||
* @param messages the @a MessageMap to load the messages into.
|
|
||||||
* @param recursive whether to load all files recursively.
|
|
||||||
* @param verbose whether to verbosely log problems.
|
|
||||||
* @param errorDescription a string in which to store the error description in case of error.
|
|
||||||
* @return the result code.
|
|
||||||
*/
|
|
||||||
static result_t readConfigFiles(const string& relPath, const string& extension, bool recursive,
|
|
||||||
bool verbose, string* errorDescription, MessageMap* messages) {
|
|
||||||
vector<string> files, dirs;
|
|
||||||
bool hasTemplates = false;
|
|
||||||
result_t result = collectConfigFiles(relPath, "", extension, &files, false, "", &dirs, &hasTemplates);
|
|
||||||
if (result != RESULT_OK) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
readTemplates(relPath, extension, hasTemplates, verbose);
|
|
||||||
for (const auto& name : files) {
|
|
||||||
logInfo(lf_main, "reading file %s", name.c_str());
|
|
||||||
result = loadDefinitionsFromConfigPath(messages, name, verbose, nullptr, errorDescription);
|
|
||||||
if (result != RESULT_OK) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
logInfo(lf_main, "successfully read file %s", name.c_str());
|
|
||||||
}
|
|
||||||
if (recursive) {
|
|
||||||
for (const auto& name : dirs) {
|
|
||||||
logInfo(lf_main, "reading dir %s", name.c_str());
|
|
||||||
result = readConfigFiles(name, extension, true, verbose, errorDescription, messages);
|
|
||||||
if (result != RESULT_OK) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
logInfo(lf_main, "successfully read dir %s", name.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return RESULT_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper method for immediate reading of a @a Message from the bus.
|
|
||||||
* @param message the @a Message to read.
|
|
||||||
*/
|
|
||||||
void readMessage(Message* message) {
|
|
||||||
if (!s_mainLoop || !message) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
BusHandler* busHandler = s_mainLoop->getBusHandler();
|
|
||||||
result_t result = busHandler->readFromBus(message, "");
|
|
||||||
if (result != RESULT_OK) {
|
|
||||||
logError(lf_main, "error reading message %s %s: %s", message->getCircuit().c_str(), message->getName().c_str(),
|
|
||||||
getResultCode(result));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result_t executeInstructions(MessageMap* messages, bool verbose) {
|
|
||||||
string errorDescription;
|
|
||||||
result_t result = messages->resolveConditions(verbose, &errorDescription);
|
|
||||||
if (result != RESULT_OK) {
|
|
||||||
logError(lf_main, "error resolving conditions: %s, last error: %s", getResultCode(result),
|
|
||||||
errorDescription.c_str());
|
|
||||||
}
|
|
||||||
ostringstream log;
|
|
||||||
result = messages->executeInstructions(readMessage, &log);
|
|
||||||
if (result != RESULT_OK) {
|
|
||||||
logError(lf_main, "error executing instructions: %s, last error: %s", getResultCode(result),
|
|
||||||
log.str().c_str());
|
|
||||||
} else if (verbose && log.tellp() > 0) {
|
|
||||||
logInfo(lf_main, log.str().c_str());
|
|
||||||
}
|
|
||||||
logNotice(lf_main, "found messages: %d (%d conditional on %d conditions, %d poll, %d update)", messages->size(),
|
|
||||||
messages->sizeConditional(), messages->sizeConditions(), messages->sizePoll(), messages->sizePassive());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
result_t loadDefinitionsFromConfigPath(FileReader* reader, const string& filename, bool verbose,
|
|
||||||
map<string, string>* defaults, string* errorDescription, bool replace) {
|
|
||||||
istream* stream = nullptr;
|
|
||||||
time_t mtime = 0;
|
|
||||||
if (s_configUriPrefix.empty()) {
|
|
||||||
stream = FileReader::openFile(s_configLocalPrefix + filename, errorDescription, &mtime);
|
|
||||||
} else {
|
|
||||||
string content;
|
|
||||||
if (lazyHttpClient()
|
|
||||||
&& s_configHttpClient->get(s_configUriPrefix + filename + s_configLangQuery, "", &content, &mtime)) {
|
|
||||||
stream = new istringstream(content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result_t result;
|
|
||||||
if (stream) {
|
|
||||||
result = reader->readFromStream(stream, filename, mtime, verbose, defaults, errorDescription, replace);
|
|
||||||
delete(stream);
|
|
||||||
} else {
|
|
||||||
result = RESULT_ERR_NOTFOUND;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
result_t loadConfigFiles(MessageMap* messages, bool verbose, bool denyRecursive) {
|
|
||||||
logInfo(lf_main, "loading configuration files from %s", s_configPath.c_str());
|
|
||||||
messages->lock();
|
|
||||||
messages->clear();
|
|
||||||
s_globalTemplates.clear();
|
|
||||||
for (auto& it : s_templatesByPath) {
|
|
||||||
if (it.second != &s_globalTemplates) {
|
|
||||||
delete it.second;
|
|
||||||
}
|
|
||||||
it.second = nullptr;
|
|
||||||
}
|
|
||||||
s_templatesByPath.clear();
|
|
||||||
|
|
||||||
string errorDescription;
|
|
||||||
result_t result = readConfigFiles("", ".csv",
|
|
||||||
(!s_opt.scanConfig || s_opt.checkConfig) && !denyRecursive, verbose, &errorDescription, messages);
|
|
||||||
if (result == RESULT_OK) {
|
|
||||||
logInfo(lf_main, "read config files, got %d messages", messages->size());
|
|
||||||
} else {
|
|
||||||
logError(lf_main, "error reading config files from %s: %s, last error: %s", s_configPath.c_str(),
|
|
||||||
getResultCode(result), errorDescription.c_str());
|
|
||||||
}
|
|
||||||
messages->unlock();
|
|
||||||
return s_opt.checkConfig ? result : RESULT_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
result_t loadScanConfigFile(MessageMap* messages, symbol_t address, bool verbose, string* relativeFile) {
|
|
||||||
Message* message = messages->getScanMessage(address);
|
|
||||||
if (!message || message->getLastUpdateTime() == 0) {
|
|
||||||
return RESULT_ERR_NOTFOUND;
|
|
||||||
}
|
|
||||||
const SlaveSymbolString& data = message->getLastSlaveData();
|
|
||||||
if (data.getDataSize() < 1+5+2+2) {
|
|
||||||
logError(lf_main, "unable to load scan config %2.2x: slave part too short (%d)", address, data.getDataSize());
|
|
||||||
return RESULT_EMPTY;
|
|
||||||
}
|
|
||||||
DataFieldSet* identFields = DataFieldSet::getIdentFields();
|
|
||||||
string manufStr, addrStr, ident; // path: cfgpath/MANUFACTURER, prefix: ZZ., ident: C[C[C[C[C]]]], SW: xxxx, HW: xxxx
|
|
||||||
unsigned int sw = 0, hw = 0;
|
|
||||||
ostringstream out;
|
|
||||||
size_t offset = 0;
|
|
||||||
size_t field = 0;
|
|
||||||
bool fromLocal = s_configUriPrefix.empty();
|
|
||||||
// manufacturer name
|
|
||||||
result_t result = (*identFields)[field]->read(data, offset, false, nullptr, -1, OF_NONE, -1, &out);
|
|
||||||
if (result == RESULT_ERR_NOTFOUND && fromLocal) {
|
|
||||||
result = (*identFields)[field]->read(data, offset, false, nullptr, -1, OF_NUMERIC, -1, &out); // manufacturer name
|
|
||||||
}
|
|
||||||
if (result == RESULT_OK) {
|
|
||||||
manufStr = out.str();
|
|
||||||
transform(manufStr.begin(), manufStr.end(), manufStr.begin(), ::tolower);
|
|
||||||
out.str("");
|
|
||||||
out << setw(2) << hex << setfill('0') << nouppercase << static_cast<unsigned>(address);
|
|
||||||
addrStr = out.str();
|
|
||||||
out.str("");
|
|
||||||
out.clear();
|
|
||||||
offset += (*identFields)[field++]->getLength(pt_slaveData, MAX_LEN);
|
|
||||||
result = (*identFields)[field]->read(data, offset, false, nullptr, -1, OF_NONE, -1, &out); // identification string
|
|
||||||
}
|
|
||||||
if (result == RESULT_OK) {
|
|
||||||
ident = out.str();
|
|
||||||
out.str("");
|
|
||||||
out.clear();
|
|
||||||
offset += (*identFields)[field++]->getLength(pt_slaveData, MAX_LEN);
|
|
||||||
result = (*identFields)[field]->read(data, offset, nullptr, -1, &sw); // software version number
|
|
||||||
if (result == RESULT_ERR_OUT_OF_RANGE) {
|
|
||||||
sw = (data.dataAt(offset) << 16) | data.dataAt(offset+1); // use hex value instead
|
|
||||||
result = RESULT_OK;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (result == RESULT_OK) {
|
|
||||||
offset += (*identFields)[field++]->getLength(pt_slaveData, MAX_LEN);
|
|
||||||
result = (*identFields)[field]->read(data, offset, nullptr, -1, &hw); // hardware version number
|
|
||||||
if (result == RESULT_ERR_OUT_OF_RANGE) {
|
|
||||||
hw = (data.dataAt(offset) << 16) | data.dataAt(offset+1); // use hex value instead
|
|
||||||
result = RESULT_OK;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (result != RESULT_OK) {
|
|
||||||
logError(lf_main, "unable to load scan config %2.2x: decode field %s %s", address,
|
|
||||||
identFields->getName(field).c_str(), getResultCode(result));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
bool hasTemplates = false;
|
|
||||||
string best;
|
|
||||||
map<string, string> bestDefaults;
|
|
||||||
vector<string> files;
|
|
||||||
auto it = ident.begin();
|
|
||||||
while (it != ident.end()) {
|
|
||||||
if (*it != '_' && !::isalnum(*it)) {
|
|
||||||
it = ident.erase(it);
|
|
||||||
} else {
|
|
||||||
*it = static_cast<char>(::tolower(*it));
|
|
||||||
it++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// find files matching MANUFACTURER/ZZ.*csv in cfgpath
|
|
||||||
string query;
|
|
||||||
if (!fromLocal) {
|
|
||||||
out << "&a=" << addrStr << "&i=" << ident << "&h=" << dec << static_cast<unsigned>(hw) << "&s=" << dec
|
|
||||||
<< static_cast<unsigned>(sw);
|
|
||||||
query = out.str();
|
|
||||||
out.str("");
|
|
||||||
out.clear();
|
|
||||||
}
|
|
||||||
result = collectConfigFiles(manufStr, addrStr + ".", ".csv", &files, false, query, nullptr, &hasTemplates);
|
|
||||||
if (result != RESULT_OK) {
|
|
||||||
logError(lf_main, "unable to load scan config %2.2x: list files in %s %s", address, manufStr.c_str(),
|
|
||||||
getResultCode(result));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
if (files.empty()) {
|
|
||||||
logError(lf_main, "unable to load scan config %2.2x: no file from %s with prefix %s found", address,
|
|
||||||
manufStr.c_str(), addrStr.c_str());
|
|
||||||
return RESULT_ERR_NOTFOUND;
|
|
||||||
}
|
|
||||||
logDebug(lf_main, "found %d matching scan config files from %s with prefix %s: %s", files.size(), manufStr.c_str(),
|
|
||||||
addrStr.c_str(), getResultCode(result));
|
|
||||||
// complete name: cfgpath/MANUFACTURER/ZZ[.C[C[C[C[C]]]]][.circuit][.suffix][.*][.SWxxxx][.HWxxxx][.*].csv
|
|
||||||
size_t bestMatch = 0;
|
|
||||||
for (const auto& name : files) {
|
|
||||||
symbol_t checkDest;
|
|
||||||
unsigned int checkSw, checkHw;
|
|
||||||
map<string, string> defaults;
|
|
||||||
const string filename = name.substr(manufStr.length()+1);
|
|
||||||
if (!messages->extractDefaultsFromFilename(filename, &defaults, &checkDest, &checkSw, &checkHw)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (address != checkDest || (checkSw != UINT_MAX && sw != checkSw) || (checkHw != UINT_MAX && hw != checkHw)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
size_t match = 1;
|
|
||||||
string checkIdent = defaults["name"];
|
|
||||||
if (!checkIdent.empty()) {
|
|
||||||
string remain = ident;
|
|
||||||
bool matches = false;
|
|
||||||
while (remain.length() > 0 && remain.length() >= checkIdent.length()) {
|
|
||||||
if (checkIdent == remain) {
|
|
||||||
matches = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (!::isdigit(remain[remain.length()-1])) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
remain.erase(remain.length()-1); // remove trailing digit
|
|
||||||
}
|
|
||||||
if (!matches) {
|
|
||||||
continue; // IDENT mismatch
|
|
||||||
}
|
|
||||||
match += remain.length();
|
|
||||||
}
|
|
||||||
if (match >= bestMatch) {
|
|
||||||
bestMatch = match;
|
|
||||||
best = name;
|
|
||||||
bestDefaults = defaults;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (best.empty()) {
|
|
||||||
logError(lf_main,
|
|
||||||
"unable to load scan config %2.2x: no file from %s with prefix %s matches ID \"%s\", SW%4.4d, HW%4.4d",
|
|
||||||
address, manufStr.c_str(), addrStr.c_str(), ident.c_str(), sw, hw);
|
|
||||||
return RESULT_ERR_NOTFOUND;
|
|
||||||
}
|
|
||||||
|
|
||||||
// found the right file. load the templates if necessary, then load the file itself
|
|
||||||
bool readCommon = readTemplates(manufStr, ".csv", hasTemplates, s_opt.checkConfig);
|
|
||||||
if (readCommon) {
|
|
||||||
result = collectConfigFiles(manufStr, "", ".csv", &files, true, "&a=-");
|
|
||||||
if (result == RESULT_OK && !files.empty()) {
|
|
||||||
for (const auto& name : files) {
|
|
||||||
string baseName = name.substr(manufStr.length()+1, name.length()-manufStr.length()-strlen(".csv")); // *.
|
|
||||||
if (baseName == "_templates.") { // skip templates
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (baseName.length() < 3 || baseName.find_first_of('.') != 2) { // different from the scheme "ZZ."
|
|
||||||
string errorDescription;
|
|
||||||
result = loadDefinitionsFromConfigPath(messages, name, verbose, nullptr, &errorDescription);
|
|
||||||
if (result == RESULT_OK) {
|
|
||||||
logNotice(lf_main, "read common config file %s", name.c_str());
|
|
||||||
} else {
|
|
||||||
logError(lf_main, "error reading common config file %s: %s, %s", name.c_str(), getResultCode(result),
|
|
||||||
errorDescription.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bestDefaults["name"] = ident;
|
|
||||||
string errorDescription;
|
|
||||||
result = loadDefinitionsFromConfigPath(messages, best, verbose, &bestDefaults, &errorDescription);
|
|
||||||
if (result != RESULT_OK) {
|
|
||||||
logError(lf_main, "error reading scan config file %s for ID \"%s\", SW%4.4d, HW%4.4d: %s, %s", best.c_str(),
|
|
||||||
ident.c_str(), sw, hw, getResultCode(result), errorDescription.c_str());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
logNotice(lf_main, "read scan config file %s for ID \"%s\", SW%4.4d, HW%4.4d", best.c_str(), ident.c_str(), sw, hw);
|
|
||||||
*relativeFile = best;
|
|
||||||
return RESULT_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper method for parsing a master/slave message pair from a command line argument.
|
|
||||||
* @param arg the argument to parse.
|
|
||||||
* @param onlyMasterSlave true to parse only a MS message, false to also parse MM and BC message.
|
|
||||||
* @param master the @a MasterSymbolString to parse into.
|
|
||||||
* @param slave the @a SlaveSymbolString to parse into.
|
|
||||||
* @return true when the argument was valid, false otherwise.
|
|
||||||
*/
|
|
||||||
bool parseMessage(const string& arg, bool onlyMasterSlave, MasterSymbolString* master, SlaveSymbolString* slave) {
|
|
||||||
size_t pos = arg.find_first_of('/');
|
|
||||||
if (pos == string::npos) {
|
|
||||||
logError(lf_main, "invalid message %s: missing \"/\"", arg.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
result_t result = master->parseHex(arg.substr(0, pos));
|
|
||||||
if (result == RESULT_OK) {
|
|
||||||
result = slave->parseHex(arg.substr(pos+1));
|
|
||||||
}
|
|
||||||
if (result != RESULT_OK) {
|
|
||||||
logError(lf_main, "invalid message %s: %s", arg.c_str(), getResultCode(result));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (master->size() < 5) { // skip QQ ZZ PB SB NN
|
|
||||||
logError(lf_main, "invalid message %s: master part too short", arg.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!isMaster((*master)[0])) {
|
|
||||||
logError(lf_main, "invalid message %s: QQ is no master", arg.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!isValidAddress((*master)[1], !onlyMasterSlave) || (onlyMasterSlave && isMaster((*master)[1]))) {
|
|
||||||
logError(lf_main, "invalid message %s: ZZ is invalid", arg.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main function.
|
* Main function.
|
||||||
@@ -1395,8 +874,13 @@ int main(int argc, char* argv[], char* envp[]) {
|
|||||||
if (!s_configPath.empty() && s_configPath[s_configPath.length()-1] != '/') {
|
if (!s_configPath.empty() && s_configPath[s_configPath.length()-1] != '/') {
|
||||||
s_configPath += "/";
|
s_configPath += "/";
|
||||||
}
|
}
|
||||||
|
const string lang = MappedFileReader::normalizeLanguage(
|
||||||
|
s_opt.preferLanguage == nullptr || !s_opt.preferLanguage[0] ? "" : s_opt.preferLanguage
|
||||||
|
);
|
||||||
|
string configLocalPrefix, configUriPrefix;
|
||||||
|
HttpClient* configHttpClient = nullptr;
|
||||||
if (s_configPath.find("://") == string::npos) {
|
if (s_configPath.find("://") == string::npos) {
|
||||||
s_configLocalPrefix = s_configPath;
|
configLocalPrefix = s_configPath;
|
||||||
} else {
|
} else {
|
||||||
if (!s_opt.scanConfig) {
|
if (!s_opt.scanConfig) {
|
||||||
logError(lf_main, "invalid configpath without scanconfig");
|
logError(lf_main, "invalid configpath without scanconfig");
|
||||||
@@ -1411,7 +895,7 @@ int main(int argc, char* argv[], char* envp[]) {
|
|||||||
}
|
}
|
||||||
uint16_t configPort = 80;
|
uint16_t configPort = 80;
|
||||||
string proto, configHost;
|
string proto, configHost;
|
||||||
if (!HttpClient::parseUrl(s_configPath, &proto, &configHost, &configPort, &s_configUriPrefix)) {
|
if (!HttpClient::parseUrl(s_configPath, &proto, &configHost, &configPort, &configUriPrefix)) {
|
||||||
#ifndef HAVE_SSL
|
#ifndef HAVE_SSL
|
||||||
if (proto == "https") {
|
if (proto == "https") {
|
||||||
logError(lf_main, "invalid configPath URL (HTTPS not supported)");
|
logError(lf_main, "invalid configPath URL (HTTPS not supported)");
|
||||||
@@ -1421,23 +905,19 @@ int main(int argc, char* argv[], char* envp[]) {
|
|||||||
logError(lf_main, "invalid configPath URL");
|
logError(lf_main, "invalid configPath URL");
|
||||||
return EINVAL;
|
return EINVAL;
|
||||||
}
|
}
|
||||||
if (!lazyHttpClient() || (
|
configHttpClient = new HttpClient(s_opt.caFile, s_opt.caPath);
|
||||||
|
if (
|
||||||
// check with low timeout of 1 second initially:
|
// check with low timeout of 1 second initially:
|
||||||
!s_configHttpClient->connect(configHost, configPort, proto == "https", PACKAGE_NAME "/" PACKAGE_VERSION, 1)
|
!configHttpClient->connect(configHost, configPort, proto == "https", PACKAGE_NAME "/" PACKAGE_VERSION, 1)
|
||||||
// if that did not work, issue a single retry with default timeout:
|
// if that did not work, issue a single retry with default timeout:
|
||||||
&& !s_configHttpClient->connect(configHost, configPort, proto == "https", PACKAGE_NAME "/" PACKAGE_VERSION)
|
&& !configHttpClient->connect(configHost, configPort, proto == "https", PACKAGE_NAME "/" PACKAGE_VERSION)
|
||||||
)) {
|
) {
|
||||||
logError(lf_main, "invalid configPath URL (connect)");
|
logError(lf_main, "invalid configPath URL (connect)");
|
||||||
|
delete configHttpClient;
|
||||||
cleanup();
|
cleanup();
|
||||||
return EINVAL;
|
return EINVAL;
|
||||||
}
|
}
|
||||||
s_configHttpClient->disconnect();
|
configHttpClient->disconnect();
|
||||||
}
|
|
||||||
const string lang = MappedFileReader::normalizeLanguage(
|
|
||||||
s_opt.preferLanguage == nullptr || !s_opt.preferLanguage[0] ? "" : s_opt.preferLanguage
|
|
||||||
);
|
|
||||||
if (!lang.empty()) {
|
|
||||||
s_configLangQuery = "?l=" + lang;
|
|
||||||
}
|
}
|
||||||
if (!s_opt.readOnly && s_opt.scanConfig && s_opt.initialScan == 0) {
|
if (!s_opt.readOnly && s_opt.scanConfig && s_opt.initialScan == 0) {
|
||||||
s_opt.initialScan = BROADCAST;
|
s_opt.initialScan = BROADCAST;
|
||||||
@@ -1448,16 +928,19 @@ int main(int argc, char* argv[], char* envp[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s_messageMap = new MessageMap(s_opt.checkConfig, lang);
|
s_messageMap = new MessageMap(s_opt.checkConfig, lang);
|
||||||
|
s_scanHelper = new ScanHelper(s_messageMap, s_configPath, configLocalPrefix, configUriPrefix,
|
||||||
|
lang.empty() ? lang : "?l=" + lang, configHttpClient, s_opt.checkConfig);
|
||||||
|
s_messageMap->setResolver(s_scanHelper);
|
||||||
if (s_opt.checkConfig) {
|
if (s_opt.checkConfig) {
|
||||||
logNotice(lf_main, PACKAGE_STRING "." REVISION " performing configuration check...");
|
logNotice(lf_main, PACKAGE_STRING "." REVISION " performing configuration check...");
|
||||||
|
|
||||||
result_t result = loadConfigFiles(s_messageMap, true, s_opt.scanConfig && arg_index < argc);
|
result_t result = s_scanHelper->loadConfigFiles(!s_opt.scanConfig || arg_index >= argc);
|
||||||
result_t overallResult = executeInstructions(s_messageMap, true);
|
result_t overallResult = s_scanHelper->executeInstructions(nullptr);
|
||||||
MasterSymbolString master;
|
MasterSymbolString master;
|
||||||
SlaveSymbolString slave;
|
SlaveSymbolString slave;
|
||||||
while (result == RESULT_OK && s_opt.scanConfig && arg_index < argc) {
|
while (result == RESULT_OK && s_opt.scanConfig && arg_index < argc) {
|
||||||
// check scan config for each passed ident message
|
// check scan config for each passed ident message
|
||||||
if (!parseMessage(argv[arg_index++], true, &master, &slave)) {
|
if (!s_scanHelper->parseMessage(argv[arg_index++], true, &master, &slave)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
symbol_t address = master[1];
|
symbol_t address = master[1];
|
||||||
@@ -1470,8 +953,8 @@ int main(int argc, char* argv[], char* envp[]) {
|
|||||||
} else {
|
} else {
|
||||||
message->storeLastData(master, slave);
|
message->storeLastData(master, slave);
|
||||||
string file;
|
string file;
|
||||||
result_t res = loadScanConfigFile(s_messageMap, address, true, &file);
|
result_t res = s_scanHelper->loadScanConfigFile(address, &file);
|
||||||
result_t instrRes = executeInstructions(s_messageMap, true);
|
result_t instrRes = s_scanHelper->executeInstructions(nullptr);
|
||||||
if (res == RESULT_OK) {
|
if (res == RESULT_OK) {
|
||||||
logInfo(lf_main, "scan config %2.2x: file %s loaded", address, file.c_str());
|
logInfo(lf_main, "scan config %2.2x: file %s loaded", address, file.c_str());
|
||||||
} else if (overallResult == RESULT_OK) {
|
} else if (overallResult == RESULT_OK) {
|
||||||
@@ -1543,17 +1026,17 @@ int main(int argc, char* argv[], char* envp[]) {
|
|||||||
device->getName());
|
device->getName());
|
||||||
|
|
||||||
// load configuration files
|
// load configuration files
|
||||||
loadConfigFiles(s_messageMap);
|
s_scanHelper->loadConfigFiles(s_messageMap);
|
||||||
|
|
||||||
// create the MainLoop and start it
|
// create the MainLoop and start it
|
||||||
s_mainLoop = new MainLoop(s_opt, device, s_messageMap);
|
s_mainLoop = new MainLoop(s_opt, device, s_messageMap, s_scanHelper);
|
||||||
if (s_opt.injectMessages) {
|
if (s_opt.injectMessages) {
|
||||||
BusHandler* busHandler = s_mainLoop->getBusHandler();
|
BusHandler* busHandler = s_mainLoop->getBusHandler();
|
||||||
while (arg_index < argc) {
|
while (arg_index < argc) {
|
||||||
// add each passed message
|
// add each passed message
|
||||||
MasterSymbolString master;
|
MasterSymbolString master;
|
||||||
SlaveSymbolString slave;
|
SlaveSymbolString slave;
|
||||||
if (!parseMessage(argv[arg_index++], false, &master, &slave)) {
|
if (!s_scanHelper->parseMessage(argv[arg_index++], false, &master, &slave)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
busHandler->injectMessage(master, slave);
|
busHandler->injectMessage(master, slave);
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include "lib/ebus/data.h"
|
#include "lib/ebus/data.h"
|
||||||
#include "lib/ebus/message.h"
|
|
||||||
#include "lib/ebus/result.h"
|
#include "lib/ebus/result.h"
|
||||||
#include "lib/utils/log.h"
|
#include "lib/utils/log.h"
|
||||||
|
|
||||||
@@ -91,56 +90,6 @@ struct options {
|
|||||||
bool dumpFlush; //!< flush each byte
|
bool dumpFlush; //!< flush each byte
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the @a DataFieldTemplates for the specified configuration file.
|
|
||||||
* @param filename the full name of the configuration file, or "*" to get the non-root templates with the longest name
|
|
||||||
* or the root templates if not available.
|
|
||||||
* @return the @a DataFieldTemplates.
|
|
||||||
*/
|
|
||||||
DataFieldTemplates* getTemplates(const string& filename);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load the message definitions from configuration files.
|
|
||||||
* @param messages the @a MessageMap to load the messages into.
|
|
||||||
* @param verbose whether to verbosely log problems.
|
|
||||||
* @param denyRecursive whether to avoid loading all files recursively (e.g. for scan config check).
|
|
||||||
* @return the result code.
|
|
||||||
*/
|
|
||||||
result_t loadConfigFiles(MessageMap* messages, bool verbose = false, bool denyRecursive = false);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load the message definitions from a configuration file matching the scan result.
|
|
||||||
* @param messages the @a MessageMap to load the messages into.
|
|
||||||
* @param address the address of the scan participant
|
|
||||||
* (either master for broadcast master data or slave for read slave data).
|
|
||||||
* @param data the scan @a SlaveSymbolString for which to load the configuration file.
|
|
||||||
* @param verbose whether to verbosely log problems.
|
|
||||||
* @param relativeFile the string in which the name of the configuration file is stored on success.
|
|
||||||
* @return the result code.
|
|
||||||
*/
|
|
||||||
result_t loadScanConfigFile(MessageMap* messages, symbol_t address, bool verbose, string* relativeFile);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper method for executing all loaded and resolvable instructions.
|
|
||||||
* @param messages the @a MessageMap instance.
|
|
||||||
* @param verbose whether to verbosely log all problems.
|
|
||||||
* @return the result code.
|
|
||||||
*/
|
|
||||||
result_t executeInstructions(MessageMap* messages, bool verbose = false);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper method for loading definitions from a relative file from the config path/URL.
|
|
||||||
* @param reader the @a FileReader instance to load with the definitions.
|
|
||||||
* @param filename the relative name of the file being read.
|
|
||||||
* @param verbose whether to verbosely log problems.
|
|
||||||
* @param defaults the default values by name (potentially overwritten by file name), or nullptr to not use defaults.
|
|
||||||
* @param errorDescription a string in which to store the error description in case of error.
|
|
||||||
* @param replace whether to replace an already existing entry.
|
|
||||||
* @return @a RESULT_OK on success, or an error code.
|
|
||||||
*/
|
|
||||||
result_t loadDefinitionsFromConfigPath(FileReader* reader, const string& filename, bool verbose,
|
|
||||||
map<string, string>* defaults, string* errorDescription, bool replace = false);
|
|
||||||
|
|
||||||
} // namespace ebusd
|
} // namespace ebusd
|
||||||
|
|
||||||
#endif // EBUSD_MAIN_H_
|
#endif // EBUSD_MAIN_H_
|
||||||
|
|||||||
+16
-9
@@ -21,6 +21,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "ebusd/mainloop.h"
|
#include "ebusd/mainloop.h"
|
||||||
|
#include "ebusd/scan.h"
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -104,8 +105,8 @@ result_t UserList::addFromFile(const string& filename, unsigned int lineNo, map<
|
|||||||
#define VERBOSITY_4 (VERBOSITY_3 | OF_ALL_ATTRS)
|
#define VERBOSITY_4 (VERBOSITY_3 | OF_ALL_ATTRS)
|
||||||
|
|
||||||
|
|
||||||
MainLoop::MainLoop(const struct options& opt, Device *device, MessageMap* messages)
|
MainLoop::MainLoop(const struct options& opt, Device *device, MessageMap* messages, ScanHelper* scanHelper)
|
||||||
: Thread(), m_device(device), m_reconnectCount(0), m_userList(opt.accessLevel), m_messages(messages),
|
: 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_address(opt.address), m_scanConfig(opt.scanConfig), m_initialScan(opt.readOnly ? ESC : opt.initialScan),
|
||||||
m_polling(opt.pollInterval > 0), m_enableHex(opt.enableHex), m_shutdown(false), m_runUpdateCheck(opt.updateCheck),
|
m_polling(opt.pollInterval > 0), m_enableHex(opt.enableHex), m_shutdown(false), m_runUpdateCheck(opt.updateCheck),
|
||||||
m_httpClient(opt.caFile, opt.caPath) {
|
m_httpClient(opt.caFile, opt.caPath) {
|
||||||
@@ -148,7 +149,7 @@ MainLoop::MainLoop(const struct options& opt, Device *device, MessageMap* messag
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// create BusHandler
|
// create BusHandler
|
||||||
m_busHandler = new BusHandler(m_device, m_messages,
|
m_busHandler = new BusHandler(m_device, m_messages, scanHelper,
|
||||||
m_address, opt.answer,
|
m_address, opt.answer,
|
||||||
opt.acquireRetries, opt.sendRetries,
|
opt.acquireRetries, opt.sendRetries,
|
||||||
opt.acquireTimeout, opt.receiveTimeout,
|
opt.acquireTimeout, opt.receiveTimeout,
|
||||||
@@ -166,7 +167,12 @@ MainLoop::MainLoop(const struct options& opt, Device *device, MessageMap* messag
|
|||||||
} else {
|
} else {
|
||||||
logError(lf_main, "error registering data handlers");
|
logError(lf_main, "error registering data handlers");
|
||||||
}
|
}
|
||||||
m_newlyDefinedMessages = opt.enableDefine ? new MessageMap(true, "", false) : nullptr;
|
if (opt.enableDefine) {
|
||||||
|
m_newlyDefinedMessages = new MessageMap(true, "", false);
|
||||||
|
m_newlyDefinedMessages->setResolver(scanHelper);
|
||||||
|
} else {
|
||||||
|
m_newlyDefinedMessages = nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MainLoop::~MainLoop() {
|
MainLoop::~MainLoop() {
|
||||||
@@ -330,7 +336,7 @@ void MainLoop::run() {
|
|||||||
} else if (reload && m_busHandler->hasSignal()) {
|
} else if (reload && m_busHandler->hasSignal()) {
|
||||||
reload = false;
|
reload = false;
|
||||||
// execute initial instructions
|
// execute initial instructions
|
||||||
executeInstructions(m_messages);
|
m_scanHelper->executeInstructions(m_busHandler);
|
||||||
if (m_messages->sizeConditions() > 0 && !m_polling) {
|
if (m_messages->sizeConditions() > 0 && !m_polling) {
|
||||||
logError(lf_main, "conditions require a poll interval > 0");
|
logError(lf_main, "conditions require a poll interval > 0");
|
||||||
}
|
}
|
||||||
@@ -1749,7 +1755,7 @@ result_t MainLoop::executeDecode(const vector<string>& args, ostringstream* ostr
|
|||||||
time(&now);
|
time(&now);
|
||||||
istringstream defstr("#\n" + args[argPos]); // ensure first line is not used for determining col names
|
istringstream defstr("#\n" + args[argPos]); // ensure first line is not used for determining col names
|
||||||
string errorDescription;
|
string errorDescription;
|
||||||
DataFieldTemplates* templates = getTemplates("*");
|
DataFieldTemplates* templates = m_scanHelper->getTemplates("*");
|
||||||
LoadableDataFieldSet fields("", templates);
|
LoadableDataFieldSet fields("", templates);
|
||||||
result_t ret = fields.readFromStream(&defstr, "temporary", now, true, nullptr, &errorDescription);
|
result_t ret = fields.readFromStream(&defstr, "temporary", now, true, nullptr, &errorDescription);
|
||||||
if (ret != RESULT_OK) {
|
if (ret != RESULT_OK) {
|
||||||
@@ -1781,7 +1787,7 @@ result_t MainLoop::executeEncode(const vector<string>& args, ostringstream* ostr
|
|||||||
time(&now);
|
time(&now);
|
||||||
istringstream defstr("#\n" + args[argPos]); // ensure first line is not used for determining col names
|
istringstream defstr("#\n" + args[argPos]); // ensure first line is not used for determining col names
|
||||||
string errorDescription;
|
string errorDescription;
|
||||||
DataFieldTemplates* templates = getTemplates("*");
|
DataFieldTemplates* templates = m_scanHelper->getTemplates("*");
|
||||||
LoadableDataFieldSet fields("", templates);
|
LoadableDataFieldSet fields("", templates);
|
||||||
result_t ret = fields.readFromStream(&defstr, "temporary", now, true, nullptr, &errorDescription);
|
result_t ret = fields.readFromStream(&defstr, "temporary", now, true, nullptr, &errorDescription);
|
||||||
if (ret != RESULT_OK) {
|
if (ret != RESULT_OK) {
|
||||||
@@ -1922,7 +1928,8 @@ result_t MainLoop::executeReload(const vector<string>& args, ostringstream* ostr
|
|||||||
return RESULT_OK;
|
return RESULT_OK;
|
||||||
}
|
}
|
||||||
m_busHandler->clear();
|
m_busHandler->clear();
|
||||||
return loadConfigFiles(m_messages);
|
m_scanHelper->loadConfigFiles(!m_scanConfig);
|
||||||
|
return RESULT_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
result_t MainLoop::executeInfo(const vector<string>& args, const string& user, ostringstream* ostream) {
|
result_t MainLoop::executeInfo(const vector<string>& args, const string& user, ostringstream* ostream) {
|
||||||
@@ -2343,7 +2350,7 @@ result_t MainLoop::executeGet(const vector<string>& args, bool* connected, ostri
|
|||||||
time(&now);
|
time(&now);
|
||||||
istringstream defstr("#\n" + def); // ensure first line is not used for determining col names
|
istringstream defstr("#\n" + def); // ensure first line is not used for determining col names
|
||||||
string errorDescription;
|
string errorDescription;
|
||||||
DataFieldTemplates* templates = getTemplates("*");
|
DataFieldTemplates* templates = m_scanHelper->getTemplates("*");
|
||||||
LoadableDataFieldSet fields("", templates);
|
LoadableDataFieldSet fields("", templates);
|
||||||
ret = fields.readFromStream(&defstr, "temporary", now, true, nullptr, &errorDescription);
|
ret = fields.readFromStream(&defstr, "temporary", now, true, nullptr, &errorDescription);
|
||||||
if (ret == RESULT_OK && fields.size()) {
|
if (ret == RESULT_OK && fields.size()) {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
#include "ebusd/bushandler.h"
|
#include "ebusd/bushandler.h"
|
||||||
#include "ebusd/datahandler.h"
|
#include "ebusd/datahandler.h"
|
||||||
#include "ebusd/network.h"
|
#include "ebusd/network.h"
|
||||||
|
#include "ebusd/scan.h"
|
||||||
#include "lib/ebus/filereader.h"
|
#include "lib/ebus/filereader.h"
|
||||||
#include "lib/ebus/message.h"
|
#include "lib/ebus/message.h"
|
||||||
#include "lib/utils/rotatefile.h"
|
#include "lib/utils/rotatefile.h"
|
||||||
@@ -105,8 +106,9 @@ class MainLoop : public Thread, DeviceListener {
|
|||||||
* @param opt the program options.
|
* @param opt the program options.
|
||||||
* @param device the @a Device instance.
|
* @param device the @a Device instance.
|
||||||
* @param messages the @a MessageMap instance.
|
* @param messages the @a MessageMap instance.
|
||||||
|
* @param scanHelper the @a ScanHelper instance.
|
||||||
*/
|
*/
|
||||||
MainLoop(const struct options& opt, Device *device, MessageMap* messages);
|
MainLoop(const struct options& opt, Device *device, MessageMap* messages, ScanHelper* scanHelper);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Destructor.
|
* Destructor.
|
||||||
@@ -406,6 +408,9 @@ class MainLoop : public Thread, DeviceListener {
|
|||||||
/** the @a MessageMap instance. */
|
/** the @a MessageMap instance. */
|
||||||
MessageMap* m_messages;
|
MessageMap* m_messages;
|
||||||
|
|
||||||
|
/** the @a ScanHelper instance. */
|
||||||
|
ScanHelper* m_scanHelper;
|
||||||
|
|
||||||
/** the own master address for sending on the bus. */
|
/** the own master address for sending on the bus. */
|
||||||
const symbol_t m_address;
|
const symbol_t m_address;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,510 @@
|
|||||||
|
/*
|
||||||
|
* 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/scan.h"
|
||||||
|
#include "ebusd/bushandler.h"
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
#include <functional>
|
||||||
|
#include "lib/utils/log.h"
|
||||||
|
|
||||||
|
|
||||||
|
namespace ebusd {
|
||||||
|
|
||||||
|
using std::dec;
|
||||||
|
using std::hex;
|
||||||
|
using std::setfill;
|
||||||
|
using std::setw;
|
||||||
|
using std::nouppercase;
|
||||||
|
using std::cout;
|
||||||
|
|
||||||
|
|
||||||
|
ScanHelper::~ScanHelper() {
|
||||||
|
// free templates
|
||||||
|
for (const auto& it : m_templatesByPath) {
|
||||||
|
if (it.second != &m_globalTemplates) {
|
||||||
|
delete it.second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_templatesByPath.clear();
|
||||||
|
if (m_configHttpClient) {
|
||||||
|
delete m_configHttpClient;
|
||||||
|
m_configHttpClient = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result_t ScanHelper::collectConfigFiles(const string& relPath, const string& prefix, const string& extension,
|
||||||
|
vector<string>* files,
|
||||||
|
bool ignoreAddressPrefix, const string& query,
|
||||||
|
vector<string>* dirs, bool* hasTemplates) {
|
||||||
|
const string relPathWithSlash = relPath.empty() ? "" : relPath + "/";
|
||||||
|
if (!m_configUriPrefix.empty()) {
|
||||||
|
string uri = m_configUriPrefix + relPathWithSlash + m_configLangQuery + (m_configLangQuery.empty() ? "?" : "&")
|
||||||
|
+ "t=" + extension.substr(1) + query;
|
||||||
|
string names;
|
||||||
|
if (!m_configHttpClient->get(uri, "", &names)) {
|
||||||
|
return RESULT_ERR_NOTFOUND;
|
||||||
|
}
|
||||||
|
istringstream stream(names);
|
||||||
|
string name;
|
||||||
|
while (getline(stream, name)) {
|
||||||
|
if (name.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (name == "_templates"+extension) {
|
||||||
|
if (hasTemplates) {
|
||||||
|
*hasTemplates = true;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (prefix.length() == 0 ? (!ignoreAddressPrefix || name.length() < 3 || name.find_first_of('.') != 2)
|
||||||
|
: (name.length() >= prefix.length() && name.substr(0, prefix.length()) == prefix)) {
|
||||||
|
files->push_back(relPathWithSlash + name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return RESULT_OK;
|
||||||
|
}
|
||||||
|
const string path = m_configLocalPrefix + relPathWithSlash;
|
||||||
|
logDebug(lf_main, "reading directory %s", path.c_str());
|
||||||
|
DIR* dir = opendir(path.c_str());
|
||||||
|
if (dir == nullptr) {
|
||||||
|
return RESULT_ERR_NOTFOUND;
|
||||||
|
}
|
||||||
|
dirent* d;
|
||||||
|
while ((d = readdir(dir)) != nullptr) {
|
||||||
|
string name = d->d_name;
|
||||||
|
if (name == "." || name == "..") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const string p = path + name;
|
||||||
|
struct stat stat_buf = {};
|
||||||
|
if (stat(p.c_str(), &stat_buf) != 0) {
|
||||||
|
logError(lf_main, "unable to stat file %s", p.c_str());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
logDebug(lf_main, "file type of %s is %s", p.c_str(),
|
||||||
|
S_ISDIR(stat_buf.st_mode) ? "dir" : S_ISREG(stat_buf.st_mode) ? "file" : "other");
|
||||||
|
if (S_ISDIR(stat_buf.st_mode)) {
|
||||||
|
if (dirs != nullptr) {
|
||||||
|
dirs->push_back(relPathWithSlash + name);
|
||||||
|
}
|
||||||
|
} else if (S_ISREG(stat_buf.st_mode) && name.length() >= extension.length()
|
||||||
|
&& name.substr(name.length()-extension.length()) == extension) {
|
||||||
|
if (name == "_templates"+extension) {
|
||||||
|
if (hasTemplates) {
|
||||||
|
*hasTemplates = true;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (prefix.length() == 0 ? (!ignoreAddressPrefix || name.length() < 3 || name.find_first_of('.') != 2)
|
||||||
|
: (name.length() >= prefix.length() && name.substr(0, prefix.length()) == prefix)) {
|
||||||
|
files->push_back(relPathWithSlash + name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closedir(dir);
|
||||||
|
|
||||||
|
return RESULT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
DataFieldTemplates* ScanHelper::getTemplates(const string& filename) {
|
||||||
|
if (filename == "*") {
|
||||||
|
unsigned long maxLength = 0;
|
||||||
|
DataFieldTemplates* best = nullptr;
|
||||||
|
for (auto it : m_templatesByPath) {
|
||||||
|
if (it.first.size() > maxLength) {
|
||||||
|
best = it.second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (best) {
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
string path;
|
||||||
|
size_t pos = filename.find_last_of('/');
|
||||||
|
if (pos != string::npos) {
|
||||||
|
path = filename.substr(0, pos);
|
||||||
|
}
|
||||||
|
const auto it = m_templatesByPath.find(path);
|
||||||
|
if (it != m_templatesByPath.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &m_globalTemplates;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ScanHelper::readTemplates(const string relPath, const string extension, bool available) {
|
||||||
|
const auto it = m_templatesByPath.find(relPath);
|
||||||
|
if (it != m_templatesByPath.end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
DataFieldTemplates* templates;
|
||||||
|
if (relPath.empty() || !available) {
|
||||||
|
templates = &m_globalTemplates;
|
||||||
|
} else {
|
||||||
|
templates = new DataFieldTemplates(m_globalTemplates);
|
||||||
|
}
|
||||||
|
m_templatesByPath[relPath] = templates;
|
||||||
|
if (!available) {
|
||||||
|
// global templates are stored as replacement in order to determine whether the directory was already loaded
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
string errorDescription;
|
||||||
|
string logPath = relPath.empty() ? "/" : relPath;
|
||||||
|
logInfo(lf_main, "reading templates %s", logPath.c_str());
|
||||||
|
string file = (relPath.empty() ? "" : relPath + "/") + "_templates" + extension;
|
||||||
|
result_t result = loadDefinitionsFromConfigPath(templates, file, nullptr, &errorDescription, true);
|
||||||
|
if (result == RESULT_OK) {
|
||||||
|
logInfo(lf_main, "read templates in %s", logPath.c_str());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
logError(lf_main, "error reading templates in %s: %s, last error: %s", logPath.c_str(), getResultCode(result),
|
||||||
|
errorDescription.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
result_t ScanHelper::readConfigFiles(const string& relPath, const string& extension, bool recursive,
|
||||||
|
string* errorDescription) {
|
||||||
|
vector<string> files, dirs;
|
||||||
|
bool hasTemplates = false;
|
||||||
|
result_t result = collectConfigFiles(relPath, "", extension, &files, false, "", &dirs, &hasTemplates);
|
||||||
|
if (result != RESULT_OK) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
readTemplates(relPath, extension, hasTemplates);
|
||||||
|
for (const auto& name : files) {
|
||||||
|
logInfo(lf_main, "reading file %s", name.c_str());
|
||||||
|
result = loadDefinitionsFromConfigPath(m_messages, name, nullptr, errorDescription);
|
||||||
|
if (result != RESULT_OK) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
logInfo(lf_main, "successfully read file %s", name.c_str());
|
||||||
|
}
|
||||||
|
if (recursive) {
|
||||||
|
for (const auto& name : dirs) {
|
||||||
|
logInfo(lf_main, "reading dir %s", name.c_str());
|
||||||
|
result = readConfigFiles(name, extension, true, errorDescription);
|
||||||
|
if (result != RESULT_OK) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
logInfo(lf_main, "successfully read dir %s", name.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return RESULT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static BusHandler* executeInstructionsBusHandlerInstance = nullptr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method for immediate reading of a @a Message from the bus.
|
||||||
|
* @param message the @a Message to read.
|
||||||
|
*/
|
||||||
|
static void readMessage(Message* message) {
|
||||||
|
if (!executeInstructionsBusHandlerInstance || !message) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
result_t result = executeInstructionsBusHandlerInstance->readFromBus(message, "");
|
||||||
|
if (result != RESULT_OK) {
|
||||||
|
logError(lf_main, "error reading message %s %s: %s", message->getCircuit().c_str(), message->getName().c_str(),
|
||||||
|
getResultCode(result));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result_t ScanHelper::executeInstructions(BusHandler* busHandler) {
|
||||||
|
string errorDescription;
|
||||||
|
result_t result = m_messages->resolveConditions(m_verbose, &errorDescription);
|
||||||
|
if (result != RESULT_OK) {
|
||||||
|
logError(lf_main, "error resolving conditions: %s, last error: %s", getResultCode(result),
|
||||||
|
errorDescription.c_str());
|
||||||
|
}
|
||||||
|
ostringstream log;
|
||||||
|
executeInstructionsBusHandlerInstance = busHandler;
|
||||||
|
result = m_messages->executeInstructions(&readMessage, &log);
|
||||||
|
if (result != RESULT_OK) {
|
||||||
|
logError(lf_main, "error executing instructions: %s, last error: %s", getResultCode(result),
|
||||||
|
log.str().c_str());
|
||||||
|
} else if (m_verbose && log.tellp() > 0) {
|
||||||
|
logInfo(lf_main, log.str().c_str());
|
||||||
|
}
|
||||||
|
logNotice(lf_main, "found messages: %d (%d conditional on %d conditions, %d poll, %d update)", m_messages->size(),
|
||||||
|
m_messages->sizeConditional(), m_messages->sizeConditions(), m_messages->sizePoll(), m_messages->sizePassive());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result_t ScanHelper::loadDefinitionsFromConfigPath(FileReader* reader, const string& filename,
|
||||||
|
map<string, string>* defaults, string* errorDescription, bool replace) {
|
||||||
|
istream* stream = nullptr;
|
||||||
|
time_t mtime = 0;
|
||||||
|
if (m_configUriPrefix.empty()) {
|
||||||
|
stream = FileReader::openFile(m_configLocalPrefix + filename, errorDescription, &mtime);
|
||||||
|
} else {
|
||||||
|
string content;
|
||||||
|
if (m_configHttpClient
|
||||||
|
&& m_configHttpClient->get(m_configUriPrefix + filename + m_configLangQuery, "", &content, &mtime)) {
|
||||||
|
stream = new istringstream(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result_t result;
|
||||||
|
if (stream) {
|
||||||
|
result = reader->readFromStream(stream, filename, mtime, m_verbose, defaults, errorDescription, replace);
|
||||||
|
delete(stream);
|
||||||
|
} else {
|
||||||
|
result = RESULT_ERR_NOTFOUND;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result_t ScanHelper::loadConfigFiles(bool recursive) {
|
||||||
|
logInfo(lf_main, "loading configuration files from %s", m_configPath.c_str());
|
||||||
|
m_messages->lock();
|
||||||
|
m_messages->clear();
|
||||||
|
m_globalTemplates.clear();
|
||||||
|
for (auto& it : m_templatesByPath) {
|
||||||
|
if (it.second != &m_globalTemplates) {
|
||||||
|
delete it.second;
|
||||||
|
}
|
||||||
|
it.second = nullptr;
|
||||||
|
}
|
||||||
|
m_templatesByPath.clear();
|
||||||
|
|
||||||
|
string errorDescription;
|
||||||
|
result_t result = readConfigFiles("", ".csv", recursive, &errorDescription);
|
||||||
|
if (result == RESULT_OK) {
|
||||||
|
logInfo(lf_main, "read config files, got %d messages", m_messages->size());
|
||||||
|
} else {
|
||||||
|
logError(lf_main, "error reading config files from %s: %s, last error: %s", m_configPath.c_str(),
|
||||||
|
getResultCode(result), errorDescription.c_str());
|
||||||
|
}
|
||||||
|
m_messages->unlock();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result_t ScanHelper::loadScanConfigFile(symbol_t address, string* relativeFile) {
|
||||||
|
Message* message = m_messages->getScanMessage(address);
|
||||||
|
if (!message || message->getLastUpdateTime() == 0) {
|
||||||
|
return RESULT_ERR_NOTFOUND;
|
||||||
|
}
|
||||||
|
const SlaveSymbolString& data = message->getLastSlaveData();
|
||||||
|
if (data.getDataSize() < 1+5+2+2) {
|
||||||
|
logError(lf_main, "unable to load scan config %2.2x: slave part too short (%d)", address, data.getDataSize());
|
||||||
|
return RESULT_EMPTY;
|
||||||
|
}
|
||||||
|
DataFieldSet* identFields = DataFieldSet::getIdentFields();
|
||||||
|
string manufStr, addrStr, ident; // path: cfgpath/MANUFACTURER, prefix: ZZ., ident: C[C[C[C[C]]]], SW: xxxx, HW: xxxx
|
||||||
|
unsigned int sw = 0, hw = 0;
|
||||||
|
ostringstream out;
|
||||||
|
size_t offset = 0;
|
||||||
|
size_t field = 0;
|
||||||
|
bool fromLocal = m_configUriPrefix.empty();
|
||||||
|
// manufacturer name
|
||||||
|
result_t result = (*identFields)[field]->read(data, offset, false, nullptr, -1, OF_NONE, -1, &out);
|
||||||
|
if (result == RESULT_ERR_NOTFOUND && fromLocal) {
|
||||||
|
result = (*identFields)[field]->read(data, offset, false, nullptr, -1, OF_NUMERIC, -1, &out); // manufacturer name
|
||||||
|
}
|
||||||
|
if (result == RESULT_OK) {
|
||||||
|
manufStr = out.str();
|
||||||
|
transform(manufStr.begin(), manufStr.end(), manufStr.begin(), ::tolower);
|
||||||
|
out.str("");
|
||||||
|
out << setw(2) << hex << setfill('0') << nouppercase << static_cast<unsigned>(address);
|
||||||
|
addrStr = out.str();
|
||||||
|
out.str("");
|
||||||
|
out.clear();
|
||||||
|
offset += (*identFields)[field++]->getLength(pt_slaveData, MAX_LEN);
|
||||||
|
result = (*identFields)[field]->read(data, offset, false, nullptr, -1, OF_NONE, -1, &out); // identification string
|
||||||
|
}
|
||||||
|
if (result == RESULT_OK) {
|
||||||
|
ident = out.str();
|
||||||
|
out.str("");
|
||||||
|
out.clear();
|
||||||
|
offset += (*identFields)[field++]->getLength(pt_slaveData, MAX_LEN);
|
||||||
|
result = (*identFields)[field]->read(data, offset, nullptr, -1, &sw); // software version number
|
||||||
|
if (result == RESULT_ERR_OUT_OF_RANGE) {
|
||||||
|
sw = (data.dataAt(offset) << 16) | data.dataAt(offset+1); // use hex value instead
|
||||||
|
result = RESULT_OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result == RESULT_OK) {
|
||||||
|
offset += (*identFields)[field++]->getLength(pt_slaveData, MAX_LEN);
|
||||||
|
result = (*identFields)[field]->read(data, offset, nullptr, -1, &hw); // hardware version number
|
||||||
|
if (result == RESULT_ERR_OUT_OF_RANGE) {
|
||||||
|
hw = (data.dataAt(offset) << 16) | data.dataAt(offset+1); // use hex value instead
|
||||||
|
result = RESULT_OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result != RESULT_OK) {
|
||||||
|
logError(lf_main, "unable to load scan config %2.2x: decode field %s %s", address,
|
||||||
|
identFields->getName(field).c_str(), getResultCode(result));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
bool hasTemplates = false;
|
||||||
|
string best;
|
||||||
|
map<string, string> bestDefaults;
|
||||||
|
vector<string> files;
|
||||||
|
auto it = ident.begin();
|
||||||
|
while (it != ident.end()) {
|
||||||
|
if (*it != '_' && !::isalnum(*it)) {
|
||||||
|
it = ident.erase(it);
|
||||||
|
} else {
|
||||||
|
*it = static_cast<char>(::tolower(*it));
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// find files matching MANUFACTURER/ZZ.*csv in cfgpath
|
||||||
|
string query;
|
||||||
|
if (!fromLocal) {
|
||||||
|
out << "&a=" << addrStr << "&i=" << ident << "&h=" << dec << static_cast<unsigned>(hw) << "&s=" << dec
|
||||||
|
<< static_cast<unsigned>(sw);
|
||||||
|
query = out.str();
|
||||||
|
out.str("");
|
||||||
|
out.clear();
|
||||||
|
}
|
||||||
|
result = collectConfigFiles(manufStr, addrStr + ".", ".csv", &files, false, query, nullptr, &hasTemplates);
|
||||||
|
if (result != RESULT_OK) {
|
||||||
|
logError(lf_main, "unable to load scan config %2.2x: list files in %s %s", address, manufStr.c_str(),
|
||||||
|
getResultCode(result));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (files.empty()) {
|
||||||
|
logError(lf_main, "unable to load scan config %2.2x: no file from %s with prefix %s found", address,
|
||||||
|
manufStr.c_str(), addrStr.c_str());
|
||||||
|
return RESULT_ERR_NOTFOUND;
|
||||||
|
}
|
||||||
|
logDebug(lf_main, "found %d matching scan config files from %s with prefix %s: %s", files.size(), manufStr.c_str(),
|
||||||
|
addrStr.c_str(), getResultCode(result));
|
||||||
|
// complete name: cfgpath/MANUFACTURER/ZZ[.C[C[C[C[C]]]]][.circuit][.suffix][.*][.SWxxxx][.HWxxxx][.*].csv
|
||||||
|
size_t bestMatch = 0;
|
||||||
|
for (const auto& name : files) {
|
||||||
|
symbol_t checkDest;
|
||||||
|
unsigned int checkSw, checkHw;
|
||||||
|
map<string, string> defaults;
|
||||||
|
const string filename = name.substr(manufStr.length()+1);
|
||||||
|
if (!m_messages->extractDefaultsFromFilename(filename, &defaults, &checkDest, &checkSw, &checkHw)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (address != checkDest || (checkSw != UINT_MAX && sw != checkSw) || (checkHw != UINT_MAX && hw != checkHw)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
size_t match = 1;
|
||||||
|
string checkIdent = defaults["name"];
|
||||||
|
if (!checkIdent.empty()) {
|
||||||
|
string remain = ident;
|
||||||
|
bool matches = false;
|
||||||
|
while (remain.length() > 0 && remain.length() >= checkIdent.length()) {
|
||||||
|
if (checkIdent == remain) {
|
||||||
|
matches = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!::isdigit(remain[remain.length()-1])) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
remain.erase(remain.length()-1); // remove trailing digit
|
||||||
|
}
|
||||||
|
if (!matches) {
|
||||||
|
continue; // IDENT mismatch
|
||||||
|
}
|
||||||
|
match += remain.length();
|
||||||
|
}
|
||||||
|
if (match >= bestMatch) {
|
||||||
|
bestMatch = match;
|
||||||
|
best = name;
|
||||||
|
bestDefaults = defaults;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (best.empty()) {
|
||||||
|
logError(lf_main,
|
||||||
|
"unable to load scan config %2.2x: no file from %s with prefix %s matches ID \"%s\", SW%4.4d, HW%4.4d",
|
||||||
|
address, manufStr.c_str(), addrStr.c_str(), ident.c_str(), sw, hw);
|
||||||
|
return RESULT_ERR_NOTFOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
// found the right file. load the templates if necessary, then load the file itself
|
||||||
|
bool readCommon = readTemplates(manufStr, ".csv", hasTemplates);
|
||||||
|
if (readCommon) {
|
||||||
|
result = collectConfigFiles(manufStr, "", ".csv", &files, true, "&a=-");
|
||||||
|
if (result == RESULT_OK && !files.empty()) {
|
||||||
|
for (const auto& name : files) {
|
||||||
|
string baseName = name.substr(manufStr.length()+1, name.length()-manufStr.length()-strlen(".csv")); // *.
|
||||||
|
if (baseName == "_templates.") { // skip templates
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (baseName.length() < 3 || baseName.find_first_of('.') != 2) { // different from the scheme "ZZ."
|
||||||
|
string errorDescription;
|
||||||
|
result = loadDefinitionsFromConfigPath(m_messages, name, nullptr, &errorDescription);
|
||||||
|
if (result == RESULT_OK) {
|
||||||
|
logNotice(lf_main, "read common config file %s", name.c_str());
|
||||||
|
} else {
|
||||||
|
logError(lf_main, "error reading common config file %s: %s, %s", name.c_str(), getResultCode(result),
|
||||||
|
errorDescription.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bestDefaults["name"] = ident;
|
||||||
|
string errorDescription;
|
||||||
|
result = loadDefinitionsFromConfigPath(m_messages, best, &bestDefaults, &errorDescription);
|
||||||
|
if (result != RESULT_OK) {
|
||||||
|
logError(lf_main, "error reading scan config file %s for ID \"%s\", SW%4.4d, HW%4.4d: %s, %s", best.c_str(),
|
||||||
|
ident.c_str(), sw, hw, getResultCode(result), errorDescription.c_str());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
logNotice(lf_main, "read scan config file %s for ID \"%s\", SW%4.4d, HW%4.4d", best.c_str(), ident.c_str(), sw, hw);
|
||||||
|
*relativeFile = best;
|
||||||
|
return RESULT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ScanHelper::parseMessage(const string& arg, bool onlyMasterSlave, MasterSymbolString* master, SlaveSymbolString* slave) {
|
||||||
|
size_t pos = arg.find_first_of('/');
|
||||||
|
if (pos == string::npos) {
|
||||||
|
logError(lf_main, "invalid message %s: missing \"/\"", arg.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
result_t result = master->parseHex(arg.substr(0, pos));
|
||||||
|
if (result == RESULT_OK) {
|
||||||
|
result = slave->parseHex(arg.substr(pos+1));
|
||||||
|
}
|
||||||
|
if (result != RESULT_OK) {
|
||||||
|
logError(lf_main, "invalid message %s: %s", arg.c_str(), getResultCode(result));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (master->size() < 5) { // skip QQ ZZ PB SB NN
|
||||||
|
logError(lf_main, "invalid message %s: master part too short", arg.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!isMaster((*master)[0])) {
|
||||||
|
logError(lf_main, "invalid message %s: QQ is no master", arg.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!isValidAddress((*master)[1], !onlyMasterSlave) || (onlyMasterSlave && isMaster((*master)[1]))) {
|
||||||
|
logError(lf_main, "invalid message %s: ZZ is invalid", arg.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ebusd
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef EBUSD_SCAN_H_
|
||||||
|
#define EBUSD_SCAN_H_
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string>
|
||||||
|
#include <map>
|
||||||
|
#include "lib/ebus/data.h"
|
||||||
|
#include "lib/ebus/message.h"
|
||||||
|
#include "lib/ebus/result.h"
|
||||||
|
#include "lib/utils/httpclient.h"
|
||||||
|
#include "lib/utils/log.h"
|
||||||
|
|
||||||
|
namespace ebusd {
|
||||||
|
|
||||||
|
/** \file ebusd/scan.h
|
||||||
|
* Helpers for handling device scanning and config loading.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class BusHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class for handling device scanning and config loading.
|
||||||
|
*/
|
||||||
|
class ScanHelper : public Resolver {
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
* @param messages the @a MessageMap to load the messages into.
|
||||||
|
* @param configPath the (optionally corrected) config path for retrieving configuration files from.
|
||||||
|
* @param configLocalPrefix the path prefix (including trailing "/") for retrieving configuration files from local files (empty for HTTPS).
|
||||||
|
* @param configUriPrefix the URI prefix (including trailing "/") for retrieving configuration files from HTTPS (empty for local files).
|
||||||
|
* @param configLangQuery the optional language query part for retrieving configuration files from HTTPS (empty for local files).
|
||||||
|
* @param configHttpClient the @a HttpClient for retrieving configuration files from HTTPS.
|
||||||
|
* @param verbose whether to verbosely log problems.
|
||||||
|
*/
|
||||||
|
ScanHelper(MessageMap* messages,
|
||||||
|
const string configPath, const string configLocalPrefix,
|
||||||
|
const string configUriPrefix, const string configLangQuery,
|
||||||
|
HttpClient* configHttpClient, bool verbose)
|
||||||
|
: Resolver(), m_messages(messages),
|
||||||
|
m_configPath(configPath), m_configLocalPrefix(configLocalPrefix),
|
||||||
|
m_configUriPrefix(configUriPrefix), m_configLangQuery(configLangQuery),
|
||||||
|
m_configHttpClient(configHttpClient), m_verbose(verbose) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destructor.
|
||||||
|
*/
|
||||||
|
virtual ~ScanHelper();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to connect to the specified server.
|
||||||
|
* @param host the host name to connect to.
|
||||||
|
* @param port the port to connect to.
|
||||||
|
* @param https true for HTTPS, false for HTTP.
|
||||||
|
* @param timeout the timeout in seconds, defaults to 5 seconds.
|
||||||
|
* @return true on success, false on connect failure.
|
||||||
|
*/
|
||||||
|
bool connect(const string& host, uint16_t port, bool https = false, int timeout = 5);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the @a DataFieldTemplates for the specified configuration file.
|
||||||
|
* @param filename the full name of the configuration file, or "*" to get the non-root templates with the longest name
|
||||||
|
* or the root templates if not available.
|
||||||
|
* @return the @a DataFieldTemplates.
|
||||||
|
*/
|
||||||
|
virtual DataFieldTemplates* getTemplates(const string& filename);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the message definitions from configuration files.
|
||||||
|
* @param recursive whether to load all files recursively.
|
||||||
|
* @return the result code.
|
||||||
|
*/
|
||||||
|
result_t loadConfigFiles(bool recursive = true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the message definitions from a configuration file matching the scan result.
|
||||||
|
* @param address the address of the scan participant
|
||||||
|
* (either master for broadcast master data or slave for read slave data).
|
||||||
|
* @param data the scan @a SlaveSymbolString for which to load the configuration file.
|
||||||
|
* @param relativeFile the string in which the name of the configuration file is stored on success.
|
||||||
|
* @return the result code.
|
||||||
|
*/
|
||||||
|
result_t loadScanConfigFile(symbol_t address, string* relativeFile);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method for executing all loaded and resolvable instructions.
|
||||||
|
* @param busHandler the @a BusHandler instance.
|
||||||
|
* @return the result code.
|
||||||
|
*/
|
||||||
|
result_t executeInstructions(BusHandler* busHandler);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method for loading definitions from a relative file from the config path/URL.
|
||||||
|
* @param reader the @a FileReader instance to load with the definitions.
|
||||||
|
* @param filename the relative name of the file being read.
|
||||||
|
* @param defaults the default values by name (potentially overwritten by file name), or nullptr to not use defaults.
|
||||||
|
* @param errorDescription a string in which to store the error description in case of error.
|
||||||
|
* @param replace whether to replace an already existing entry.
|
||||||
|
* @return @a RESULT_OK on success, or an error code.
|
||||||
|
*/
|
||||||
|
virtual result_t loadDefinitionsFromConfigPath(FileReader* reader, const string& filename,
|
||||||
|
map<string, string>* defaults, string* errorDescription, bool replace = false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method for parsing a master/slave message pair from a command line argument.
|
||||||
|
* @param arg the argument to parse.
|
||||||
|
* @param onlyMasterSlave true to parse only a MS message, false to also parse MM and BC message.
|
||||||
|
* @param master the @a MasterSymbolString to parse into.
|
||||||
|
* @param slave the @a SlaveSymbolString to parse into.
|
||||||
|
* @return true when the argument was valid, false otherwise.
|
||||||
|
*/
|
||||||
|
bool parseMessage(const string& arg, bool onlyMasterSlave, MasterSymbolString* master, SlaveSymbolString* slave);
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* Collect configuration files matching the prefix and extension from the specified path.
|
||||||
|
* @param relPath the relative path from which to collect the files (without trailing "/").
|
||||||
|
* @param prefix the filename prefix the files have to match, or empty.
|
||||||
|
* @param extension the filename extension the files have to match.
|
||||||
|
* @param files the @a vector to which to add the matching files.
|
||||||
|
* @param query the query string suffix for HTTPS retrieval starting with "&", or empty.
|
||||||
|
* @param dirs the @a vector to which to add found directories (without any name check), or nullptr to ignore.
|
||||||
|
* @param hasTemplates the bool to set when the templates file was found in the path, or nullptr to ignore.
|
||||||
|
* @return the result code.
|
||||||
|
*/
|
||||||
|
result_t collectConfigFiles(const string& relPath, const string& prefix, const string& extension,
|
||||||
|
vector<string>* files,
|
||||||
|
bool ignoreAddressPrefix = false, const string& query = "",
|
||||||
|
vector<string>* dirs = nullptr, bool* hasTemplates = nullptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the @a DataFieldTemplates for the specified path if necessary.
|
||||||
|
* @param relPath the relative path from which to read the files (without trailing "/").
|
||||||
|
* @param extension the filename extension of the files to read.
|
||||||
|
* @param available whether the templates file is available in the path.
|
||||||
|
* @return false when the templates for the path were already loaded before, true when the templates for the path were added (independent from @a available).
|
||||||
|
* @return the @a DataFieldTemplates.
|
||||||
|
*/
|
||||||
|
bool readTemplates(const string relPath, const string extension, bool available);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the configuration files from the specified path.
|
||||||
|
* @param relPath the relative path from which to read the files (without trailing "/").
|
||||||
|
* @param extension the filename extension of the files to read.
|
||||||
|
* @param recursive whether to load all files recursively.
|
||||||
|
* @param errorDescription a string in which to store the error description in case of error.
|
||||||
|
* @return the result code.
|
||||||
|
*/
|
||||||
|
result_t readConfigFiles(const string& relPath, const string& extension, bool recursive,
|
||||||
|
string* errorDescription);
|
||||||
|
|
||||||
|
/** the @a MessageMap instance. */
|
||||||
|
MessageMap* m_messages;
|
||||||
|
|
||||||
|
/** the (optionally corrected) config path for retrieving configuration files from. */
|
||||||
|
const string m_configPath;
|
||||||
|
|
||||||
|
/** the path prefix (including trailing "/") for retrieving configuration files from local files (empty for HTTPS). */
|
||||||
|
const string m_configLocalPrefix;
|
||||||
|
|
||||||
|
/** the URI prefix (including trailing "/") for retrieving configuration files from HTTPS (empty for local files). */
|
||||||
|
const string m_configUriPrefix;
|
||||||
|
|
||||||
|
/** the optional language query part for retrieving configuration files from HTTPS (empty for local files). */
|
||||||
|
const string m_configLangQuery;
|
||||||
|
|
||||||
|
/** the @a HttpClient for retrieving configuration files from HTTPS. */
|
||||||
|
HttpClient* m_configHttpClient;
|
||||||
|
|
||||||
|
/** whether to verbosely log problems. */
|
||||||
|
const bool m_verbose;
|
||||||
|
|
||||||
|
/** the global @a DataFieldTemplates. */
|
||||||
|
DataFieldTemplates m_globalTemplates;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the loaded @a DataFieldTemplates by relative path (may also carry
|
||||||
|
* @a globalTemplates as replacement for missing file).
|
||||||
|
*/
|
||||||
|
map<string, DataFieldTemplates*> m_templatesByPath;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ebusd
|
||||||
|
|
||||||
|
#endif // EBUSD_SCAN_H_
|
||||||
@@ -94,11 +94,6 @@ static const char* defaultMessageFieldMap[] = { // access level not included in
|
|||||||
/** the m_pollOrder of the last polled message. */
|
/** the m_pollOrder of the last polled message. */
|
||||||
static unsigned int g_lastPollOrder = 0;
|
static unsigned int g_lastPollOrder = 0;
|
||||||
|
|
||||||
extern DataFieldTemplates* getTemplates(const string& filename);
|
|
||||||
|
|
||||||
extern result_t loadDefinitionsFromConfigPath(FileReader* reader, const string& filename, bool verbose,
|
|
||||||
map<string, string>* defaults, string* errorDescription, bool replace = false);
|
|
||||||
|
|
||||||
|
|
||||||
Message::Message(const string& filename, const string& circuit, const string& level, const string& name,
|
Message::Message(const string& filename, const string& circuit, const string& level, const string& name,
|
||||||
bool isWrite, bool isPassive, const map<string, string>& attributes,
|
bool isWrite, bool isPassive, const map<string, string>& attributes,
|
||||||
@@ -1738,7 +1733,11 @@ void Instruction::getDestination(ostringstream* ostream) const {
|
|||||||
|
|
||||||
result_t LoadInstruction::execute(MessageMap* messages, ostringstream* log) {
|
result_t LoadInstruction::execute(MessageMap* messages, ostringstream* log) {
|
||||||
string errorDescription;
|
string errorDescription;
|
||||||
result_t result = loadDefinitionsFromConfigPath(messages, m_filename, false, &m_defaults, &errorDescription);
|
Resolver* resolver = messages->getResolver();
|
||||||
|
if (!resolver) {
|
||||||
|
return RESULT_ERR_MISSING_ARG;
|
||||||
|
}
|
||||||
|
result_t result = resolver->loadDefinitionsFromConfigPath(messages, m_filename, &m_defaults, &errorDescription);
|
||||||
if (log->tellp() > 0) {
|
if (log->tellp() > 0) {
|
||||||
*log << ", ";
|
*log << ", ";
|
||||||
}
|
}
|
||||||
@@ -2306,7 +2305,10 @@ result_t MessageMap::addFromFile(const string& filename, unsigned int lineNo, ma
|
|||||||
return RESULT_ERR_INVALID_ARG;
|
return RESULT_ERR_INVALID_ARG;
|
||||||
}
|
}
|
||||||
result = RESULT_ERR_EOF;
|
result = RESULT_ERR_EOF;
|
||||||
DataFieldTemplates* templates = getTemplates(filename);
|
if (!m_resolver) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
DataFieldTemplates* templates = m_resolver->getTemplates(filename);
|
||||||
bool hasMulti = types.find(VALUE_SEPARATOR) != string::npos;
|
bool hasMulti = types.find(VALUE_SEPARATOR) != string::npos;
|
||||||
istringstream stream(types);
|
istringstream stream(types);
|
||||||
string type;
|
string type;
|
||||||
|
|||||||
+52
-1
@@ -1255,6 +1255,43 @@ class LoadedFileInfo {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for resolving templates and loading additional message definitions.
|
||||||
|
*/
|
||||||
|
class Resolver {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
Resolver() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destructor.
|
||||||
|
*/
|
||||||
|
virtual ~Resolver() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the @a DataFieldTemplates for the specified configuration file.
|
||||||
|
* @param filename the full name of the configuration file, or "*" to get the non-root templates with the longest name
|
||||||
|
* or the root templates if not available.
|
||||||
|
* @return the @a DataFieldTemplates.
|
||||||
|
*/
|
||||||
|
virtual DataFieldTemplates* getTemplates(const string& filename) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load definitions from a relative file from the config path/URL.
|
||||||
|
* @param reader the @a FileReader instance to load with the definitions.
|
||||||
|
* @param filename the relative name of the file being read.
|
||||||
|
* @param defaults the default values by name (potentially overwritten by file name), or nullptr to not use defaults.
|
||||||
|
* @param errorDescription a string in which to store the error description in case of error.
|
||||||
|
* @param replace whether to replace an already existing entry.
|
||||||
|
* @return @a RESULT_OK on success, or an error code.
|
||||||
|
*/
|
||||||
|
virtual result_t loadDefinitionsFromConfigPath(FileReader* reader, const string& filename,
|
||||||
|
map<string, string>* defaults, string* errorDescription, bool replace = false) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds a map of all known @a Message instances.
|
* Holds a map of all known @a Message instances.
|
||||||
*/
|
*/
|
||||||
@@ -1267,7 +1304,7 @@ class MessageMap : public MappedFileReader {
|
|||||||
* @param deleteData whether to delete the scan message @a DataField during @a Message destruction.
|
* @param deleteData whether to delete the scan message @a DataField during @a Message destruction.
|
||||||
*/
|
*/
|
||||||
explicit MessageMap(bool addAll = false, const string& preferLanguage = "", bool deleteData = true)
|
explicit MessageMap(bool addAll = false, const string& preferLanguage = "", bool deleteData = true)
|
||||||
: MappedFileReader::MappedFileReader(true, preferLanguage),
|
: MappedFileReader::MappedFileReader(true, preferLanguage), m_resolver(nullptr),
|
||||||
m_addAll(addAll), m_additionalScanMessages(false), m_maxIdLength(0), m_maxBroadcastIdLength(0),
|
m_addAll(addAll), m_additionalScanMessages(false), m_maxIdLength(0), m_maxBroadcastIdLength(0),
|
||||||
m_messageCount(0), m_conditionalMessageCount(0), m_passiveMessageCount(0) {
|
m_messageCount(0), m_conditionalMessageCount(0), m_passiveMessageCount(0) {
|
||||||
m_scanMessage = Message::createScanMessage(false, deleteData);
|
m_scanMessage = Message::createScanMessage(false, deleteData);
|
||||||
@@ -1289,6 +1326,17 @@ class MessageMap : public MappedFileReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the @a Resolver instance.
|
||||||
|
* @param the @a Resolver instance.
|
||||||
|
*/
|
||||||
|
void setResolver(Resolver* resolver) { m_resolver = resolver; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the @a Resolver instance.
|
||||||
|
*/
|
||||||
|
Resolver* getResolver() const { return m_resolver; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a @a Message instance to this set.
|
* Add a @a Message instance to this set.
|
||||||
* @param message the @a Message instance to add.
|
* @param message the @a Message instance to add.
|
||||||
@@ -1575,6 +1623,9 @@ class MessageMap : public MappedFileReader {
|
|||||||
/** empty vector for @a getLoadedFiles(). */
|
/** empty vector for @a getLoadedFiles(). */
|
||||||
static vector<string> s_noFiles;
|
static vector<string> s_noFiles;
|
||||||
|
|
||||||
|
/** the @a Resolver instance. */
|
||||||
|
Resolver* m_resolver;
|
||||||
|
|
||||||
/** whether to add all messages, even if duplicate. */
|
/** whether to add all messages, even if duplicate. */
|
||||||
const bool m_addAll;
|
const bool m_addAll;
|
||||||
|
|
||||||
|
|||||||
@@ -54,27 +54,29 @@ DataFieldTemplates* templates = nullptr;
|
|||||||
|
|
||||||
namespace ebusd {
|
namespace ebusd {
|
||||||
|
|
||||||
DataFieldTemplates* getTemplates(const string& filename) {
|
class TestResolver : public Resolver {
|
||||||
if (filename == "") { // avoid compiler warning
|
public:
|
||||||
|
virtual DataFieldTemplates* getTemplates(const string& filename) {
|
||||||
|
if (filename == "") { // avoid compiler warning
|
||||||
|
return templates;
|
||||||
|
}
|
||||||
return templates;
|
return templates;
|
||||||
}
|
}
|
||||||
return templates;
|
|
||||||
}
|
|
||||||
|
|
||||||
result_t loadDefinitionsFromConfigPath(FileReader* reader, const string& filename, bool verbose,
|
virtual result_t loadDefinitionsFromConfigPath(FileReader* reader, const string& filename,
|
||||||
map<string, string>* defaults, string* errorDescription, bool replace = false) {
|
map<string, string>* defaults, string* errorDescription, bool replace = false) {
|
||||||
time_t mtime = 0;
|
time_t mtime = 0;
|
||||||
istream* stream = FileReader::openFile(filename, errorDescription, &mtime);
|
istream* stream = FileReader::openFile(filename, errorDescription, &mtime);
|
||||||
result_t result;
|
result_t result;
|
||||||
if (stream) {
|
if (stream) {
|
||||||
result = reader->readFromStream(stream, filename, mtime, verbose, defaults, errorDescription);
|
result = reader->readFromStream(stream, filename, mtime, false, defaults, errorDescription);
|
||||||
delete(stream);
|
delete(stream);
|
||||||
} else {
|
} else {
|
||||||
result = RESULT_ERR_NOTFOUND;
|
result = RESULT_ERR_NOTFOUND;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
return result;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace ebusd
|
} // namespace ebusd
|
||||||
|
|
||||||
@@ -208,6 +210,7 @@ int main() {
|
|||||||
templates->readLineFromStream(&dummystr, __FILE__, false, &lineNo, &row, &errorDescription, false, nullptr, nullptr);
|
templates->readLineFromStream(&dummystr, __FILE__, false, &lineNo, &row, &errorDescription, false, nullptr, nullptr);
|
||||||
lineNo = 0;
|
lineNo = 0;
|
||||||
MessageMap* messages = new MessageMap("");
|
MessageMap* messages = new MessageMap("");
|
||||||
|
messages->setResolver(new TestResolver());
|
||||||
dummystr.clear();
|
dummystr.clear();
|
||||||
dummystr.str("#");
|
dummystr.str("#");
|
||||||
messages->readLineFromStream(&dummystr, __FILE__, false, &lineNo, &row, &errorDescription, false, nullptr, nullptr);
|
messages->readLineFromStream(&dummystr, __FILE__, false, &lineNo, &row, &errorDescription, false, nullptr, nullptr);
|
||||||
|
|||||||
Reference in New Issue
Block a user