The expiration time for new job artifacts in CI/CD pipelines is now 30 days (GitLab default). Previously generated artifacts in already completed jobs will not be affected by the change. The latest artifacts for all jobs in the latest successful pipelines will be kept. More information: https://gitlab.lrz.de/help/user/admin_area/settings/continuous_integration.html#default-artifacts-expiration

ConfiguratorTemplate.h 30.4 KB
Newer Older
1
/*
2
 * ConfiguratorTemplate.h
3
4
 *
 *  Created on: 13.01.2018
5
 *      Author: Micha Mueller, Carla Guillen
6
7
 */

8
9
#ifndef SRC_CONFIGURATORTEMPLATE_H_
#define SRC_CONFIGURATORTEMPLATE_H_
10

11
#include <map>
Alessio Netti's avatar
Alessio Netti committed
12
#include <set>
13
14
15
16
17

#include <boost/foreach.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/info_parser.hpp>
Alessio Netti's avatar
Alessio Netti committed
18
#include <boost/regex.hpp>
19
#include "ConfiguratorInterface.h"
20
#include "sensorbase.h"
21
#include "SensorGroupTemplate.h"
22
#include "version.h"
23

Alessio Netti's avatar
Alessio Netti committed
24
25
26
27
#include <iostream>
#include <sstream>
#include <iomanip>

28
//#define STRCMP(node,str) boost::iequals(node.first,str) //DEPRECATED
Micha Mueller's avatar
Micha Mueller committed
29
#define CFG_VAL	boost::property_tree::iptree&
30

31
#define ADD						BOOST_FOREACH(boost::property_tree::iptree::value_type &val, config)
32
#define ATTRIBUTE(name,setter)		do { if (boost::iequals(val.first, name)) { s.setter(val.second.data()); } } while(0)
Micha Mueller's avatar
Micha Mueller committed
33
#define SETTING(name)				if (boost::iequals(val.first, name))
34

35
36
#define DEFAULT_CACHE_INTERVAL 900000

Micha Mueller's avatar
Micha Mueller committed
37
/**
38
 * Non-virtual interface template for the configurators.
Micha Mueller's avatar
Micha Mueller committed
39
 */
