Currently job artifacts in CI/CD pipelines on LRZ GitLab never expire. Starting from Wed 26.1.2022 the default expiration time will be 30 days (GitLab default). Currently existing 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 31.1 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

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

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

32
#define ADD						BOOST_FOREACH(boost::property_tree::iptree::value_type &val, config)
33
#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
34
#define SETTING(name)				if (boost::iequals(val.first, name))
35

36
37
#define DEFAULT_CACHE_INTERVAL 900000

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

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

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

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

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

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

73
74
	ConfiguratorTemplate(const ConfiguratorTemplate&) = delete;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

		derivedSetGlobalSettings(pluginSettings);
360
361
	}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Micha Mueller's avatar
Micha Mueller committed
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
718
	/**
	 * 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";
	}

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

730
731
732
733
734
	/**
	 * 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
735
	virtual void global(CFG_VAL config) {}
736

Alessio Netti's avatar
Alessio Netti committed
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
	/**
	 * 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();
	}

	/**
757
758
759
	 * Replaces occurences of 'x' characters by a hex representation of a
	 * numerical CPU core ID. If no 'x' characters are found only the CPU hex
	 * string with specified width is returned.
Alessio Netti's avatar
Alessio Netti committed
760
	 *
761
762
763
	 * Examples: mqttPart=  "xx", val=11 --> return   "0B"
	 *           mqttPart="A3xx", val=11 --> return "A30B"
	 *           mqttPart="A3YY", val=11 --> return "000B"
Alessio Netti's avatar
Alessio Netti committed
764
765
766
767
768
769
770
771
772
	 *
	 * @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;
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789

		size_t n = std::count(mqttPart.begin(), mqttPart.end(), 'x');

		if (n==0) {
            stream << std::setfill ('0') << std::setw(mqttPart.length()) << std::uppercase << std::hex << val;
            return stream.str();
		} else {
		    std::string result(mqttPart);

		    stream << std::setfill ('0') << std::setw(n) << std::uppercase << std::hex << val;
		    std::string replacement = stream.str();
		    std::string pattern(n, 'x');

		    size_t index = result.find(pattern, index);
            result.replace(index, n, replacement);
            return result;
		}
Alessio Netti's avatar
Alessio Netti committed
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
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
	}

	/**
	 * 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
859
860
861
	/**
	 * 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
862
863
	 *
	 * @return		true if successful, false otherwise
Alessio Netti's avatar
Alessio Netti committed
864
	 */
Alessio Netti's avatar
Alessio Netti committed
865
	bool constructSensorNames() {
Alessio Netti's avatar
Alessio Netti committed
866
867
868
		boost::regex sensorReg(SENSOR_PATTERN), groupReg(GROUP_PATTERN);
		boost::cmatch match;
		if(_sensorPattern == "")
Alessio Netti's avatar
Alessio Netti committed
869
			return true;
Alessio Netti's avatar
Alessio Netti committed
870
		else if (!boost::regex_search(_sensorPattern.c_str(), match, sensorReg)) {
Alessio Netti's avatar
Alessio Netti committed
871
872
			LOG(error) << "Invalid sensor naming pattern " << _sensorPattern << ". You must at least include <sensor>!";
			return false;
Alessio Netti's avatar
Alessio Netti committed
873
874
875
876
877
878
879
880
881
882
883
884
		}

		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
885
		return true;
Alessio Netti's avatar
Alessio Netti committed
886
887
	}

888
889
	std::string		_entityName;
	std::string		_groupName;
890
	std::string		_baseName;
891
892
893

	std::string 	_cfgPath;
	std::string		_mqttPrefix;
Alessio Netti's avatar
Alessio Netti committed
894
	std::string		_sensorPattern;
895
	unsigned int	_cacheInterval;
896
897
	std::vector<SGroupPtr> 	_sensorGroupInterfaces;
	std::vector<SG_Ptr>		_sensorGroups;
898
	std::vector<SEntity*>	_sensorEntitys;
Michael Ott's avatar
Michael Ott committed
899
	sBaseMap_t		_templateSensorBases;
900
901
	sGroupMap_t		_templateSensorGroups;
	sEntityMap_t	_templateSensorEntitys;
902
903
};

904
#endif /* SRC_CONFIGURATORTEMPLATE_H_ */