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

8
9
#ifndef SRC_CONFIGURATORTEMPLATE_H_
#define SRC_CONFIGURATORTEMPLATE_H_
10

11
12
13
14
15
16
#include <map>

#include <boost/foreach.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/info_parser.hpp>
17
18
19
#include "ConfiguratorInterface.h"
#include "SensorBase.h"
#include "SensorGroupTemplate.h"
20

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

24
#define ADD						BOOST_FOREACH(boost::property_tree::iptree::value_type &val, config)
25
#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
26
#define SETTING(name)				if (boost::iequals(val.first, name))
27

Micha Mueller's avatar
Micha Mueller committed
28
/**
29
 * Non-virtual interface template for the configurators.
Micha Mueller's avatar
Micha Mueller committed
30
 */
31
template <class SBase, class SGroup, class SEntity = nullptr_t>
32
class ConfiguratorTemplate : public ConfiguratorInterface {
33
	//the template shall only be instantiated for classes which derive from SensorBase/SensorGroup
34
	static_assert(std::is_base_of<SensorBase, SBase>::value, "SBase must derive from SensorBase!");
35
	static_assert(std::is_base_of<SensorGroupInterface, SGroup>::value, "SGroup must derive from SensorGroupInterface!");
36

37
protected:
38
39
	typedef std::map<std::string, SGroup*> sGroupMap_t;
	typedef std::map<std::string, SEntity*> sEntityMap_t;
40

41
public:
42
	ConfiguratorTemplate() :
43
44
45
		_entityName("INVALID"),
		_groupName("INVALID"),
		_baseName("INVALID"),
46
47
48
		_cfgPath(""),
		_mqttPrefix(""),
		_cacheInterval(900000) {}
49

50
51
	ConfiguratorTemplate(const ConfiguratorTemplate&) = delete;

52
	virtual ~ConfiguratorTemplate() {
53
54
		for (auto g : _sensorGroups) {
			delete g;
55
		}
56
57
58
		for (auto e : _sensorEntitys) {
			delete e;
		}
59
		for (auto tg : _templateSensorGroups) {
60
			delete tg.second;
61
62
		}
		for (auto te : _templateSensorEntitys) {
63
			delete te.second;
64
		}
65
		_sensorGroupInterfaces.clear();
66
67
		_sensorGroups.clear();
		_sensorEntitys.clear();
68
		_templateSensorGroups.clear();
69
		_templateSensorEntitys.clear();
70
	}
71

72
73
	ConfiguratorTemplate& operator=(const ConfiguratorTemplate&) = delete;

Micha Mueller's avatar
Micha Mueller committed
74
75
	/**
	 * Read in the given configuration
76
	 *
77
78
	 * Overwriting this method is only required if a custom logic is really necessary!
	 *
Micha Mueller's avatar
Micha Mueller committed
79
	 * @param	cfgPath Path to the config-file
80
81
	 *
	 * @return	True on success, false otherwise
Micha Mueller's avatar
Micha Mueller committed
82
	 */
83
	bool readConfig(std::string cfgPath) {
84
		_cfgPath = cfgPath;
85
86
87
88
89

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

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

92
93
94
		//read groups and templates for groups. If present also entity/-template stuff
		BOOST_FOREACH(boost::property_tree::iptree::value_type &val, cfg) {
			//TODO allow for template sensors?
95
			//TODO allow single sensors for convenience?
96
97
98
99
			//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
100
101
102
					//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)) {
103
						auto ret = _templateSensorEntitys.insert(std::pair<std::string, SEntity*>(val.second.data(), entity));
104
						if(!ret.second) {
105
							LOG(warning) << "Template " << _entityName << " " << val.second.data() << " already exists! Omitting...";
106
107
108
109
110
111
112
113
114
115
116
						}
					} else {
						LOG(warning) << "Template " << _entityName << " \"" << val.second.data() << "\" has bad values! Ignoring...";
					}
				}
			//template group
			} else if (boost::iequals(val.first, "template_" + _groupName)) {
				LOG(debug) << "Template " << _groupName << " \"" << val.second.data() << "\"";
				if (!val.second.empty()) {
					SGroup* group = new SGroup(val.second.data());
					if (readSensorGroup(*group, val.second)) {
117
						auto ret = _templateSensorGroups.insert(std::pair<std::string, SGroup*>(val.second.data(), group));
118
						if(!ret.second) {
119
							LOG(warning) << "Template " << _groupName << " " << val.second.data() << " already exists! Omitting...";
120
						}
121
122
123
124
125
126
127
128
					} else {
						LOG(warning) << "Template " << _groupName << " \"" << val.second.data() << "\" has bad values! Ignoring...";
					}
				}
			//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
129
130
					SEntity* entity = new SEntity();
					if (readSensorEntity(*entity, val.second, false)) {
131
132
133
						_sensorEntitys.push_back(entity);
					} else {
						LOG(warning) << _entityName << " \"" << val.second.data() << "\" has bad values! Ignoring...";
134
					}
135
136
137
138
139
140
141
				}
			//group
			} else if (boost::iequals(val.first, _groupName)) {
				LOG(debug) << _groupName << " \"" << val.second.data() << "\"";
				if (!val.second.empty()) {
					SGroup* group = new SGroup(val.second.data());
					if (readSensorGroup(*group, val.second)) {
142
						storeSensorGroup(group);
143
144
145
146
					} else {
						LOG(warning) << _groupName << " \"" << val.second.data() << "\" has bad values! Ignoring...";
					}
				}
147
148
			}
		}