40
template <class SBase, class SGroup, class SEntity = nullptr_t>
41
class ConfiguratorTemplate : public ConfiguratorInterface {
42
	//the template shall only be instantiated for classes which derive from SensorBase/SensorGroup
43
	static_assert(std::is_base_of<SensorBase, SBase>::value, "SBase must derive from SensorBase!");
44
	static_assert(std::is_base_of<SensorGroupInterface, SGroup>::value, "SGroup must derive from SensorGroupInterface!");
45

46
protected:
Michael Ott's avatar
Michael Ott committed
47
	typedef std::map<std::string, SBase*> sBaseMap_t;
48
49
	typedef std::map<std::string, SGroup*> sGroupMap_t;
	typedef std::map<std::string, SEntity*> sEntityMap_t;
50

51
52
53
	using SB_Ptr = std::shared_ptr<SBase>;
	using SG_Ptr = std::shared_ptr<SGroup>;

54
55
56
57
58
	const char COMMA = ',';
	const char OPEN_SQBRKET = '[';
	const char CLOSE_SQBRKET = ']';
	const char DASH = '-';

Alessio Netti's avatar
Alessio Netti committed
59
60
	const std::string SENSOR_PATTERN = "(?i)<sensor>";
	const std::string GROUP_PATTERN = "(?i)<group>";
Alessio Netti's avatar
Alessio Netti committed
61

62
public:
63
	ConfiguratorTemplate() :
64
65
66
		_entityName("INVALID"),
		_groupName("INVALID"),
		_baseName("INVALID"),
67
68
		_cfgPath(""),
		_mqttPrefix(""),
Alessio Netti's avatar
Alessio Netti committed
69
		_sensorPattern(""),
70
		_cacheInterval(DEFAULT_CACHE_INTERVAL) {}
71

72
73
	ConfiguratorTemplate(const ConfiguratorTemplate&) = delete;

74
	virtual ~ConfiguratorTemplate() {
75
76
77
		for (auto e : _sensorEntitys) {
			delete e;
		}
Michael Ott's avatar
Michael Ott committed
78
79
80
		for (auto tb : _templateSensorBases) {
			delete tb.second;
		}
81
		for (auto tg : _templateSensorGroups) {
82
			delete tg.second;
83
84
		}
		for (auto te : _templateSensorEntitys) {
85
			delete te.second;
86
		}
87
		_sensorGroupInterfaces.clear();
88
89
		_sensorGroups.clear();
		_sensorEntitys.clear();
Michael Ott's avatar
Michael Ott committed
90
		_templateSensorBases.clear();
91
		_templateSensorGroups.clear();
92
		_templateSensorEntitys.clear();
93
	}
94

95
96
	ConfiguratorTemplate& operator=(const ConfiguratorTemplate&) = delete;

97
98
99
	std::string getVersion() {
		return std::string(VERSION);
	}
Micha Mueller's avatar
Micha Mueller committed
100
101
	/**
	 * Read in the given configuration
102
	 *
103
104
	 * Overwriting this method is only required if a custom logic is really necessary!
	 *
Micha Mueller's avatar
Micha Mueller committed
105
	 * @param	cfgPath Path to the config-file
106
107
	 *
	 * @return	True on success, false otherwise
Micha Mueller's avatar
Micha Mueller committed
108
	 */
109
	bool readConfig(std::string cfgPath) {
110
		_cfgPath = cfgPath;
111
112
113
114
115

		boost::property_tree::iptree cfg;
		boost::property_tree::read_info(cfgPath, cfg);

		//read global variables (if present overwrite those from global.conf)
116
117
		readGlobal(cfg);

118
119
120
121
122
123
		//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()) {
Micha Mueller's avatar
Micha Mueller committed
124
125
126
					//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)) {
127
						auto ret = _templateSensorEntitys.insert(std::pair<std::string, SEntity*>(val.second.data(), entity));
128
						if(!ret.second) {
129
							LOG(warning) << "Template " << _entityName << " " << val.second.data() << " already exists! Omitting...";
Micha Mueller's avatar
Micha Mueller committed
130
							delete entity;
131
132
133
						}
					} else {
						LOG(warning) << "Template " << _entityName << " \"" << val.second.data() << "\" has bad values! Ignoring...";
Micha Mueller's avatar
Micha Mueller committed
134
						delete entity;
135
136
137
138
139
140
141
					}
				}
			//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());
142
					if (readSensorGroup(*group, val.second, true)) {
143
						auto ret = _templateSensorGroups.insert(std::pair<std::string, SGroup*>(val.second.data(), group));
144
						if(!ret.second) {
145
							LOG(warning) << "Template " << _groupName << " " << val.second.data() << " already exists! Omitting...";
Micha Mueller's avatar
Micha Mueller committed
146
							delete group;
147
						}
148
149
					} else {
						LOG(warning) << "Template " << _groupName << " \"" << val.second.data() << "\" has bad values! Ignoring...";
Micha Mueller's avatar
Micha Mueller committed
150
						delete group;
151
152
					}
				}
Michael Ott's avatar
Michael Ott committed
153
154
155
156
157
			//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());
