ConfiguratorTemplate.h 16 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
25
#define ADD						BOOST_FOREACH(boost::property_tree::iptree::value_type &val, config)
#define ATTRIBUTE(name,setter)		if (boost::iequals(val.first, name)) { s.setter(val.second.data()); }
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
	 *
Micha Mueller's avatar
Micha Mueller committed
76
	 * @param	cfgPath Path to the config-file
77
78
	 *
	 * @return	True on success, false otherwise
Micha Mueller's avatar
Micha Mueller committed
79
	 */
80
	bool readConfig(std::string cfgPath) final {
81
		_cfgPath = cfgPath;
82
83
84
85
86

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

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

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

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

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

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

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

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

		derivedSetGlobalSettings(pluginSettings);
206
207
	}

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

private:
	void storeSensorGroup(SGroup* sGroup) {
		_sensorGroups.push_back(sGroup);
		_sensorGroupInterfaces.push_back(sGroup);
Micha Mueller's avatar
Micha Mueller committed
221
222
	}

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

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

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

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

295
296
		sensorGroup(sGroup, config);
		return true;
297
298
	}

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

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

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

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

Micha Mueller's avatar
Micha Mueller committed
362
	bool readGlobal(CFG_VAL config) {
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
		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;
				}
			}
		}
379
380
		global(config);
		return true;
381
382
	}

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

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

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

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

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

462
463
464
465
466
	/**
	 * 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
467
	virtual void global(CFG_VAL config) {}
468

469
470
	std::string		_entityName;
	std::string		_groupName;
471
	std::string		_baseName;
472
473
474
475

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

483
#endif /* SRC_CONFIGURATORTEMPLATE_H_ */