149
150
151
152
153
154
		//read of config finished. Now we build the mqtt-topic for every sensor
		for(auto g : _sensorGroups) {
			for(auto s : g->getSensors()) {
				s->setMqtt(_mqttPrefix + g->getMqttPart() + s->getMqtt());
			}
		}
155
		return true;
156
157
158
159
	}

	/**
	 * Clear internal storage and read in the configuration again.
160
161
	 *
	 * @return	True on success, false otherwise
162
	 */
163
	bool reReadConfig() final {
Micha Mueller's avatar
Micha Mueller committed
164
165
		//bring everything to a halt
		for(auto g : _sensorGroups) {
166
			g->stop();
Micha Mueller's avatar
Micha Mueller committed
167
168
169
170
171
172
		}

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

174
		//clean up sensors/groups/entitys and templates
Micha Mueller's avatar
Micha Mueller committed
175
176
177
		for(auto g : _sensorGroups) {
			delete g;
		}
178
179
180
		for(auto e : _sensorEntitys) {
			delete e;
		}
181
		for (auto tg : _templateSensorGroups) {
182
			delete tg.second;
183
184
		}
		for (auto te : _templateSensorEntitys) {
185
			delete te.second;
186
		}
187
		_sensorGroupInterfaces.clear();
Micha Mueller's avatar
Micha Mueller committed
188
		_sensorGroups.clear();
189
		_sensorEntitys.clear();
190
		_templateSensorGroups.clear();
191
		_templateSensorEntitys.clear();
192

Micha Mueller's avatar
Micha Mueller committed
193
		//back to the very beginning
194
		return readConfig(_cfgPath);
195
	}
196

197
198
199
200
201
202
203
	/**
	 * 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.
	 */
204
	void setGlobalSettings(const pluginSettings_t& pluginSettings) final {
205
206
		_mqttPrefix = pluginSettings.mqttPrefix;
		_cacheInterval = pluginSettings.cacheInterval;
207
208

		derivedSetGlobalSettings(pluginSettings);
209
210
	}

Micha Mueller's avatar
Micha Mueller committed
211
212
213
214
215
	/**
	 * Get all sensor groups
	 *
	 * @return	Vector containing pointers to all sensor groups of this plugin
	 */
Micha Mueller's avatar
Micha Mueller committed
216
	std::vector<SensorGroupInterface*>& getSensorGroups() final {
217
218
219
		return _sensorGroupInterfaces;
	}