158
					if (readSensorBase(*base, val.second, true)) {
Michael Ott's avatar
Michael Ott committed
159
160
161
162
163
164
165
166
167
168
						auto ret = _templateSensorBases.insert(std::pair<std::string, SBase*>(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;
					}
				}
169
170
171
172
173
			//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());
174
					if (readSensorGroup(*group, val.second, true)) {
175
						//group which consists of only one sensor
176
						SB_Ptr sensor = std::make_shared<SBase>(val.second.data());
177
						if (readSensorBase(*sensor, val.second, true)) {
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
							group->pushBackSensor(sensor);
							auto ret = _templateSensorGroups.insert(std::pair<std::string, SGroup*>(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;
					}
				}
193
194
195
196
			//entity
			} else if (boost::iequals(val.first, _entityName)) {
				LOG(debug) << _entityName << " \"" << val.second.data() << "\"";
				if (!val.second.empty()) {
Micha Mueller's avatar
Micha Mueller committed
197
198
					SEntity* entity = new SEntity();
					if (readSensorEntity(*entity, val.second, false)) {
199
200
201
						_sensorEntitys.push_back(entity);
					} else {
						LOG(warning) << _entityName << " \"" << val.second.data() << "\" has bad values! Ignoring...";
Micha Mueller's avatar
Micha Mueller committed
202
						delete entity;
203
					}
204
205
206
207
208
				}
			//group
			} else if (boost::iequals(val.first, _groupName)) {
				LOG(debug) << _groupName << " \"" << val.second.data() << "\"";
				if (!val.second.empty()) {
209
					SG_Ptr group = std::make_shared<SGroup>(val.second.data());
210
					if (readSensorGroup(*group, val.second)) {
211
						storeSensorGroup(group);
212
213
214
215
					} else {
						LOG(warning) << _groupName << " \"" << val.second.data() << "\" has bad values! Ignoring...";
					}
				}
216
217
218
219
			//single sensor
			} else if (boost::iequals(val.first, "single_" + _baseName)) {
				LOG(debug) << "Single " << _baseName << " \"" << val.second.data() << "\"";
				if (!val.second.empty()) {
220
					SG_Ptr group = std::make_shared<SGroup>(val.second.data());
221
222
					if (readSensorGroup(*group, val.second)) {
						//group which consists of only one sensor
223
						SB_Ptr sensor;
224
225
						//perhaps one sensor is already present because it was copied from the template group
						if (group->getSensors().size() != 0) {
226
227
228
229
230
231
232
233
234
							sensor = std::dynamic_pointer_cast<SBase>(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";
								}
235
							} else {
236
								LOG(warning) << "Single " << _baseName << " " << val.second.data() << " had a type mismatch when casting! Omitting";
237
238
							}
						} else {
239
							sensor = std::make_shared<SBase>(val.second.data());
240
241
242
243
244
245
246
247
248
249
250
							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...";
					}
				}
Alessio Netti's avatar
Alessio Netti committed
251
			} else if( !boost::iequals(val.first, "global") ) {
252
253
			    LOG(error) << "\"" << val.first << "\": unknown construct!";
			    return false;
254
255
			}
		}
256
		//read of config finished. Now we build the mqtt-topic for every sensor
Alessio Netti's avatar
Alessio Netti committed
257
258
		if(!constructSensorNames())
			return false;
259
260
		for(const auto& g : _sensorGroups) {
			for(const auto& s : g->getSensors()) {
261
262
263
				s->setMqtt(_mqttPrefix + g->getMqttPart() + s->getMqtt());
			}
		}
264
		return true;
265
266
267
	}

	/**
Alessio Netti's avatar
Alessio Netti committed
268
	 * Clear internal storage and returns the plugin to the uninitialized state.
269
	 */
Alessio Netti's avatar
Alessio Netti committed
270
	void clearConfig() final {
Micha Mueller's avatar
Micha Mueller committed
271
272
		//bring everything to a halt
		for(auto g : _sensorGroups) {
273
			g->stop();
Micha Mueller's avatar
Micha Mueller committed
274
275
276
277
278
279
		}

		//wait until everything is halted
		for(auto g : _sensorGroups) {
			g->wait();
		}
280

281
282
283
284
		//clean up sensors/groups/entitys and templates
		for(auto e : _sensorEntitys) {
			delete e;
		}
Michael Ott's avatar
Michael Ott committed
285
286
287
		for (auto tb : _templateSensorBases) {
			delete tb.second;
		}
288
		for (auto tg : _templateSensorGroups) {
289
			delete tg.second;
290
291
		}
		for (auto te : _templateSensorEntitys) {
292
			delete te.second;
293
		}
294
		_sensorGroupInterfaces.clear();
Micha Mueller's avatar
Micha Mueller committed
295
		_sensorGroups.clear();
296
		_sensorEntitys.clear();
Michael Ott's avatar
Michael Ott committed
297
		_templateSensorBases.clear();
298
		_templateSensorGroups.clear();
299
		_templateSensorEntitys.clear();
Alessio Netti's avatar
Alessio Netti committed
300
301
302
303
304
305
306
307
308
	}

	/**
	 * Clear internal storage and read in the configuration again.
	 *
	 * @return	True on success, false otherwise
	 */
	bool reReadConfig() final {
		clearConfig();
309

Micha Mueller's avatar
Micha Mueller committed
310
		//back to the very beginning
311
		return readConfig(_cfgPath);
312
	}
