ConfiguratorTemplate.h 29.2 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
20
21
#include "ConfiguratorInterface.h"
#include "SensorBase.h"
#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

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

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

49
50
51
	using SB_Ptr = std::shared_ptr<SBase>;
	using SG_Ptr = std::shared_ptr<SGroup>;

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

Alessio Netti's avatar
Alessio Netti committed
57
58
59
	const std::string SENSOR_PATTERN = "<sensor>";
	const std::string GROUP_PATTERN = "<group>";

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

70
71
	ConfiguratorTemplate(const ConfiguratorTemplate&) = delete;

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

93
94
	ConfiguratorTemplate& operator=(const ConfiguratorTemplate&) = delete;

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

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

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

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

	/**
	 * Clear internal storage and read in the configuration again.
267
268
	 *
	 * @return	True on success, false otherwise
269
	 */
270
	bool reReadConfig() 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();
300

Micha Mueller's avatar
Micha Mueller committed
301
		//back to the very beginning
302
		return readConfig(_cfgPath);
303
	}
304

305
306
307
308
309
310
311
	/**
	 * 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.
	 */
312
	void setGlobalSettings(const pluginSettings_t& pluginSettings) final {
313
		_mqttPrefix = pluginSettings.mqttPrefix;
Alessio Netti's avatar
Alessio Netti committed
314
		_sensorPattern = pluginSettings.sensorPattern;
315
		_cacheInterval = pluginSettings.cacheInterval;
316
317

		derivedSetGlobalSettings(pluginSettings);
318
319
	}

Micha Mueller's avatar
Micha Mueller committed
320
321
322
323
324
	/**
	 * Get all sensor groups
	 *
	 * @return	Vector containing pointers to all sensor groups of this plugin
	 */
325
	std::vector<SGroupPtr>& getSensorGroups() final {
326
327
328
		return _sensorGroupInterfaces;
	}

329
protected:
330
	void storeSensorGroup(SG_Ptr sGroup) {
331
332
		_sensorGroups.push_back(sGroup);
		_sensorGroupInterfaces.push_back(sGroup);
Micha Mueller's avatar
Micha Mueller committed
333
334
	}

335
336
	/**
	 * Non-virtual interface method for class-internal use only.
337
	 * Reads and sets the common base values of a sensor base (currently none),
338
339
	 * then calls the corresponding derived function to read plugin specific
	 * values.
340
	 *
341
	 * @param sBase		The sensor base for which to set the values
342
	 * @param config	A boost property (sub-)tree containing the sensor values
343
	 * @param isTemplate	Are we parsing a template or a regular sensor?
344
345
346
	 *
	 * @return	True on success, false otherwise
	 */
347
	bool readSensorBase(SBase& sBase, CFG_VAL config, bool isTemplate=false) {
348
		sBase.setCacheInterval(_cacheInterval);
349
350
351
352
353
354
355
356
357
358
359
360
361
		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 = _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
362
363
364
			}
		}
		
365
366
367
		BOOST_FOREACH(boost::property_tree::iptree::value_type &val, config) {
			if (boost::iequals(val.first, "mqttsuffix")) {
				sBase.setMqtt(val.second.data());
368
			} else if (boost::iequals(val.first, "skipConstVal")) {
369
370
371
372
373
				if (val.second.data() == "on") {
					sBase.setSkipConstVal(true);
				} else {
					sBase.setSkipConstVal(false);
				}
374
			} else if (boost::iequals(val.first, "delta")) {
375
				sBase.setDelta( val.second.data() == "on" );
376
377
378
379
			} 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()) );
380
			}
381
382
383
		}
		sensorBase(sBase, config);
		return true;
384
385
386
387
	}

	/**
	 * Non-virtual interface method for class-internal use only.
388
389
	 * Reads and sets the common base values of a sensor group, then calls
	 * the corresponding derived function to read in plugin specific values.
390
	 *
391
	 * @param sGroup	The sensor group for which to set the values
392
	 * @param config	A boost property (sub-)tree containing the sensor values
393
	 * @param isTemplate	Is this a template or a regular group?
394
395
396
	 *
	 * @return	True on success, false otherwise
	 */
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
	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.";
				}
412
413
414
			}
		}

415
		//read in values inherited from SensorGroupInterface
