diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index 259950a7..be9e04ea 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -168,6 +168,12 @@ static const struct argp_option argpoptions[] = { {NULL, 0, NULL, 0, NULL, 0 }, }; +/** the global @a DataFieldTemplates. */ +static DataFieldTemplates globalTemplates; + +/** the loaded @a DataFieldTemplates by sub path. */ +static map templatesByPath; + /** * The program argument parsing function. * @param key the key from @a argpoptions. @@ -441,6 +447,12 @@ void shutdown() delete mainLoop; mainLoop = NULL; } + // free templates + globalTemplates.clear(); // TODO should be unnecessary due to dtor + for (map::iterator it = templatesByPath.begin(); it != templatesByPath.end(); it++) { + delete it->second; + } + templatesByPath.clear(); // reset all signal handlers to default signal(SIGHUP, SIG_DFL); @@ -563,18 +575,18 @@ static result_t readConfigFiles(const string path, const string extension, DataF return RESULT_OK; }; -result_t loadConfigFiles(DataFieldTemplates* templates, MessageMap* messages, bool recursive, bool verbose) { +result_t loadConfigFiles(MessageMap* messages, bool recursive, bool verbose) { logInfo(lf_main, "loading configuration files from %s", opt.configPath); string path = string(opt.configPath); messages->clear(); - templates->clear(); - result_t result = templates->readFromFile(path+"/_templates.csv", NULL, verbose); + globalTemplates.clear(); + result_t result = globalTemplates.readFromFile(path+"/_templates.csv", NULL, verbose); if (result == RESULT_OK) logInfo(lf_main, "read templates"); else - logError(lf_main, "error reading templates: %s, %s", getResultCode(result), templates->getLastError().c_str()); + logError(lf_main, "error reading templates: %s, %s", getResultCode(result), globalTemplates.getLastError().c_str()); - result = readConfigFiles(path, ".csv", templates, messages, recursive, verbose); + result = readConfigFiles(path, ".csv", &globalTemplates, messages, recursive, verbose); if (result == RESULT_OK) logInfo(lf_main, "read config files"); else @@ -589,26 +601,22 @@ result_t loadConfigFiles(DataFieldTemplates* templates, MessageMap* messages, bo return result; } -static map templatesByPath; - -result_t loadScanConfigFile(DataFieldTemplates* templates, MessageMap* messages, SymbolString& master, SymbolString& slave) { - if (master.size()<5) - return RESULT_EMPTY; - unsigned char zz = master[1]; - SymbolString& data = master; +result_t loadScanConfigFile(MessageMap* messages, unsigned char address, SymbolString& data) { PartType partType; - if (zz == BROADCAST) { - zz = (unsigned char)(master[0]+5); // slave address of sending master + if (isMaster(address)) { + address = (unsigned char)(data[0]+5); // slave address of sending master partType = pt_masterData; + if (data.size()<5+1+5+2+2) // skip QQ ZZ PB SB NN + return RESULT_EMPTY; } else { partType = pt_slaveData; - data = slave; // TODO check + if (data.size()<1+1+5+2+2) // skip NN + return RESULT_EMPTY; } DataFieldSet* identFields = DataFieldSet::getIdentFields(); - // MANUFACTURER/ZZ. ( C.S.H., C.H., C.S., S.H., C., S., H., "" ) csv - string path, prefix, ident, sw, hw; // path: cfgpath/MANUFCATUER, prefix: ZZ., ident: C[C[C[C[C]]]], sw: xxxx, hw: xxxx + // MANUFACTURER/ZZ. ( C.S.H., C.H., C.S., S.H., C., S., H., "" ) .*csv + string path, prefix, ident, sw, hw; // path: cfgpath/MANUFACTURER, prefix: ZZ., ident: C[C[C[C[C]]]], sw: xxxx, hw: xxxx ostringstream out; - out << opt.configPath << "/"; unsigned char offset = 0; size_t field = 0; result_t result = (*identFields)[field]->read(partType, data, offset, out, 0); // manufacturer name @@ -616,8 +624,10 @@ result_t loadScanConfigFile(DataFieldTemplates* templates, MessageMap* messages, result = (*identFields)[field]->read(partType, data, offset, out, OF_NUMERIC); // manufacturer name if (result==RESULT_OK) { path = out.str(); + transform(path.begin(), path.end(), path.begin(), ::tolower); + path = string(opt.configPath) + "/" + path; out.str(""); - out << setw(2) << hex << setfill('0') << nouppercase << static_cast(zz) << "."; + out << setw(2) << hex << setfill('0') << nouppercase << static_cast(address) << "."; prefix = out.str(); out.str(""); out.clear(); @@ -636,20 +646,29 @@ result_t loadScanConfigFile(DataFieldTemplates* templates, MessageMap* messages, offset = (unsigned char)(offset+(*identFields)[field++]->getLength(partType)); result = (*identFields)[field]->read(partType, data, offset, out, 0); // hardware version number } + if (result!=RESULT_OK) { + logDebug(lf_main, "load scan config files: %s", getResultCode(result)); + return result; + } vector files; if (result==RESULT_OK) { hw = out.str(); result = collectConfigFiles(path, prefix, ".csv", files); } + logDebug(lf_main, "found %d matching scan config files from %s with prefix %s: %s", files.size(), path.c_str(), prefix.c_str(), getResultCode(result)); if (result!=RESULT_OK) return result; if (files.empty()) return RESULT_ERR_NOTFOUND; // complete name: cfgpath/MANUFACTURER/ZZ[.C[C[C[C[C]]]]][.SWxxxx][.HWxxxx][.*].csv - // format ident string: remove trailing spaces - //TODO ident.erase(remove_if(ident.begin(), ident.end(), static_cast(&std::isspace))); - transform(ident.begin(), ident.end(), ident.begin(), ::tolower); + for (string::iterator it = ident.begin(); it!=ident.end(); it++) { + if (::isspace(*it)) { + ident.erase(it--); + } else { + *it = (char)::tolower(*it); + } + } size_t prefixLen = path.length()+1+prefix.length()-1; size_t bestMatch = 0; string best; @@ -675,7 +694,7 @@ result_t loadScanConfigFile(DataFieldTemplates* templates, MessageMap* messages, } } pos = name.find(".", 1); // check for ".C[C[C[C[C]]]]." - if (pos!=string::npos && pos>1 && pos<=6) { // up to 5 chars between two "."s + if (ident.length()>0 && pos!=string::npos && pos>1 && pos<=6) { // up to 5 chars between two "."s, immediately after "ZZ." string check = name.substr(1, pos-1); string remain = ident; bool matches = false; @@ -705,24 +724,42 @@ result_t loadScanConfigFile(DataFieldTemplates* templates, MessageMap* messages, return RESULT_ERR_NOTFOUND; // found the right file. load the templates if necessary, then load the file itself - if (templates==NULL) { - map::iterator it = templatesByPath.find(path); - if (it==templatesByPath.end()) { - templates = new DataFieldTemplates(); - templatesByPath[path] = templates; - } else { - templates = it->second; - } - } - if (templates!=NULL) { // && !readTemplates.contains(path+"/_templates.csv")) { + DataFieldTemplates* templates = NULL; + map::iterator it = templatesByPath.find(path); + if (it==templatesByPath.end()) { + templates = new DataFieldTemplates(globalTemplates); result = templates->readFromFile(path+"/_templates.csv", NULL); if (result == RESULT_OK) logNotice(lf_main, "read templates in %s for scan %s", path.c_str(), ident.c_str()); else logError(lf_main, "error reading templates in %s for scan %s: %s, %s", path.c_str(), ident.c_str(), getResultCode(result), templates->getLastError().c_str()); + templatesByPath[path] = templates; + result = collectConfigFiles(path, "", ".csv", files); + if (result==RESULT_OK && !files.empty()) { + for (vector::iterator it = files.begin(); it!=files.end(); it++) { + string name = *it; + name = name.substr(path.length()+1, name.length()-path.length()-strlen(".csv")); // *. + if (name=="_templates.") // skip templates + continue; + if (name.length()<3 || name.find_first_of('.')!=2) { // different from the scheme "ZZ." + name = *it; + result = messages->readFromFile(name, templates); + if (result==RESULT_OK) + logNotice(lf_main, "read common config file %s for scan %s", name.c_str(), ident.c_str()); + else + logError(lf_main, "error reading common config file %s for scan %s: %s", name.c_str(), ident.c_str(), getResultCode(result)); + } + } + } + } else { + templates = it->second; } - logNotice(lf_main, "reading file %s for scan %s", best.c_str(), ident.c_str()); - return messages->readFromFile(best, templates); + result = messages->readFromFile(best, templates); + if (result==RESULT_OK) + logNotice(lf_main, "read config file %s for scan %s", best.c_str(), ident.c_str()); + else + logError(lf_main, "error reading config file %s for scan %s: %s", best.c_str(), ident.c_str(), getResultCode(result)); + return result; } @@ -752,19 +789,18 @@ int main(int argc, char* argv[]) if (argp_parse(&argp, argc, argv, ARGP_IN_ORDER, NULL, &opt) != 0) return EINVAL; - DataFieldTemplates templates; MessageMap messages = MessageMap(opt.checkConfig && opt.scanConfig); if (opt.checkConfig) { logNotice(lf_main, "Performing configuration check..."); - result_t result = loadConfigFiles(&templates, &messages, !opt.scanConfig, true); + result_t result = loadConfigFiles(&messages, !opt.scanConfig, true); if (result == RESULT_OK && opt.checkConfig > 1) { logNotice(lf_main, "Configuration dump:"); messages.dump(cout); } messages.clear(); - templates.clear(); + globalTemplates.clear(); return 0; } @@ -789,13 +825,14 @@ int main(int argc, char* argv[]) logNotice(lf_main, PACKAGE_STRING " started"); // load configuration files - loadConfigFiles(&templates, &messages, !opt.scanConfig); + loadConfigFiles(&messages, !opt.scanConfig); if (messages.sizeConditions()>0 && opt.pollInterval==0) logError(lf_main, "conditions require a poll interval > 0"); // create the MainLoop and run it - mainLoop = new MainLoop(opt, device, &templates, &messages); - mainLoop->run(); + mainLoop = new MainLoop(opt, device, &messages); + mainLoop->start("mainloop"); + mainLoop->join(); // shutdown shutdown(); diff --git a/src/ebusd/main.h b/src/ebusd/main.h index 8421c662..281b1c18 100644 --- a/src/ebusd/main.h +++ b/src/ebusd/main.h @@ -64,22 +64,20 @@ struct options /** * Load the message definitions from configuration files. - * @param templates the @a DataFieldTemplates to load the templates into. * @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. * @return the result code. */ -result_t loadConfigFiles(DataFieldTemplates* templates, MessageMap* messages, bool recursive=true, bool verbose=false); +result_t loadConfigFiles(MessageMap* messages, bool recursive=true, bool verbose=false); /** * Load the message definitions from a configuration file matching the scan result. - * @param templates the @a DataFieldTemplates to load the necessary templates into, or NULL. * @param messages the @a MessageMap to load the messages into. - * @param master the scan master @a SymbolString for which to load the configuration file. - * @param slave the scan slave @a SymbolString for which to load the configuration file. + * @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 SymbolString for which to load the configuration file. * @return the result code. */ -result_t loadScanConfigFile(DataFieldTemplates* templates, MessageMap* messages, SymbolString& master, SymbolString& slave); +result_t loadScanConfigFile(MessageMap* messages, unsigned char address, SymbolString& data); #endif // MAIN_H_ diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index 316cddcf..983af4eb 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -43,8 +43,8 @@ static const char* columnNames[] = { /** the number of known column names. */ static const size_t columnCount = sizeof(columnNames) / sizeof(char*); -MainLoop::MainLoop(const struct options opt, Device *device, DataFieldTemplates* templates, MessageMap* messages) - : m_device(device), m_templates(templates), m_messages(messages), m_address(opt.address) +MainLoop::MainLoop(const struct options opt, Device *device, MessageMap* messages) + : m_device(device), m_messages(messages), m_address(opt.address), m_scanConfig(opt.scanConfig) { // setup Device m_device->setLogRaw(opt.logRaw); @@ -90,20 +90,54 @@ MainLoop::~MainLoop() } m_messages->clear(); - m_templates->clear(); } void MainLoop::run() { bool running = true; + time_t lastTaskRun, now; + int taskDelay = 5; + unsigned char lastScanAddress = 0; // 0 is known to be a master + time(&now); + lastTaskRun = now; while (running) { string result; // pick the next message to handle - NetMessage* message = m_netQueue.pop(5); + NetMessage* message = m_netQueue.pop(taskDelay); + time(&now); + if (now lastTaskRun+taskDelay) { + logDebug(lf_main, "performing regular tasks"); + if (m_scanConfig) { + lastScanAddress = m_busHandler->getNextScanAddress(lastScanAddress); + if (lastScanAddress==SYN) { + taskDelay = 5; + lastScanAddress = 0; + } else { + SymbolString slave(false); + result_t result = m_busHandler->scanAndWait(lastScanAddress, slave); + taskDelay = (result == RESULT_ERR_NO_SIGNAL) ? 10 : 1; + if (result!=RESULT_OK) + logError(lf_main, "scan config %2.2x message: %s", lastScanAddress, getResultCode(result)); + else { + logInfo(lf_main, "scan config %2.2x message received", lastScanAddress); + result = loadScanConfigFile(m_messages, lastScanAddress, slave); + if (result!=RESULT_OK) + logError(lf_main, "scan config %2.2x file: %s", lastScanAddress, getResultCode(result)); + else { + logInfo(lf_main, "scan config %2.2x file loaded", lastScanAddress); + m_busHandler->setScanConfigLoaded(lastScanAddress); + } + } + } + } + time(&lastTaskRun); + } if (message==NULL) { - // TODO perform regular tasks continue; } string request = message->getRequest(); @@ -913,7 +947,7 @@ string MainLoop::executeReload(vector &args) return "usage: reload\n" " Reload CSV config files."; - result_t result = loadConfigFiles(m_templates, m_messages); + result_t result = loadConfigFiles(m_messages); return getResultCode(result); } @@ -933,7 +967,7 @@ string MainLoop::executeInfo(vector &args) { if (args.size() == 0) return "usage: info\n" - " Report information about the daemon."; + " Report information about the daemon, the configuration, and seen devices."; ostringstream result; result << "version: " << PACKAGE_STRING << "\n"; @@ -945,7 +979,7 @@ string MainLoop::executeInfo(vector &args) } result << "masters: " << static_cast(m_busHandler->getMasterCount()) << "\n"; result << "messages: " << static_cast(m_messages->size()); - + m_busHandler->formatSeenInfo(result); return result.str(); } @@ -970,6 +1004,7 @@ string MainLoop::executeHelp() " find|f Find message(s): find [-v] [-r] [-w] [-p] [-d] [-i PB] [-f] [-F COL[,COL]*] [-e] [-c CIRCUIT] [NAME]\n" " listen|l Listen for updates: listen [stop]\n" " state|s Report bus state\n" + " info|i Report information about the daemon, the configuration, and seen devices.\n" " grab|g Grab unknown messages: grab [stop]\n" " Report the messages: grab result\n" " scan Scan slaves: scan [full]\n" @@ -982,7 +1017,6 @@ string MainLoop::executeHelp() " dump Toggle dumping raw bytes\n" " reload Reload CSV config files\n" " stop Stop the daemon\n" - " info|i Report information about the daemon\n" " quit|q Close connection\n" " help|h Print help help [COMMAND]"; } diff --git a/src/ebusd/mainloop.h b/src/ebusd/mainloop.h index a4e7987f..99dab76c 100644 --- a/src/ebusd/mainloop.h +++ b/src/ebusd/mainloop.h @@ -31,7 +31,7 @@ using namespace std; /** * The main loop handling requests from connected clients. */ -class MainLoop +class MainLoop : public Thread { public: @@ -39,10 +39,9 @@ public: * Construct the main loop and create network and bus handling components. * @param opt the program options. * @param device the @a Device instance. - * @param templates the @a DataFieldTemplates instance. * @param messages the @a MessageMap instance. */ - MainLoop(const struct options opt, Device *device, DataFieldTemplates* templates, MessageMap* messages); + MainLoop(const struct options opt, Device *device, MessageMap* messages); /** * Destructor. @@ -65,14 +64,14 @@ private: /** the @a Device instance. */ Device* m_device; - /** the @a DataFieldTemplates instance. */ - DataFieldTemplates* m_templates; - /** the @a MessageMap instance. */ MessageMap* m_messages; /** the own master address for sending on the bus. */ - unsigned char m_address; + const unsigned char m_address; + + /** whether to pick configuration files matching initial scan. */ + const bool m_scanConfig; /** the created @a BusHandler instance. */ BusHandler* m_busHandler;