313

314
315
316
317
318
	/**
	 * Print configuration as read in.
	 *
	 * @param ll    Logging level to log with
	 */
319
	void printConfig(LOG_LEVEL ll) final {
320
	  LOG_VAR(ll) << "    General: ";
321
	  if (_mqttPrefix != "") {
322
	    LOG_VAR(ll) << "        MQTT-Prefix: " << _mqttPrefix;
323
	  } else {
324
	    LOG_VAR(ll) << "        MQTT-Prefix: DEFAULT";
325
326
	  }
	  if (_sensorPattern != "") {
327
	    LOG_VAR(ll) << "        Sensor Pattern: " << _sensorPattern;
328
329
	  }
	  if (_cacheInterval != DEFAULT_CACHE_INTERVAL) {
330
	    LOG_VAR(ll) << "        Cache interval: " << _cacheInterval << " ms";
331
	  } else {
332
	    LOG_VAR(ll) << "        Cache interval: DEFAULT";
333
334
	  }

335
	  //prints plugin specific configurator attributes and entities if present
336
337
	  printConfiguratorConfig(ll);

338
	  LOG_VAR(ll) << "    Groups:";
339
	  for(auto g : _sensorGroups) {
340
341
342
	    g->SensorGroupInterface::printConfig(ll);
	    g->printConfig(ll);
	    g->SensorGroupTemplate<SBase>::printConfig(ll);
343
344
345
	  }
	}

346
347
348
349
350
351
352
	/**
	 * 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.
	 */
353
	void setGlobalSettings(const pluginSettings_t& pluginSettings) final {
354
		_mqttPrefix = pluginSettings.mqttPrefix;
Alessio Netti's avatar
Alessio Netti committed
355
		_sensorPattern = pluginSettings.sensorPattern;
356
		_cacheInterval = pluginSettings.cacheInterval;
357
358

		derivedSetGlobalSettings(pluginSettings);
359
360
	}

Micha Mueller's avatar
Micha Mueller committed
361
362
363
364
365
	/**
	 * Get all sensor groups
	 *
	 * @return	Vector containing pointers to all sensor groups of this plugin
	 */
366
	std::vector<SGroupPtr>& getSensorGroups() final {
367
368
369
		return _sensorGroupInterfaces;
	}

370
protected:
371
	void storeSensorGroup(SG_Ptr sGroup) {
372
373
		_sensorGroups.push_back(sGroup);
		_sensorGroupInterfaces.push_back(sGroup);
Micha Mueller's avatar
Micha Mueller committed
374
375
	}

376
377
	/**
	 * Non-virtual interface method for class-internal use only.
378
	 * Reads and sets the common base values of a sensor base (currently none),
379
380
	 * then calls the corresponding derived function to read plugin specific
	 * values.
381
	 *
382
	 * @param sBase		The sensor base for which to set the values
383
	 * @param config	A boost property (sub-)tree containing the sensor values
384
	 * @param isTemplate	Are we parsing a template or a regular sensor?
385
386
387
	 *
	 * @return	True on success, false otherwise
	 */