416
		BOOST_FOREACH(boost::property_tree::iptree::value_type &val, config) {
417
			if (boost::iequals(val.first, "interval")) {
418
				sGroup.setInterval(stoull(val.second.data()));
419
			} else if (boost::iequals(val.first, "minValues")) {
420
				sGroup.setMinValues(stoull(val.second.data()));
421
422
			} else if (boost::iequals(val.first, "mqttPart")) {
				sGroup.setMqttPart(val.second.data());
423
424
425
426
427
428
			} else if (boost::iequals(val.first, "sync")) {
				if (val.second.data() == "off") {
					sGroup.setSync(false);
				} else {
					sGroup.setSync(true);
				}
429
			} else if (boost::iequals(val.first, _baseName)) {
430
431
432
				if (!isTemplate) {
					LOG(debug) << "  " << _baseName << " " << val.second.data();
				}
433
				SB_Ptr sensor = std::make_shared<SBase>(val.second.data());
434
				if (readSensorBase(*sensor, val.second, isTemplate)) {
435
					sGroup.pushBackSensor(sensor);
436
				} else if (!isTemplate) {
437
					LOG(warning) << _baseName << " " << sGroup.getGroupName() << "::" << sensor->getName() << " could not be read! Omitting";
438
				}
439
440
441
			}
		}

442
443
444
		//TODO keep debug logging for config?
//		LOG(debug) << "  Interval : " << sGroup.getInterval();
//		LOG(debug) << "  minValues: " << sGroup.getMinValues();
445

446
447
		sensorGroup(sGroup, config);
		return true;
448
449
	}

450
451
452
453
454
455
456
	/**
	 * 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
457
458
	 * @param isTemplate	Indicate if sEntity is a template. If so, also store
	 * 						the corresponding sGroups in the template map
459
460
461
	 *
	 * @return	True on success, false otherwise
	 */
Micha Mueller's avatar
Micha Mueller committed
462
	bool readSensorEntity(SEntity& sEntity, CFG_VAL config, bool isTemplate) {
463
464
465
466
467
468
469
		//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()) {
470
				sEntity = *(it->second);
Micha Mueller's avatar
Micha Mueller committed
471
472
				for(auto g : _templateSensorGroups) {
					if (isEntityOfGroup(*(it->second), *(g.second))) {
473
						SG_Ptr group = std::make_shared<SGroup>(*(g.second));
Micha Mueller's avatar
Micha Mueller committed
474
						setEntityForGroup(sEntity, *group);
475
						storeSensorGroup(group);
Micha Mueller's avatar
Micha Mueller committed
476
477
					}
				}
478
479
480
481
			} else {
				LOG(warning) << "Template " << _entityName << "\"" << def.get().data() << "\" not found! Using standard values.";
			}
		}
Micha Mueller's avatar
Micha Mueller committed
482

483
		sensorEntity(sEntity, config);
Micha Mueller's avatar
Micha Mueller committed
484
485
486
487

		BOOST_FOREACH(boost::property_tree::iptree::value_type &val, config) {
			if (boost::iequals(val.first, _groupName)) {
				LOG(debug) << "  " << _groupName << " " << val.second.data();
488
				if (!val.second.empty()) {
489
490
491
492
					if (isTemplate) {
						SGroup* group = new SGroup(val.second.data());
						if(readSensorGroup(*group, val.second)) {
							setEntityForGroup(sEntity, *group);
493
494
495
496
497
498
							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 {
499
500
							LOG(warning) << _groupName << " " << group->getGroupName() << " could not be read! Omitting";
							delete group;
Micha Mueller's avatar
Micha Mueller committed
501
502
						}
					} else {
503
504
505
506
507
508
509
						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";
						}
510
511
512
513
514
					}
				}
			} else if (boost::iequals(val.first, "single_" + _baseName)) {
				LOG(debug) << "Single " << _baseName << " \"" << val.second.data() << "\"";
				if (!val.second.empty()) {
515
516
517
518
519
520
					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());
521
522
523
524
525
526
527
528
529
530
531
532
							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 {
533
534
535
536
537
538
539
540
541
							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;
542
543
							//perhaps one sensor is already present because it was copied from the template group
							if (group->getSensors().size() != 0) {
544
545
546
547
548
549
550
551
552
								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";
									}
553
								} else {
554
									LOG(warning) << "Single " << _baseName << " " << val.second.data() << " had a type mismatch when casting! Omitting";
555
556
								}
							} else {
557
								sensor = std::make_shared<SBase>(val.second.data());
558
559
560
561
562
563
564
								if (readSensorBase(*sensor, val.second)) {
									group->pushBackSensor(sensor);
									storeSensorGroup(group);
								} else {
									LOG(warning) << "Single " << _baseName << " " << val.second.data() << " could not be read! Omitting";
								}
							}
565
566
						} else {
							LOG(warning) << "Single " << _baseName << " \"" << val.second.data() << "\" has bad values! Ignoring...";
567
						}
