/* * ConfiguratorTemplate.h * * Created on: 13.01.2018 * Author: Micha Mueller */ #ifndef SRC_CONFIGURATORTEMPLATE_H_ #define SRC_CONFIGURATORTEMPLATE_H_ #include #include #include #include #include #include "ConfiguratorInterface.h" #include "SensorBase.h" #include "SensorGroupTemplate.h" //#define STRCMP(node,str) boost::iequals(node.first,str) //DEPRECATED #define CFG_VAL boost::property_tree::iptree& #define ADD BOOST_FOREACH(boost::property_tree::iptree::value_type &val, config) #define ATTRIBUTE(name,setter) do { if (boost::iequals(val.first, name)) { s.setter(val.second.data()); } } while(0) #define SETTING(name) if (boost::iequals(val.first, name)) /** * Non-virtual interface template for the configurators. */ template class ConfiguratorTemplate : public ConfiguratorInterface { //the template shall only be instantiated for classes which derive from SensorBase/SensorGroup static_assert(std::is_base_of::value, "SBase must derive from SensorBase!"); static_assert(std::is_base_of::value, "SGroup must derive from SensorGroupInterface!"); protected: typedef std::map sGroupMap_t; typedef std::map sEntityMap_t; public: ConfiguratorTemplate() : _entityName("INVALID"), _groupName("INVALID"), _baseName("INVALID"), _cfgPath(""), _mqttPrefix(""), _cacheInterval(900000) {} ConfiguratorTemplate(const ConfiguratorTemplate&) = delete; virtual ~ConfiguratorTemplate() { for (auto g : _sensorGroups) { delete g; } for (auto e : _sensorEntitys) { delete e; } for (auto tg : _templateSensorGroups) { delete tg.second; } for (auto te : _templateSensorEntitys) { delete te.second; } _sensorGroupInterfaces.clear(); _sensorGroups.clear(); _sensorEntitys.clear(); _templateSensorGroups.clear(); _templateSensorEntitys.clear(); } ConfiguratorTemplate& operator=(const ConfiguratorTemplate&) = delete; /** * Read in the given configuration * * Overwriting this method is only required if a custom logic is really necessary! * * @param cfgPath Path to the config-file * * @return True on success, false otherwise */ bool readConfig(std::string cfgPath) { _cfgPath = cfgPath; boost::property_tree::iptree cfg; boost::property_tree::read_info(cfgPath, cfg); //read global variables (if present overwrite those from global.conf) readGlobal(cfg); //read groups and templates for groups. If present also entity/-template stuff BOOST_FOREACH(boost::property_tree::iptree::value_type &val, cfg) { //TODO allow for template sensors? //TODO allow single sensors for convenience? //template entity if (boost::iequals(val.first, "template_" + _entityName)) { LOG(debug) << "Template " << _entityName << " \"" << val.second.data() << "\""; if (!val.second.empty()) { //name of an entity is only used to identify templates and is not stored otherwise SEntity* entity = new SEntity(); if (readSensorEntity(*entity, val.second, true)) { auto ret = _templateSensorEntitys.insert(std::pair(val.second.data(), entity)); if(!ret.second) { LOG(warning) << "Template " << _entityName << " " << val.second.data() << " already exists! Omitting..."; delete entity; } } else { LOG(warning) << "Template " << _entityName << " \"" << val.second.data() << "\" has bad values! Ignoring..."; delete entity; } } //template group } else if (boost::iequals(val.first, "template_" + _groupName)) { LOG(debug) << "Template " << _groupName << " \"" << val.second.data() << "\""; if (!val.second.empty()) { SGroup* group = new SGroup(val.second.data()); if (readSensorGroup(*group, val.second)) { auto ret = _templateSensorGroups.insert(std::pair(val.second.data(), group)); if(!ret.second) { LOG(warning) << "Template " << _groupName << " " << val.second.data() << " already exists! Omitting..."; delete group; } } else { LOG(warning) << "Template " << _groupName << " \"" << val.second.data() << "\" has bad values! Ignoring..."; delete group; } } //template single sensor } else if (boost::iequals(val.first, "template_single_" + _baseName)) { LOG(debug) << "Template single " << _baseName << " \"" << val.second.data() << "\""; if (!val.second.empty()) { SGroup* group = new SGroup(val.second.data()); if (readSensorGroup(*group, val.second)) { //group which consists of only one sensor SBase* sensor = new SBase(val.second.data()); if (readSensorBase(*sensor, val.second)) { group->pushBackSensor(sensor); auto ret = _templateSensorGroups.insert(std::pair(val.second.data(), group)); if(!ret.second) { LOG(warning) << "Template single " << _baseName << " " << val.second.data() << " already exists! Omitting..."; delete group; } } else { LOG(warning) << "Template single " << _baseName << " " << val.second.data() << " could not be read! Omitting"; delete group; } } else { LOG(warning) << "Template single " << _baseName << " \"" << val.second.data() << "\" has bad values! Ignoring..."; delete group; } } //entity } else if (boost::iequals(val.first, _entityName)) { LOG(debug) << _entityName << " \"" << val.second.data() << "\""; if (!val.second.empty()) { SEntity* entity = new SEntity(); if (readSensorEntity(*entity, val.second, false)) { _sensorEntitys.push_back(entity); } else { LOG(warning) << _entityName << " \"" << val.second.data() << "\" has bad values! Ignoring..."; delete entity; } } //group } else if (boost::iequals(val.first, _groupName)) { LOG(debug) << _groupName << " \"" << val.second.data() << "\""; if (!val.second.empty()) { SGroup* group = new SGroup(val.second.data()); if (readSensorGroup(*group, val.second)) { storeSensorGroup(group); } else { LOG(warning) << _groupName << " \"" << val.second.data() << "\" has bad values! Ignoring..."; delete group; } } //single sensor } else if (boost::iequals(val.first, "single_" + _baseName)) { LOG(debug) << "Single " << _baseName << " \"" << val.second.data() << "\""; if (!val.second.empty()) { SGroup* group = new SGroup(val.second.data()); if (readSensorGroup(*group, val.second)) { //group which consists of only one sensor SBase* sensor; //perhaps one sensor is already present because it was copied from the template group if (group->getSensors().size() != 0) { sensor = dynamic_cast(group->getSensors()[0]); sensor->setName(val.second.data()); if (readSensorBase(*sensor, val.second)) { storeSensorGroup(group); } else { LOG(warning) << "Single " << _baseName << " " << val.second.data() << " could not be read! Omitting"; delete group; } } else { sensor = new SBase(val.second.data()); if (readSensorBase(*sensor, val.second)) { group->pushBackSensor(sensor); storeSensorGroup(group); } else { LOG(warning) << "Single " << _baseName << " " << val.second.data() << " could not be read! Omitting"; delete group; } } } else { LOG(warning) << "Single " << _baseName << " \"" << val.second.data() << "\" has bad values! Ignoring..."; delete group; } } } } //read of config finished. Now we build the mqtt-topic for every sensor for(auto g : _sensorGroups) { for(auto s : g->getSensors()) { s->setMqtt(_mqttPrefix + g->getMqttPart() + s->getMqtt()); LOG(debug) << g->getGroupName() << "::" << s->getName() << " using MQTT-topic \"" << s->getMqtt() << "\""; } } return true; } /** * Clear internal storage and read in the configuration again. * * @return True on success, false otherwise */ bool reReadConfig() final { //bring everything to a halt for(auto g : _sensorGroups) { g->stop(); } //wait until everything is halted for(auto g : _sensorGroups) { g->wait(); } //clean up sensors/groups/entitys and templates for(auto g : _sensorGroups) { delete g; } for(auto e : _sensorEntitys) { delete e; } for (auto tg : _templateSensorGroups) { delete tg.second; } for (auto te : _templateSensorEntitys) { delete te.second; } _sensorGroupInterfaces.clear(); _sensorGroups.clear(); _sensorEntitys.clear(); _templateSensorGroups.clear(); _templateSensorEntitys.clear(); //back to the very beginning return readConfig(_cfgPath); } /** * Sets internal variables with the ones provided by pluginSettings. * This method should be called once after constructing a configurator * to provide him with the global default values. * * @param pluginSettings Struct with global default settings for the plugins. */ void setGlobalSettings(const pluginSettings_t& pluginSettings) final { _mqttPrefix = pluginSettings.mqttPrefix; _cacheInterval = pluginSettings.cacheInterval; derivedSetGlobalSettings(pluginSettings); } /** * Get all sensor groups * * @return Vector containing pointers to all sensor groups of this plugin */ std::vector& getSensorGroups() final { return _sensorGroupInterfaces; } protected: void storeSensorGroup(SGroup* sGroup) { _sensorGroups.push_back(sGroup); _sensorGroupInterfaces.push_back(sGroup); } /** * Non-virtual interface method for class-internal use only. * Reads and sets the common base values of a sensor base (currently none), * then calls the corresponding derived function to read plugin specific * values. * * @param sBase The sensor base for which to set the values * @param config A boost property (sub-)tree containing the sensor values * * @return True on success, false otherwise */ bool readSensorBase(SBase& sBase, CFG_VAL config) { //TODO default templates useful? BOOST_FOREACH(boost::property_tree::iptree::value_type &val, config) { if (boost::iequals(val.first, "mqttsuffix")) { sBase.setMqtt(val.second.data()); } } sensorBase(sBase, config); return true; } /** * Non-virtual interface method for class-internal use only. * Reads and sets the common base values of a sensor group, then calls * the corresponding derived function to read in plugin specific values. * * @param sGroup The sensor group for which to set the values * @param config A boost property (sub-)tree containing the sensor values * * @return True on success, false otherwise */ bool readSensorGroup(SGroup& sGroup, CFG_VAL config) { sGroup.setCacheInterval(_cacheInterval); //first check if default group is given boost::optional def = config.get_child_optional("default"); if(def) { //we copy all values from default (including copy constructing its sensors) //if own sensors are specified they are appended LOG(debug) << " Using \"" << def.get().data() << "\" as default."; auto it = _templateSensorGroups.find(def.get().data()); if(it != _templateSensorGroups.end()) { sGroup = *(it->second); sGroup.setGroupName(config.data()); } else { LOG(warning) << "Template " << _groupName << "\"" << def.get().data() << "\" not found! Using standard values."; } } //read in values inherited from SensorGroupInterface BOOST_FOREACH(boost::property_tree::iptree::value_type &val, config) { if (boost::iequals(val.first, "interval")) { sGroup.setInterval(stoull(val.second.data())); } else if (boost::iequals(val.first, "minValues")) { sGroup.setMinValues(stoull(val.second.data())); } else if (boost::iequals(val.first, "mqttPart")) { sGroup.setMqttPart(val.second.data()); } else if (boost::iequals(val.first, _baseName)) { LOG(debug) << " " << _baseName << " " << val.second.data(); SBase* sensor = new SBase(val.second.data()); if (readSensorBase(*sensor, val.second)) { sGroup.pushBackSensor(sensor); } else { LOG(warning) << _baseName << " " << sGroup.getGroupName() << "::" << sensor->getName() << " could not be read! Omitting"; delete sensor; } } } //TODO keep debug logging for config? // LOG(debug) << " Interval : " << sGroup.getInterval(); // LOG(debug) << " minValues: " << sGroup.getMinValues(); sensorGroup(sGroup, config); return true; } /** * Non-virtual interface method for class-internal use only. * Reads and sets the common base values of a sensor entity, then calls * the corresponding derived function to read in plugin specific values. * * @param sEntity The aggregating entity for which to set the values * @param config A boost property (sub-)tree containing the sensor values * @param isTemplate Indicate if sEntity is a template. If so, also store * the corresponding sGroups in the template map * * @return True on success, false otherwise */ bool readSensorEntity(SEntity& sEntity, CFG_VAL config, bool isTemplate) { //first check if default entity is given boost::optional def = config.get_child_optional("default"); if(def) { //we copy all values from default LOG(debug) << " Using \"" << def.get().data() << "\" as default."; auto it = _templateSensorEntitys.find(def.get().data()); if(it != _templateSensorEntitys.end()) { sEntity = *(it->second); for(auto g : _templateSensorGroups) { if (isEntityOfGroup(*(it->second), *(g.second))) { SGroup* group = new SGroup(*(g.second)); setEntityForGroup(sEntity, *group); storeSensorGroup(group); } } } else { LOG(warning) << "Template " << _entityName << "\"" << def.get().data() << "\" not found! Using standard values."; } } sensorEntity(sEntity, config); BOOST_FOREACH(boost::property_tree::iptree::value_type &val, config) { if (boost::iequals(val.first, _groupName)) { LOG(debug) << " " << _groupName << " " << val.second.data(); if (!val.second.empty()) { SGroup* group = new SGroup(val.second.data()); if(readSensorGroup(*group, val.second)) { setEntityForGroup(sEntity, *group); if (isTemplate) { auto ret = _templateSensorGroups.insert(std::pair(val.second.data(), group)); if(!ret.second) { LOG(warning) << "Template " << _groupName << " " << val.second.data() << " already exists! Omitting..."; delete group; } } else { storeSensorGroup(group); } } else { LOG(warning) << _groupName << " " << group->getGroupName() << " could not be read! Omitting"; delete group; } } } else if (boost::iequals(val.first, "single_" + _baseName)) { LOG(debug) << "Single " << _baseName << " \"" << val.second.data() << "\""; if (!val.second.empty()) { SGroup* group = new SGroup(val.second.data()); //group which consists of only one sensor if (readSensorGroup(*group, val.second)) { setEntityForGroup(sEntity, *group); if (isTemplate) { SBase* sensor = new SBase(val.second.data()); if (readSensorBase(*sensor, val.second)) { group->pushBackSensor(sensor); auto ret = _templateSensorGroups.insert(std::pair(val.second.data(), group)); if(!ret.second) { LOG(warning) << "Template single " << _baseName << " " << val.second.data() << " already exists! Omitting..."; delete group; } } else { LOG(warning) << "Template single " << _baseName << " " << val.second.data() << " could not be read! Omitting"; delete group; } } else { SBase* sensor; //perhaps one sensor is already present because it was copied from the template group if (group->getSensors().size() != 0) { sensor = dynamic_cast(group->getSensors()[0]); sensor->setName(val.second.data()); if (readSensorBase(*sensor, val.second)) { storeSensorGroup(group); } else { LOG(warning) << "Single " << _baseName << " " << val.second.data() << " could not be read! Omitting"; delete group; } } else { sensor = new SBase(val.second.data()); if (readSensorBase(*sensor, val.second)) { group->pushBackSensor(sensor); storeSensorGroup(group); } else { LOG(warning) << "Single " << _baseName << " " << val.second.data() << " could not be read! Omitting"; delete group; } } } } else { LOG(warning) << "Single " << _baseName << " \"" << val.second.data() << "\" has bad values! Ignoring..."; delete group; } } } } if(!isTemplate) { for(auto g : _sensorGroups) { if(isEntityOfGroup(sEntity, *g)) { finalizeGroup(*g); } } } return true; } bool readGlobal(CFG_VAL config) { boost::optional globalVals = config.get_child_optional("global"); if (globalVals) { BOOST_FOREACH(boost::property_tree::iptree::value_type &global, config.get_child("global")) { if (boost::iequals(global.first, "mqttprefix")) { _mqttPrefix = global.second.data(); if (_mqttPrefix[_mqttPrefix.length()-1] != '/') { _mqttPrefix.append("/"); } LOG(debug) << " Using own MQTT-Prefix " << _mqttPrefix; } else if (boost::iequals(global.first, "cacheInterval")) { _cacheInterval = stoul(global.second.data()); LOG(debug) << " Using own caching interval " << _cacheInterval << " [s]"; _cacheInterval *= 1000; } } global(config.get_child("global")); } return true; } /** * Virtual interface method, responsible for setting global values specifically * for its plugin. * * @param pluginSettings The struct with global default plugin settings */ virtual void derivedSetGlobalSettings(const pluginSettings_t& pluginSettings) { //Overwrite if necessary } /** * Pure virtual interface method, responsible for reading plugin-specific sensor * base values. * * @param s The sensor base for which to set the values * @param config A boost property (sub-)tree containing the sensor values */ virtual void sensorBase(SBase& s, CFG_VAL config) = 0; /** * Pure virtual interface method, responsible for reading plugin-specific sensor * group values. * * @param s The sensor group for which to set the values * @param config A boost property (sub-)tree containing the group values */ virtual void sensorGroup(SGroup& s, CFG_VAL config) = 0; /** * Virtual interface method, responsible for reading plugin-specific sensor * entity values. * * @param s The sensor entity for which to set the values * @param config A boost property (sub-)tree containing the entity values */ virtual void sensorEntity(SEntity& s, CFG_VAL config) { //Overwrite if necessary LOG(warning) << "Method sensorEntity called, but was not overwritten! Either you have unwanted entitys in your config file or forgot to overwrite this method"; } /** * Check if e is the corresponding entity of g * * @param e * @param g * * @return True if (g.entity == &e) */ //TODO not very convenient for writing plugins. Are there better solutions? virtual bool isEntityOfGroup(SEntity& e, SGroup& g) { //Overwrite if necessary LOG(warning) << "Method isEntityOfGroup called, but was not overwritten! Either you have unwanted entitys in your config file or forgot to overwrite this method"; return false; } /** * Sets e as entity for group g * * @param e * @param g */ //TODO not very convenient for writing plugins. Are there better solutions? virtual void setEntityForGroup(SEntity& e, SGroup& g) { //Overwrite if necessary LOG(warning) << "Method setEntityForGroup called, but was not overwritten! Either you have unwanted entitys in your config file or forgot to overwrite this method"; } /** * Finalize the group g with everything it needs from its entity (set e.g. the mqttPart for entity) * * @param g */ //TODO not very convenient for writing plugins. Are there better solutions? virtual void finalizeGroup(SGroup& g) { //Overwrite if necessary LOG(warning) << "Method finalizeEntityGroup called, but was not overwritten! Either you have unwanted entitys in your config file or forgot to overwrite this method"; } /** * Virtual interface method, responsible for reading plugin-specific global values. * * @param config A boost property (sub-)tree containing the global values */ virtual void global(CFG_VAL config) {} std::string _entityName; std::string _groupName; std::string _baseName; std::string _cfgPath; std::string _mqttPrefix; unsigned int _cacheInterval; std::vector _sensorGroupInterfaces; std::vector _sensorGroups; std::vector _sensorEntitys; sGroupMap_t _templateSensorGroups; sEntityMap_t _templateSensorEntitys; }; #endif /* SRC_CONFIGURATORTEMPLATE_H_ */