388
	bool readSensorBase(SBase& sBase, CFG_VAL config, bool isTemplate=false) {
389
		sBase.setCacheInterval(_cacheInterval);
390
391
392
		if (!isTemplate) {
			boost::optional<boost::property_tree::iptree&> def = config.get_child_optional("default");
			if(def) {
393
				//we copy all values from default
394
395
396
397
398
399
400
401
				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.";
				}
Michael Ott's avatar
Michael Ott committed
402
403
404
			}
		}
		
405
406
407
		BOOST_FOREACH(boost::property_tree::iptree::value_type &val, config) {
			if (boost::iequals(val.first, "mqttsuffix")) {
				sBase.setMqtt(val.second.data());
408
			} else if (boost::iequals(val.first, "skipConstVal")) {
Alessio Netti's avatar
Alessio Netti committed
409
				sBase.setSkipConstVal(to_bool(val.second.data()));
410
			} else if (boost::iequals(val.first, "delta")) {
Alessio Netti's avatar
Alessio Netti committed
411
				sBase.setDelta(to_bool(val.second.data()));
412
413
414
			} else if (boost::iequals(val.first, "sink")) {
				sBase.setSinkPath( val.second.data() );
			} else if (boost::iequals(val.first, "subSampling")) {
Alessio Netti's avatar
Alessio Netti committed
415
				sBase.setSubsampling(std::stoul(val.second.data()));
416
			}
417
418
419
		}
		sensorBase(sBase, config);
		return true;
420
421
422
423
	}

	/**
	 * Non-virtual interface method for class-internal use only.
424
425
	 * Reads and sets the common base values of a sensor group, then calls
	 * the corresponding derived function to read in plugin specific values.
426
	 *
427
	 * @param sGroup	The sensor group for which to set the values
428
	 * @param config	A boost property (sub-)tree containing the sensor values
429
	 * @param isTemplate	Is this a template or a regular group?
430
431
432
	 *
	 * @return	True on success, false otherwise
	 */
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
	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<boost::property_tree::iptree&> 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.";
				}
448
449
450
			}
		}

451
		//read in values inherited from SensorGroupInterface
452
		BOOST_FOREACH(boost::property_tree::iptree::value_type &val, config) {
453
			if (boost::iequals(val.first, "interval")) {
454
				sGroup.setInterval(stoull(val.second.data()));
455
			} else if (boost::iequals(val.first, "minValues")) {
456
				sGroup.setMinValues(stoull(val.second.data()));
457
458
			} else if (boost::iequals(val.first, "mqttPart")) {
				sGroup.setMqttPart(val.second.data());
459
460
461
462
463
464
			} else if (boost::iequals(val.first, "sync")) {
				if (val.second.data() == "off") {
					sGroup.setSync(false);
				} else {
					sGroup.setSync(true);
				}
465
			} else if (boost::iequals(val.first, _baseName)) {
466
467
468
				if (!isTemplate) {
					LOG(debug) << "  " << _baseName << " " << val.second.data();
				}
469
				SB_Ptr sensor = std::make_shared<SBase>(val.second.data());
470
				if (readSensorBase(*sensor, val.second, isTemplate)) {
471
					sGroup.pushBackSensor(sensor);
472
				} else if (!isTemplate) {
473
					LOG(warning) << _baseName << " " << sGroup.getGroupName() << "::" << sensor->getName() << " could not be read! Omitting";
474
				}
475
476
477
			}
		}

478
479
480
		//TODO keep debug logging for config?
//		LOG(debug) << "  Interval : " << sGroup.getInterval();
//		LOG(debug) << "  minValues: " << sGroup.getMinValues();
481

482
483
		sensorGroup(sGroup, config);
		return true;
484
485
	}

486
487
488
489
490
491
492
	/**
	 * 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
Micha Mueller's avatar
Micha Mueller committed
493
494
	 * @param isTemplate	Indicate if sEntity is a template. If so, also store
	 * 						the corresponding sGroups in the template map
495
496
497
	 *
	 * @return	True on success, false otherwise
	 */
