/* * ConfiguratorTemplate.h * * Created on: 13.01.2018 * Author: Micha Mueller, Carla Guillen */ #ifndef SRC_CONFIGURATORTEMPLATE_H_ #define SRC_CONFIGURATORTEMPLATE_H_ #include #include #include #include #include #include #include "ConfiguratorInterface.h" #include "sensorbase.h" #include "SensorGroupTemplate.h" #include "version.h" #include #include #include #include //#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)) #define DEFAULT_CACHE_INTERVAL 900000 /** * 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 sBaseMap_t; typedef std::map sGroupMap_t; typedef std::map sEntityMap_t; using SB_Ptr = std::shared_ptr; using SG_Ptr = std::shared_ptr; const char COMMA = ','; const char OPEN_SQBRKET = '['; const char CLOSE_SQBRKET = ']'; const char DASH = '-'; public: ConfiguratorTemplate() : _entityName("INVALID"), _groupName("INVALID"), _baseName("INVALID"), _cfgPath(""), _mqttPrefix(""), _cacheInterval(DEFAULT_CACHE_INTERVAL) {} ConfiguratorTemplate(const ConfiguratorTemplate&) = delete; virtual ~ConfiguratorTemplate() { for (auto e : _sensorEntitys) { delete e; } for (auto tb : _templateSensorBases) { delete tb.second; } for (auto tg : _templateSensorGroups) { delete tg.second; } for (auto te : _templateSensorEntitys) { delete te.second; } _sensorGroupInterfaces.clear(); _sensorGroups.clear(); _sensorEntitys.clear(); _templateSensorBases.clear(); _templateSensorGroups.clear(); _templateSensorEntitys.clear(); } ConfiguratorTemplate& operator=(const ConfiguratorTemplate&) = delete; std::string getVersion() { return std::string(VERSION); } /** * 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) { //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, true)) { 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 base } else if (boost::iequals(val.first, "template_" + _baseName)) { LOG(debug) << "Template " << _baseName << " \"" << val.second.data() << "\""; if (!val.second.empty()) { SBase* base = new SBase(val.second.data()); if (readSensorBase(*base, val.second, true)) { auto ret = _templateSensorBases.insert(std::pair(val.second.data(), base)); if(!ret.second) { LOG(warning) << "Template " << _baseName << " " << val.second.data() << " already exists! Omitting..."; delete base; } } else { LOG(warning) << "Template " << _baseName << " \"" << val.second.data() << "\" has bad values! Ignoring..."; delete base; } } //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, true)) { //group which consists of only one sensor SB_Ptr sensor = std::make_shared(val.second.data()); if (readSensorBase(*sensor, val.second, true)) { 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()) { SG_Ptr group = std::make_shared(val.second.data()); if (readSensorGroup(*group, val.second)) { storeSensorGroup(group); } else { LOG(warning) << _groupName << " \"" << val.second.data() << "\" has bad values! Ignoring..."; } } //single sensor } else if (boost::iequals(val.first, "single_" + _baseName)) { LOG(debug) << "Single " << _baseName << " \"" << val.second.data() << "\""; if (!val.second.empty()) { SG_Ptr group = std::make_shared(val.second.data()); if (readSensorGroup(*group, val.second)) { //group which consists of only one sensor SB_Ptr sensor; //perhaps one sensor is already present because it was copied from the template group if (group->getSensors().size() != 0) { sensor = std::dynamic_pointer_cast(group->getSensors()[0]); //check if cast was successful (sensor != nullptr) if (sensor) { 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"; } } else { LOG(warning) << "Single " << _baseName << " " << val.second.data() << " had a type mismatch when casting! Omitting"; } } else { sensor = std::make_shared(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"; } } } else { LOG(warning) << "Single " << _baseName << " \"" << val.second.data() << "\" has bad values! Ignoring..."; } } } else if( !boost::iequals(val.first, "global") ) { LOG(error) << "\"" << val.first << "\": unknown construct!"; return false; } } //read of config finished. Now we build the mqtt-topic for every sensor return constructSensorTopics(); } /** * Clear internal storage and returns the plugin to the uninitialized state. */ void clearConfig() 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 e : _sensorEntitys) { delete e; } for (auto tb : _templateSensorBases) { delete tb.second; } for (auto tg : _templateSensorGroups) { delete tg.second; } for (auto te : _templateSensorEntitys) { delete te.second; } _sensorGroupInterfaces.clear(); _sensorGroups.clear(); _sensorEntitys.clear(); _templateSensorBases.clear(); _templateSensorGroups.clear(); _templateSensorEntitys.clear(); } /** * Clear internal storage and read in the configuration again. * * @return True on success, false otherwise */ bool reReadConfig() final { clearConfig(); //back to the very beginning return readConfig(_cfgPath); } /** * Print configuration as read in. * * @param ll Logging level to log with */ void printConfig(LOG_LEVEL ll) final { LOG_VAR(ll) << " General: "; if (_mqttPrefix != "") { LOG_VAR(ll) << " MQTT-Prefix: " << _mqttPrefix; } else { LOG_VAR(ll) << " MQTT-Prefix: DEFAULT"; } if (_cacheInterval != DEFAULT_CACHE_INTERVAL) { LOG_VAR(ll) << " Cache interval: " << _cacheInterval << " ms"; } else { LOG_VAR(ll) << " Cache interval: DEFAULT"; } //prints plugin specific configurator attributes and entities if present printConfiguratorConfig(ll); LOG_VAR(ll) << " Groups:"; for(auto g : _sensorGroups) { g->SensorGroupInterface::printConfig(ll); g->printConfig(ll); g->SensorGroupTemplate::printConfig(ll); } } /** * 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(SG_Ptr 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 * @param isTemplate Are we parsing a template or a regular sensor? * * @return True on success, false otherwise */ bool readSensorBase(SBase& sBase, CFG_VAL config, bool isTemplate=false) { sBase.setCacheInterval(_cacheInterval); if (!isTemplate) { 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 = _templateSensorBases.find(def.get().data()); if(it != _templateSensorBases.end()) { sBase = *(it->second); sBase.setName(config.data()); } else { LOG(warning) << "Template " << _baseName << "\" " << def.get().data() << "\" not found! Using standard values."; } } } BOOST_FOREACH(boost::property_tree::iptree::value_type &val, config) { if (boost::iequals(val.first, "mqttsuffix")) { sBase.setMqtt(val.second.data()); } else if (boost::iequals(val.first, "skipConstVal")) { sBase.setSkipConstVal(to_bool(val.second.data())); } else if (boost::iequals(val.first, "delta")) { sBase.setDelta(to_bool(val.second.data())); } else if (boost::iequals(val.first, "sink")) { sBase.setSinkPath( val.second.data() ); } else if (boost::iequals(val.first, "subSampling")) { sBase.setSubsampling(std::stoul(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 * @param isTemplate Is this a template or a regular group? * * @return True on success, false otherwise */ bool readSensorGroup(SGroup& sGroup, CFG_VAL config, bool isTemplate=false) { //first check if default group is given, unless we are reading a template already if (!isTemplate) { 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, "sync")) { if (val.second.data() == "off") { sGroup.setSync(false); } else { sGroup.setSync(true); } } else if (boost::iequals(val.first, _baseName)) { if (!isTemplate) { LOG(debug) << " " << _baseName << " " << val.second.data(); } SB_Ptr sensor = std::make_shared(val.second.data()); if (readSensorBase(*sensor, val.second, isTemplate)) { sGroup.pushBackSensor(sensor); } else if (!isTemplate) { LOG(warning) << _baseName << " " << sGroup.getGroupName() << "::" << sensor->getName() << " could not be read! Omitting"; } } } //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))) { SG_Ptr group = std::make_shared(*(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()) { if (isTemplate) { SGroup* group = new SGroup(val.second.data()); if(readSensorGroup(*group, val.second)) { setEntityForGroup(sEntity, *group); 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) << _groupName << " " << group->getGroupName() << " could not be read! Omitting"; delete group; } } else { SG_Ptr group = std::make_shared(val.second.data()); if(readSensorGroup(*group, val.second)) { setEntityForGroup(sEntity, *group); storeSensorGroup(group); } else { LOG(warning) << _groupName << " " << group->getGroupName() << " could not be read! Omitting"; } } } } else if (boost::iequals(val.first, "single_" + _baseName)) { LOG(debug) << "Single " << _baseName << " \"" << val.second.data() << "\""; if (!val.second.empty()) { if (isTemplate) { SGroup* group = new SGroup(val.second.data()); //group which consists of only one sensor if (readSensorGroup(*group, val.second)) { setEntityForGroup(sEntity, *group); SB_Ptr sensor = std::make_shared(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) << "Single " << _baseName << " \"" << val.second.data() << "\" has bad values! Ignoring..."; delete group; } } else { SG_Ptr group = std::make_shared(val.second.data()); //group which consists of only one sensor if (readSensorGroup(*group, val.second)) { setEntityForGroup(sEntity, *group); SB_Ptr sensor; //perhaps one sensor is already present because it was copied from the template group if (group->getSensors().size() != 0) { sensor = std::dynamic_pointer_cast(group->getSensors()[0]); //check if cast was successful (sensor != nullptr) if (sensor) { 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"; } } else { LOG(warning) << "Single " << _baseName << " " << val.second.data() << " had a type mismatch when casting! Omitting"; } } else { sensor = std::make_shared(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"; } } } else { LOG(warning) << "Single " << _baseName << " \"" << val.second.data() << "\" has bad values! Ignoring..."; } } } } } if(!isTemplate) { for(const 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("/"); } } else if (boost::iequals(global.first, "cacheInterval")) { _cacheInterval = stoul(global.second.data()); _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"; } /** * Print information about configurable configurator attributes (or nothing if no such attributes are required) * and associated entities. * * @param ll Severity level to log with */ virtual void printConfiguratorConfig(LOG_LEVEL ll) { //Overwrite if necessary LOG_VAR(ll) << " No other plugin-specific general parameters or entities defined"; } /** * 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) {} /** * Tries to parse the given cpuString as integer numbers. On success, the specified numbers will be inserted * into a set, which will be returned. On failure, an empty set is returned. A set is used to maintain uniqueness * and an ascending order among the numbers although this is not strictly required. * * @param cpuString String which specifies a range and/or set of numbers (e.g. "1,2,3-5,7-9,10") * @return A set of integers as specified in the cpuString. If the string could not be parsed the set will be empty. */ std::set parseCpuString(const std::string& cpuString) { std::set cpus; int maxCpu = 512; std::vector subStrings; std::stringstream ssComma(cpuString); std::string item; while (std::getline(ssComma, item, ',')) { subStrings.push_back(item); } for (auto s : subStrings) { if (s.find('-') != std::string::npos) { //range of values (e.g. 1-5) specified std::stringstream ssHyphen(s); std::string min, max; std::getline(ssHyphen, min, '-'); std::getline(ssHyphen, max); try { int minVal = stoi(min); int maxVal = stoi(max); for (int i = minVal; i <= maxVal; i++) { if (i >= 0 && i < maxCpu) { cpus.insert((int)i); } } } catch (const std::exception& e) { LOG(debug) << "Could not parse values \"" << min << "-" << max << "\""; } } else { //single value try { int val = stoi(s); if (val >= 0 && val < maxCpu) { cpus.insert((int)val); } } catch (const std::exception& e) { LOG(debug) << "Could not parse value \"" << s << "\""; } } } if (cpus.empty()) { LOG(warning) << " CPUs could not be parsed!"; } else { std::stringstream sstream; sstream << " CPUS: "; for (auto i : cpus) { sstream << i << ", "; } std::string msg = sstream.str(); msg.pop_back(); msg.pop_back(); LOG(debug) << msg; } return cpus; } /** * @brief Adjusts the names of the sensors in generated groups. * * @return true if successful, false otherwise */ bool constructSensorTopics() { // Sensor names are adjusted according to the respective MQTT topics for(auto& g: _sensorGroups) for(auto& s: g->getSensors()) { s->setMqtt(MQTTChecker::formatTopic(_mqttPrefix) + MQTTChecker::formatTopic(g->getMqttPart()) + MQTTChecker::formatTopic(s->getMqtt())); s->setName(s->getMqtt()); } return true; } 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; sBaseMap_t _templateSensorBases; sGroupMap_t _templateSensorGroups; sEntityMap_t _templateSensorEntitys; }; #endif /* SRC_CONFIGURATORTEMPLATE_H_ */