ConfiguratorTemplate.h 15.8 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
65
		_sensorGroups.clear();
		_sensorEntitys.clear();
66
		_templateSensorGroups.clear();
67
		_templateSensorEntitys.clear();
68
	}
69

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

Micha Mueller's avatar
Micha Mueller committed
72
73
	/**
	 * Read in the given configuration
74
	 *
Micha Mueller's avatar
Micha Mueller committed
75
	 * @param	cfgPath Path to the config-file
76
77
	 *
	 * @return	True on success, false otherwise
Micha Mueller's avatar
Micha Mueller committed
78
	 */
79
	bool readConfig(std::string cfgPath) final {
80
		_cfgPath = cfgPath;
81
82
83
84
85

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

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

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

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

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

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

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

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

		derivedSetGlobalSettings(pluginSettings);
204
205
	}

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

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

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

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

284
285
286
		//TODO keep debug logging for config?
//		LOG(debug) << "  Interval : " << sGroup.getInterval();
//		LOG(debug) << "  minValues: " << sGroup.getMinValues();
287

288
289
		sensorGroup(sGroup, config);
		return true;
290
291
	}

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

325
		sensorEntity(sEntity, config);
Micha Mueller's avatar
Micha Mueller committed
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351

		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 {
						_sensorGroups.push_back(group);
					}
				} else {
					LOG(warning) << _groupName << " " << group->getGroupName() << " could not be read! Omitting";
				}
			}
		}

		for(auto g : _sensorGroups) {
			if(isEntityOfGroup(sEntity, *g)) {
				finalizeEntityGroup(sEntity, *g);
			}
		}
352
		return true;
353
354
	}

Micha Mueller's avatar
Micha Mueller committed
355
	bool readGlobal(CFG_VAL config) {
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
		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;
				}
			}
		}
372
373
		global(config);
		return true;
374
375
	}

376
	/**
377
	 * Virtual interface method, responsible for setting global values specifically
378
379
380
381
	 * for its plugin.
	 *
	 * @param pluginSettings	The struct with global default plugin settings
	 */
382
383
384
	virtual void derivedSetGlobalSettings(const pluginSettings_t& pluginSettings) {
		//Overwrite if necessary
	}
385
386
387

	/**
	 * Pure virtual interface method, responsible for reading plugin-specific sensor
388
	 * base values.
389
	 *
Micha Mueller's avatar
Micha Mueller committed
390
	 * @param s			The sensor base for which to set the values
391
392
	 * @param config	A boost property (sub-)tree containing the sensor values
	 */
Micha Mueller's avatar
Micha Mueller committed
393
	virtual void sensorBase(SBase& s, CFG_VAL config) = 0;
394
395
396
397
398

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

404
405
406
407
	/**
	 * Virtual interface method, responsible for reading plugin-specific sensor
	 * entity values.
	 *
Micha Mueller's avatar
Micha Mueller committed
408
	 * @param s			The sensor entity for which to set the values
409
410
	 * @param config	A boost property (sub-)tree containing the entity values
	 */
Micha Mueller's avatar
Micha Mueller committed
411
	virtual void sensorEntity(SEntity& s, CFG_VAL config) {
412
413
414
		//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";
	}
415

Micha Mueller's avatar
Micha Mueller committed
416
417
418
419
420
421
422
423
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
	/**
	 * 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";
	}

454
455
456
457
458
	/**
	 * 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
459
	virtual void global(CFG_VAL config) {}
460

461
462
	std::string		_entityName;
	std::string		_groupName;
463
	std::string		_baseName;
464
465
466
467

	std::string 	_cfgPath;
	std::string		_mqttPrefix;
	unsigned int	_cacheInterval;
468
	std::vector<SensorGroupInterface*> _sensorGroups;
469
	std::vector<SEntity*> _sensorEntitys;
470
471
	sGroupMap_t		_templateSensorGroups;
	sEntityMap_t	_templateSensorEntitys;
472
473
};

474
#endif /* SRC_CONFIGURATORTEMPLATE_H_ */