220
protected:
221
222
223
	void storeSensorGroup(SGroup* sGroup) {
		_sensorGroups.push_back(sGroup);
		_sensorGroupInterfaces.push_back(sGroup);
Micha Mueller's avatar
Micha Mueller committed
224
225
	}

226
227
	/**
	 * Non-virtual interface method for class-internal use only.
228
	 * Reads and sets the common base values of a sensor base (currently none),
229
230
	 * then calls the corresponding derived function to read plugin specific
	 * values.
231
	 *
232
	 * @param sBase		The sensor base for which to set the values
233
234
235
236
	 * @param config	A boost property (sub-)tree containing the sensor values
	 *
	 * @return	True on success, false otherwise
	 */
Micha Mueller's avatar
Micha Mueller committed
237
	bool readSensorBase(SBase& sBase, CFG_VAL config) {
238
239
240
241
242
243
244
245
		//TODO default templates useful?
		BOOST_FOREACH(boost::property_tree::iptree::value_type &val, config) {
			if (boost::iequals(val.first, "mqttsuffix")) {
				sBase.setMqtt(val.second.data());
			}
		}
		sensorBase(sBase, config);
		return true;
246
247
248
249
	}

	/**
	 * Non-virtual interface method for class-internal use only.
250
251
	 * Reads and sets the common base values of a sensor group, then calls
	 * the corresponding derived function to read in plugin specific values.
252
	 *
253
	 * @param sGroup	The sensor group for which to set the values
254
255
256
257
	 * @param config	A boost property (sub-)tree containing the sensor values
	 *
	 * @return	True on success, false otherwise
	 */
Micha Mueller's avatar
Micha Mueller committed
258
	bool readSensorGroup(SGroup& sGroup, CFG_VAL config) {
259
		sGroup.setCacheInterval(_cacheInterval);
260
261
262
263
264
265
266
267
		//first check if default group is given
		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()) {
268
269
				sGroup = *(it->second);
				sGroup.setGroupName(config.data());
270
271
272
273
274
			} else {
				LOG(warning) << "Template " << _groupName << "\"" << def.get().data() << "\" not found! Using standard values.";
			}
		}

275
		//read in values inherited from SensorGroupInterface
276
		BOOST_FOREACH(boost::property_tree::iptree::value_type &val, config) {
277
			if (boost::iequals(val.first, "interval")) {
278
				sGroup.setInterval(stoull(val.second.data()));
279
			} else if (boost::iequals(val.first, "minValues")) {
280
				sGroup.setMinValues(stoull(val.second.data()));
281
282
			} else if (boost::iequals(val.first, "mqttPart")) {
				sGroup.setMqttPart(val.second.data());
283
284
			} else if (boost::iequals(val.first, _baseName)) {
				LOG(debug) << "  " << _baseName << " " << val.second.data();
285
286
				SBase* sensor = new SBase(val.second.data());
				if (readSensorBase(*sensor, val.second)) {
287
					sGroup.pushBackSensor(sensor);
288
				} else {
289
					LOG(warning) << _baseName << " " << sGroup.getGroupName() << "::" << sensor->getName() << " could not be read! Omitting";
290
				}
291
292
293
			}
		}

294
295
296
		//TODO keep debug logging for config?
//		LOG(debug) << "  Interval : " << sGroup.getInterval();
//		LOG(debug) << "  minValues: " << sGroup.getMinValues();
297

298
299
		sensorGroup(sGroup, config);
		return true;
300
301
	}

302
303
304
305
306
307
308
	/**
	 * 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
309
310
	 * @param isTemplate	Indicate if sEntity is a template. If so, also store
	 * 						the corresponding sGroups in the template map
311
312
313
	 *
	 * @return	True on success, false otherwise
	 */
Micha Mueller's avatar
Micha Mueller committed
314
	bool readSensorEntity(SEntity& sEntity, CFG_VAL config, bool isTemplate) {
315
316
317
318
319
320
321
		//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()) {
322
				sEntity = *(it->second);
Micha Mueller's avatar
Micha Mueller committed
323
324
325
326
				for(auto g : _templateSensorGroups) {
					if (isEntityOfGroup(*(it->second), *(g.second))) {
						SGroup* group = new SGroup(*(g.second));
						setEntityForGroup(sEntity, *group);
327
						storeSensorGroup(group);
Micha Mueller's avatar
Micha Mueller committed
328
329
					}
				}
330
331
332
333
			} else {
				LOG(warning) << "Template " << _entityName << "\"" << def.get().data() << "\" not found! Using standard values.";
			}
		}