Micha Mueller's avatar
Micha Mueller committed
498
	bool readSensorEntity(SEntity& sEntity, CFG_VAL config, bool isTemplate) {
499
500
501
502
503
504
505
		//first check if default entity is given
		boost::optional<boost::property_tree::iptree&> 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()) {
506
				sEntity = *(it->second);
Micha Mueller's avatar
Micha Mueller committed
507
508
				for(auto g : _templateSensorGroups) {
					if (isEntityOfGroup(*(it->second), *(g.second))) {
509
						SG_Ptr group = std::make_shared<SGroup>(*(g.second));
Micha Mueller's avatar
Micha Mueller committed
510
						setEntityForGroup(sEntity, *group);
511
						storeSensorGroup(group);
Micha Mueller's avatar
Micha Mueller committed
512
513
					}
				}
514
515
516
517
			} else {
				LOG(warning) << "Template " << _entityName << "\"" << def.get().data() << "\" not found! Using standard values.";
			}
		}
Micha Mueller's avatar
Micha Mueller committed
518

519
		sensorEntity(sEntity, config);
Micha Mueller's avatar
Micha Mueller committed
520
521
522
523

		BOOST_FOREACH(boost::property_tree::iptree::value_type &val, config) {
			if (boost::iequals(val.first, _groupName)) {
				LOG(debug) << "  " << _groupName << " " << val.second.data();
524
				if (!val.second.empty()) {
525
526
527
528
					if (isTemplate) {
						SGroup* group = new SGroup(val.second.data());
						if(readSensorGroup(*group, val.second)) {
							setEntityForGroup(sEntity, *group);
529
530
531
532
533
534
							auto ret = _templateSensorGroups.insert(std::pair<std::string, SGroup*>(val.second.data(), group));
							if(!ret.second) {
								LOG(warning) << "Template " << _groupName << " " << val.second.data() << " already exists! Omitting...";
								delete group;
							}
						} else {
535
536
							LOG(warning) << _groupName << " " << group->getGroupName() << " could not be read! Omitting";
							delete group;
Micha Mueller's avatar
Micha Mueller committed
537
538
						}
					} else {
539
540
541
542
543
544
545
						SG_Ptr group = std::make_shared<SGroup>(val.second.data());
						if(readSensorGroup(*group, val.second)) {
							setEntityForGroup(sEntity, *group);
							storeSensorGroup(group);
						} else {
							LOG(warning) << _groupName << " " << group->getGroupName() << " could not be read! Omitting";
						}
546
547
548
549
550
					}
				}
			} else if (boost::iequals(val.first, "single_" + _baseName)) {
				LOG(debug) << "Single " << _baseName << " \"" << val.second.data() << "\"";
				if (!val.second.empty()) {
551
552
553
554
555
556
					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<SBase>(val.second.data());
557
558
559
560
561
562
563
564
565
566
567
568
							if (readSensorBase(*sensor, val.second)) {
								group->pushBackSensor(sensor);
								auto ret = _templateSensorGroups.insert(std::pair<std::string, SGroup*>(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 {
569
570
571
572
573
574
575
576
577
							LOG(warning) << "Single " << _baseName << " \"" << val.second.data() << "\" has bad values! Ignoring...";
							delete group;
						}
					} else {
						SG_Ptr group = std::make_shared<SGroup>(val.second.data());
						//group which consists of only one sensor
						if (readSensorGroup(*group, val.second)) {
							setEntityForGroup(sEntity, *group);
							SB_Ptr sensor;
578
579
							//perhaps one sensor is already present because it was copied from the template group
							if (group->getSensors().size() != 0) {
580
581
582
583
584
585
586
587
588
								sensor = std::dynamic_pointer_cast<SBase>(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";
									}
589
								} else {
590
									LOG(warning) << "Single " << _baseName << " " << val.second.data() << " had a type mismatch when casting! Omitting";
591
592
								}
							} else {
593
								sensor = std::make_shared<SBase>(val.second.data());
594
595
596
597
598
599
600
								if (readSensorBase(*sensor, val.second)) {
									group->pushBackSensor(sensor);
									storeSensorGroup(group);
								} else {
									LOG(warning) << "Single " << _baseName << " " << val.second.data() << " could not be read! Omitting";
								}
							}
601
602
						} else {
							LOG(warning) << "Single " << _baseName << " \"" << val.second.data() << "\" has bad values! Ignoring...";
603
						}
Micha Mueller's avatar
Micha Mueller committed
604
605
606
607
608
					}
				}
			}
		}

609
		if(!isTemplate) {
610
			for(const auto& g : _sensorGroups) {
611
612
613
				if(isEntityOfGroup(sEntity, *g)) {
					finalizeGroup(*g);
				}
Micha Mueller's avatar
Micha Mueller committed
614
615
			}
		}
616
		return true;
617
618
	}