Micha Mueller's avatar
Micha Mueller committed
568
569
570
571
572
					}
				}
			}
		}

573
		if(!isTemplate) {
574
			for(const auto& g : _sensorGroups) {
575
576
577
				if(isEntityOfGroup(sEntity, *g)) {
					finalizeGroup(*g);
				}
Micha Mueller's avatar
Micha Mueller committed
578
579
			}
		}
580
		return true;
581
582
	}

Micha Mueller's avatar
Micha Mueller committed
583
	bool readGlobal(CFG_VAL config) {
584
585
586
587
588
589
590
591
592
593
594
595
596
		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("/");
					}
					LOG(debug) << "  Using own MQTT-Prefix " << _mqttPrefix;
				} else if (boost::iequals(global.first, "cacheInterval")) {
					_cacheInterval = stoul(global.second.data());
					LOG(debug) << "  Using own caching interval " << _cacheInterval << " [s]";
					_cacheInterval *= 1000;
Alessio Netti's avatar
Alessio Netti committed
597
598
				} else if (boost::iequals(global.first, "sensorpattern")) {
					_sensorPattern = global.second.data();
599
600
				}
			}
601
			global(config.get_child("global"));
602
		}
603
		return true;
604
605
	}

606
	/**
607
	 * Virtual interface method, responsible for setting global values specifically
608
609
610
611
	 * for its plugin.
	 *
	 * @param pluginSettings	The struct with global default plugin settings
	 */
612
613
614
	virtual void derivedSetGlobalSettings(const pluginSettings_t& pluginSettings) {
		//Overwrite if necessary
	}
615
616
617

	/**
	 * Pure virtual interface method, responsible for reading plugin-specific sensor
618
	 * base values.
619
	 *
Micha Mueller's avatar
Micha Mueller committed
620
	 * @param s			The sensor base for which to set the values
621
622
	 * @param config	A boost property (sub-)tree containing the sensor values
	 */
Micha Mueller's avatar
Micha Mueller committed
623
	virtual void sensorBase(SBase& s, CFG_VAL config) = 0;
624
625
626
627
628

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

634
635
636
637
	/**
	 * Virtual interface method, responsible for reading plugin-specific sensor
	 * entity values.
	 *
Micha Mueller's avatar
Micha Mueller committed
638
	 * @param s			The sensor entity for which to set the values
639
640
	 * @param config	A boost property (sub-)tree containing the entity values
	 */
Micha Mueller's avatar
Micha Mueller committed
641
	virtual void sensorEntity(SEntity& s, CFG_VAL config) {
642
643
644
		//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";
	}
645

Micha Mueller's avatar
Micha Mueller committed
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
	/**
	 * 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";
	}

684
685
686
687
688
	/**
	 * 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
689
	virtual void global(CFG_VAL config) {}
690

Alessio Netti's avatar
Alessio Netti committed
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
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
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
	/**
	 * 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
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
	/**
	 * 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.
	 */
	void constructSensorNames() {
		boost::regex sensorReg(SENSOR_PATTERN), groupReg(GROUP_PATTERN);
		boost::cmatch match;
		if(_sensorPattern == "")
			return;
		else if (!boost::regex_search(_sensorPattern.c_str(), match, sensorReg)) {
			LOG(error) << "Invalid sensor naming pattern " << _sensorPattern << ". You must at least include " << SENSOR_PATTERN << "!";
			return;
		}

		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);
				}
		return;
	}

821
822
	std::string		_entityName;
	std::string		_groupName;
823
	std::string		_baseName;
824
825
826

	std::string 	_cfgPath;
	std::string		_mqttPrefix;
Alessio Netti's avatar
Alessio Netti committed
827
	std::string		_sensorPattern;
828
	unsigned int	_cacheInterval;
829
830
	std::vector<SGroupPtr> 	_sensorGroupInterfaces;
	std::vector<SG_Ptr>		_sensorGroups;
831
	std::vector<SEntity*>	_sensorEntitys;
Michael Ott's avatar
Michael Ott committed
832
	sBaseMap_t		_templateSensorBases;
833
834
	sGroupMap_t		_templateSensorGroups;
	sEntityMap_t	_templateSensorEntitys;
835
836
};

837
#endif /* SRC_CONFIGURATORTEMPLATE_H_ */