Micha Mueller's avatar
Micha Mueller committed
334

335
		sensorEntity(sEntity, config);
Micha Mueller's avatar
Micha Mueller committed
336
337
338
339
340
341
342
343
344
345
346
347
348

		BOOST_FOREACH(boost::property_tree::iptree::value_type &val, config) {
			if (boost::iequals(val.first, _groupName)) {
				LOG(debug) << "  " << _groupName << " " << val.second.data();
				SGroup* group = new SGroup(val.second.data());
				if(readSensorGroup(*group, val.second)) {
					setEntityForGroup(sEntity, *group);
					if (isTemplate) {
						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...";
						}
					} else {
349
						storeSensorGroup(group);
Micha Mueller's avatar
Micha Mueller committed
350
351
352
353
354
355
356
					}
				} else {
					LOG(warning) << _groupName << " " << group->getGroupName() << " could not be read! Omitting";
				}
			}
		}

357
358
359
360
361
		if(!isTemplate) {
			for(auto g : _sensorGroups) {
				if(isEntityOfGroup(sEntity, *g)) {
					finalizeGroup(*g);
				}
Micha Mueller's avatar
Micha Mueller committed
362
363
			}
		}
364
		return true;
365
366
	}

Micha Mueller's avatar
Micha Mueller committed
367
	bool readGlobal(CFG_VAL config) {
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
		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;
				}
			}
383
			global(config.get_child("global"));
384
		}
385
		return true;
386
387
	}

388
	/**
389
	 * Virtual interface method, responsible for setting global values specifically
390
391
392
393
	 * for its plugin.
	 *
	 * @param pluginSettings	The struct with global default plugin settings
	 */
394
395
396
	virtual void derivedSetGlobalSettings(const pluginSettings_t& pluginSettings) {
		//Overwrite if necessary
	}
397
398
399

	/**
	 * Pure virtual interface method, responsible for reading plugin-specific sensor
400
	 * base values.
401
	 *
Micha Mueller's avatar
Micha Mueller committed
402
	 * @param s			The sensor base for which to set the values
403
404
	 * @param config	A boost property (sub-)tree containing the sensor values
	 */
Micha Mueller's avatar
Micha Mueller committed
405
	virtual void sensorBase(SBase& s, CFG_VAL config) = 0;
406
407
408
409
410

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

416
417
418
419
	/**
	 * Virtual interface method, responsible for reading plugin-specific sensor
	 * entity values.
	 *
Micha Mueller's avatar
Micha Mueller committed
420
	 * @param s			The sensor entity for which to set the values
421
422
	 * @param config	A boost property (sub-)tree containing the entity values
	 */
Micha Mueller's avatar
Micha Mueller committed
423
	virtual void sensorEntity(SEntity& s, CFG_VAL config) {
424
425
426
		//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";
	}
427

Micha Mueller's avatar
Micha Mueller committed
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
	/**
	 * 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";
	}

466
467
468
469
470
	/**
	 * 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
471
	virtual void global(CFG_VAL config) {}
472

473
474
	std::string		_entityName;
	std::string		_groupName;
475
	std::string		_baseName;
476
477
478
479

	std::string 	_cfgPath;
	std::string		_mqttPrefix;
	unsigned int	_cacheInterval;
480
481
482
	std::vector<SensorGroupInterface*> _sensorGroupInterfaces;
	std::vector<SGroup*>	_sensorGroups;
	std::vector<SEntity*>	_sensorEntitys;
483
484
	sGroupMap_t		_templateSensorGroups;
	sEntityMap_t	_templateSensorEntitys;
485
486
};

487
#endif /* SRC_CONFIGURATORTEMPLATE_H_ */