Micha Mueller's avatar
Micha Mueller committed
619
	bool readGlobal(CFG_VAL config) {
620
621
622
623
624
625
626
627
628
629
630
		boost::optional<boost::property_tree::iptree&> 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;
Alessio Netti's avatar
Alessio Netti committed
631
632
				} else if (boost::iequals(global.first, "sensorpattern")) {
					_sensorPattern = global.second.data();
633
634
				}
			}
635
			global(config.get_child("global"));
636
		}
637
		return true;
638
639
	}

640
	/**
641
	 * Virtual interface method, responsible for setting global values specifically
642
643
644
645
	 * for its plugin.
	 *
	 * @param pluginSettings	The struct with global default plugin settings
	 */
646
647
648
	virtual void derivedSetGlobalSettings(const pluginSettings_t& pluginSettings) {
		//Overwrite if necessary
	}
649
650
651

	/**
	 * Pure virtual interface method, responsible for reading plugin-specific sensor
652
	 * base values.
653
	 *
Micha Mueller's avatar
Micha Mueller committed
654
	 * @param s			The sensor base for which to set the values
655
656
	 * @param config	A boost property (sub-)tree containing the sensor values
	 */
Micha Mueller's avatar
Micha Mueller committed
657
	virtual void sensorBase(SBase& s, CFG_VAL config) = 0;
658
659
660
661
662

	/**
	 * Pure virtual interface method, responsible for reading plugin-specific sensor
	 * group values.
	 *
Micha Mueller's avatar
Micha Mueller committed
663
	 * @param s			The sensor group for which to set the values
664
	 * @param config	A boost property (sub-)tree containing the group values
665
	 */
Micha Mueller's avatar
Micha Mueller committed
666
	virtual void sensorGroup(SGroup& s, CFG_VAL config) = 0;
667

668
669
670
671
	/**
	 * Virtual interface method, responsible for reading plugin-specific sensor
	 * entity values.
	 *
Micha Mueller's avatar
Micha Mueller committed
672
	 * @param s			The sensor entity for which to set the values
673
674
	 * @param config	A boost property (sub-)tree containing the entity values
	 */
Micha Mueller's avatar
Micha Mueller committed
675
	virtual void sensorEntity(SEntity& s, CFG_VAL config) {
676
677
678
		//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";
	}
679

Micha Mueller's avatar
Micha Mueller committed
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
	/**
	 * 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";
	}

718
719
	/**
	 * Print information about configurable configurator attributes (or nothing if no such attributes are required)
720
	 * and associated entities.
721
722
723
724
725
	 *
	 * @param ll    Severity level to log with
	 */
	virtual void printConfiguratorConfig(LOG_LEVEL ll) {
	  //Overwrite if necessary
726
	  LOG_VAR(ll) << "        No other plugin-specific general parameters or entities defined";
727
728
	}

729
730
731
732
733
	/**
	 * Virtual interface method, responsible for reading plugin-specific global values.
	 *
	 * @param config	A boost property (sub-)tree containing the global values
	 */
Micha Mueller's avatar
Micha Mueller committed
734
	virtual void global(CFG_VAL config) {}
735

