ConfiguratorTemplate.h 16.1 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)
26

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

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

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

49
50
	ConfiguratorTemplate(const ConfiguratorTemplate&) = delete;

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

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

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

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

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

91
92
93
		//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?
94
			//TODO allow single sensors for convenience?
95
96
97
98
			//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
99
100
101
					//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)) {
102
						auto ret = _templateSensorEntitys.insert(std::pair<std::string, SEntity*>(val.second.data(), entity));
103
						if(!ret.second) {
104
							LOG(warning) << "Template " << _entityName << " " << val.second.data() << " already exists! Omitting...";
105
106
107
108
109
110
111
112
113
114
115
						}
					} 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)) {
116
						auto ret = _templateSensorGroups.insert(std::pair<std::string, SGroup*>(val.second.data(), group));
117
						if(!ret.second) {
118
							LOG(warning) << "Template " << _groupName << " " << val.second.data() << " already exists! Omitting...";
119
						}
120
121
122
123
124
125
126
127
					} 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
128
129
					SEntity* entity = new SEntity();
					if (readSensorEntity(*entity, val.second, false)) {
130
131
132
						_sensorEntitys.push_back(entity);
					} else {
						LOG(warning) << _entityName << " \"" << val.second.data() << "\" has bad values! Ignoring...";
133
					}
134
135
136
137
138
139
140
				}
			//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)) {
141
						storeSensorGroup(group);
142
143
144
145
					} else {
						LOG(warning) << _groupName << " \"" << val.second.data() << "\" has bad values! Ignoring...";
					}
				}
146
147
			}
		}
148
149
150
151
152
153
		//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());
			}
		}
154
		return true;
155
156
157
158
	}

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

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

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

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

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

		derivedSetGlobalSettings(pluginSettings);
208
209
	}

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

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

225
226
	/**
	 * Non-virtual interface method for class-internal use only.
227
	 * Reads and sets the common base values of a sensor base (currently none),
228
229
	 * then calls the corresponding derived function to read plugin specific
	 * values.
230
	 *
231
	 * @param sBase		The sensor base for which to set the values
232
233
234
235
	 * @param config	A boost property (sub-)tree containing the sensor values
	 *
	 * @return	True on success, false otherwise
	 */
Micha Mueller's avatar
Micha Mueller committed
236
	bool readSensorBase(SBase& sBase, CFG_VAL config) {
237
238
239
240
241
242
243
244
		//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;
245
246
247
248
	}

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

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

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

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

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

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

		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 {
348
						storeSensorGroup(group);
Micha Mueller's avatar
Micha Mueller committed
349
350
351
352
353
354
355
					}
				} else {
					LOG(warning) << _groupName << " " << group->getGroupName() << " could not be read! Omitting";
				}
			}
		}

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

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

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

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

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

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

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

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

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

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

486
#endif /* SRC_CONFIGURATORTEMPLATE_H_ */