Alessio Netti's avatar
Alessio Netti committed
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
	/**
	 * Increases by a certain value the input MQTT hex topic.
	 *
	 * Example: a mqtt="AAB7" and val=5 produce "AAC2" as output.
	 *
	 * @param mqtt: the MQTT hex string whose value has to be increased
	 * @param val: the value by which mqtt has to be increased
	 *
	 * @return the increased MQTT string
	 *
	 */
	const std::string increaseMqtt(const std::string& mqtt, int val) {
		unsigned long mqttDigits = stoul(mqtt, 0, 16);
		mqttDigits += val;
		std::stringstream stream;
		stream << std::setfill ('0') << std::setw(mqtt.length()) << std::uppercase << std::hex << mqttDigits;
		return stream.str();
	}

	/**
	 * Formats a numerical CPU core ID into a hex string of specified length.
	 *
	 * Example: a mqttPart="xx" and val=11 produce "0B" as output.
	 *
	 * @param mqttPart: a template MQTT string, defines the length of the final string
	 * @param val: the value of the CPU core ID
	 *
	 * @return the hex string representation of the input CPU core ID
	 *
	 */
	const std::string formatMqttCPU(const std::string& mqttPart, unsigned int val) {
		std::stringstream stream;
		stream << std::setfill ('0') << std::setw(mqttPart.length()) << std::uppercase << std::hex << val;
		return stream.str();
	}

	/**
	 * 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<int> parseCpuString(const std::string& cpuString) {
		std::set<int> cpus;
		int maxCpu = 512;

		std::vector<std::string> 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;
	}

Alessio Netti's avatar
Alessio Netti committed
839
840
841
	/**
	 * Adjusts the names of the sensors in generated groups according to the sensorPattern specified in the global
	 * settings. Operates in tandem with the auto-publish feature.
Alessio Netti's avatar
Alessio Netti committed
842
843
	 *
	 * @return		true if successful, false otherwise
Alessio Netti's avatar
Alessio Netti committed
844
	 */
Alessio Netti's avatar
Alessio Netti committed
845
	bool constructSensorNames() {
Alessio Netti's avatar
Alessio Netti committed
846
847
848
		boost::regex sensorReg(SENSOR_PATTERN), groupReg(GROUP_PATTERN);
		boost::cmatch match;
		if(_sensorPattern == "")
Alessio Netti's avatar
Alessio Netti committed
849
			return true;
Alessio Netti's avatar
Alessio Netti committed
850
		else if (!boost::regex_search(_sensorPattern.c_str(), match, sensorReg)) {
Alessio Netti's avatar
Alessio Netti committed
851
852
			LOG(error) << "Invalid sensor naming pattern " << _sensorPattern << ". You must at least include <sensor>!";
			return false;
Alessio Netti's avatar
Alessio Netti committed
853
854
855
856
857
858
859
860
861
862
863
864
		}

		std::string name;
		// Performing auto-publish for sensors
		for(auto& g: _sensorGroups)
			for(auto& s: g->getSensors()) {
					name = _sensorPattern;
					name = boost::regex_replace(name, sensorReg, s->getName());
					name = boost::regex_replace(name, groupReg, g->getGroupName());
					// Setting the auto-publish name back to the sensor
					s->setName(name);
				}
Alessio Netti's avatar
Alessio Netti committed
865
		return true;
Alessio Netti's avatar
Alessio Netti committed
866
867
	}

868
869
	std::string		_entityName;
	std::string		_groupName;
870
	std::string		_baseName;
871
872
873

	std::string 	_cfgPath;
	std::string		_mqttPrefix;
Alessio Netti's avatar
Alessio Netti committed
874
	std::string		_sensorPattern;
875
	unsigned int	_cacheInterval;
876
877
	std::vector<SGroupPtr> 	_sensorGroupInterfaces;
	std::vector<SG_Ptr>		_sensorGroups;
878
	std::vector<SEntity*>	_sensorEntitys;
Michael Ott's avatar
Michael Ott committed
879
	sBaseMap_t		_templateSensorBases;
880
881
	sGroupMap_t		_templateSensorGroups;
	sEntityMap_t	_templateSensorEntitys;
882
883
};

884
#endif /* SRC_CONFIGURATORTEMPLATE_H_ */