Commit 6d18ec13 authored by Micha Mueller's avatar Micha Mueller
Browse files

Make PDU plugin more XML generic:

Instead of using 2 specific IDs, a generic path to the value in the XML-file has to be given now.
This enables the plugin to also be used for other XML use cases apart from specific PDU-case.
parent e7117c0d
......@@ -287,8 +287,7 @@ pdus {
sensors pcs1 {
default tempSens
interval 2000
clustsafeID 1
outletID 1
path "root.node1.node2(id=1).node3(id=1,type=2).valueNode"
mqttsuffix 0001
minValues 3
}
......@@ -310,8 +309,7 @@ Explanation of the values specific for the PDU plugin:
|:----- |:----------- |
| host | Hostname where to fetch the XML-file with sensor data from. The plugin requests the file via HTTPS (one does not need to specify the port; 443 is used automatically)
| TTL | To avoid requesting a current XML-file every time a sensor wants to read his value, one can define a time to live (TTL) for the file here. A new XML-file is requested at the earliest if the TTL has expired. Default value is 1000[ms].
| clustsafeID | ID of the clustsafe instance the sensor should read from.
| outletID | ID of the outlet within the clustsafe the sensor should read.
| path | Define a dot-separated path to the value to be read in the XML file. One can specify attribute values a node has to fulfil in brackets after the node. Even multiple (comma-separated) attributes can be given, however no whitespaces should be used (!) as they will not be filtered and could therefore be treat as part of the attributes name.
#### TODOS
* perhaps xml-file example for pdu
......
......@@ -5,7 +5,6 @@ global {
SensorTemplate {
sensor def1 {
interval 1000
clustsafeID 2
minValues 3
}
}
......@@ -14,30 +13,28 @@ pdus {
pdu rack1 {
host testHorst
TTL 500
; total {
; interval 500
; mqttsuffix 0000
; minValues 1
; }
sensors {
sensor total {
path "clustsafeResponse.energy.total"
mqttsuffix 0000
}
sensor pcs1 {
path "clustsafeResponse.energy.clustsafe(id=1).outlets.outlet(id=1)"
interval 2000
clustsafeID 1
outletID 1
mqttsuffix 0001
}
sensor pcs2 {
default def1
outletID 3
path "clustsafeResponse.energy.clustsafe(id=2).outlets.outlet(id=3)"
mqttsuffix 0002
}
sensor pcs3 {
default def1
outletID 4
path "clustsafeResponse.energy.clustsafe(id=2).outlets.outlet(id=4)"
mqttsuffix 0003
}
}
......
......@@ -8,6 +8,7 @@
#include "PDUConfigurator.h"
#include <iostream>
#include <sstream>
#include <boost/foreach.hpp>
#include <boost/property_tree/info_parser.hpp>
......@@ -122,10 +123,8 @@ bool PDUConfigurator::readSensor(PDUSensor& sensor, boost::property_tree::iptree
sensor.setMqtt(_mqttPrefix + val.second.data());
} else if (boost::iequals(val.first, "minValues")) {
sensor.setMinValues(stoull(val.second.data()));
} else if (boost::iequals(val.first, "clustsafeID")) {
sensor.setClustsafeID(stoull(val.second.data()));
} else if (boost::iequals(val.first, "outletID")) {
sensor.setOutletID(stoull(val.second.data()));
} else if (boost::iequals(val.first, "path")) {
parsePathString(sensor, val.second.data());
} else if (boost::iequals(val.first, "default")) {
//avoid unnecessary "Value not recognized" message
} else {
......@@ -136,8 +135,85 @@ bool PDUConfigurator::readSensor(PDUSensor& sensor, boost::property_tree::iptree
cout << " MQTT : " << sensor.getMqtt() << endl;
cout << " Interval : " << sensor.getInterval() << endl;
cout << " minValues: " << sensor.getMinValues() << endl;
cout << " ClustID : " << sensor.getClustsafeID() << endl;
cout << " OutletID : " << sensor.getOutletID() << endl;
return true;
}
void PDUConfigurator::parsePathString(PDUSensor& sensor, const std::string& pathString) {
cout << " Using " << pathString << " as XML search path" << endl;
std::vector<std::string> subStrings;
std::stringstream pathStream(pathString);
std::string item;
//split into parts if a attribute (indicated by '(' ')') was defined
while (std::getline(pathStream, item, ')')) {
subStrings.push_back(item);
}
for (auto subStr : subStrings) {
//extract the attributes from the path-parts
if (subStr.find('(') != std::string::npos) { //attribute specified
std::stringstream pathWithAttributesSStream(subStr);
//split into path and attributes string
std::string subPath, attributeString;
std::getline(pathWithAttributesSStream, subPath, '(');
std::getline(pathWithAttributesSStream, attributeString);
if (subPath.front() == '.') {
subPath.erase(0, 1);
}
//now further split the attributes string as multiple attributes could be defined
std::vector<std::string> attributes;
std::stringstream attributeStream(attributeString);
while (std::getline(attributeStream, item, ',')) {
attributes.push_back(item);
}
attributesVector_t attrs;
for (auto att : attributes) {
//part attributes into name and value
if (att.find('=') != std::string::npos) {
std::stringstream attStream(att);
std::string attName, attVal;
std::getline(attStream, attName, '=');
std::getline(attStream, attVal);
attrs.push_back(std::make_pair(attName, attVal));
#ifdef DEBUG
cout << " Attribute: " << attName << "=" << attVal << endl;
#endif
} else { //should not happen. If it does the path was malformed
cout << " Could not parse XML-path!" << endl;
return;
}
}
//split of the last child in the path. Required to iterate over multiple nodes which only differ in the attributes with BOOST_FOREACH
auto index = subPath.find_last_of('.');
if (index != std::string::npos) {
std::string subPathChild(subPath.substr(++index));
subPath.erase(--index);
sensor._xmlPath.push_back(std::make_tuple(subPath, subPathChild, attrs));
#ifdef DEBUG
cout << " Subpath: " << subPath << " ; Child: " << subPathChild << endl;
#endif
} else {//the path contained only one node
sensor._xmlPath.push_back(std::make_tuple("", subPath, attrs));
#ifdef DEBUG
cout << " Child: " << subPath << endl;
#endif
}
} else { //no attributes specified. Last (sub)path
if (subStr.front() == '.') {
subStr.erase(0, 1);
}
sensor._xmlPath.push_back(std::make_tuple(subStr, "", attributesVector_t()));
#ifdef DEBUG
cout << " (Sub)path: " << subStr << endl;
#endif
break;
}
}
}
......@@ -42,6 +42,14 @@ private:
*/
bool readSensor(PDUSensor& sensor, boost::property_tree::iptree& config);
/**
* Split the given string into multiple parts which are later required to find the sensor value in the boost property tree.
* Store the individual parts into the sensors _xmlPathVector
* @param sensor Sensor where the path belongs to
* @param pathString Complete unrefined string as read from the config file
*/
void parsePathString(PDUSensor& sensor, const std::string& pathString);
std::vector<Sensor*> _sensors;
sensorMap_t _templateSensors;
pduList_t _pdus;
......
......@@ -13,8 +13,6 @@ extern volatile int keepRunning;
PDUSensor::PDUSensor(const std::string& name) :
Sensor(name) {
_clustsafeID = 0;
_outletID = 0;
_pdu = NULL;
}
......@@ -26,7 +24,7 @@ void PDUSensor::read() {
reading_t reading;
reading.timestamp = getTimestamp();
reading.value = _pdu->findValue(_clustsafeID, _outletID);
reading.value = _pdu->readValue(_xmlPath);
_readingQueue->push(reading);
_latestValue.value = reading.value;
_latestValue.timestamp = reading.timestamp;
......
......@@ -25,31 +25,15 @@ public:
return _pdu;
}
void setClustsafeID(unsigned clustsafeID) {
_clustsafeID = clustsafeID;
}
unsigned getClustsafeID() const {
return _clustsafeID;
}
void setOutletID(unsigned outletID) {
_outletID = outletID;
}
unsigned getOutletID() const {
return _outletID;
}
void read();
void readAsync();
void startPolling(boost::asio::io_service& io);
xmlPathVector_t _xmlPath;
private:
PDUUnit* _pdu;
unsigned int _clustsafeID;
unsigned int _outletID;
};
#endif /* SRC_SENSORS_PDU_PDUSENSOR_H_ */
......@@ -29,8 +29,7 @@ PDUUnit::~PDUUnit() {
// TODO Auto-generated destructor stub
}
uint64_t PDUUnit::findValue(unsigned clustsafeID, unsigned outletID) {
//TODO use appropriate lock
uint64_t PDUUnit::readValue(const xmlPathVector_t& xmlPath) {
uint64_t now = getTimestamp();
if (now >= _lastRefresh + MS_TO_NS(_ttl)) {
refresh();
......@@ -38,22 +37,51 @@ uint64_t PDUUnit::findValue(unsigned clustsafeID, unsigned outletID) {
std::cout << "[" << prettyPrintTimestamp(now) << "] " << "Refreshed XML-file" << std::endl;
#endif
}
BOOST_FOREACH(boost::property_tree::ptree::value_type &v, _ptree.get_child("clustsafeResponse.energy")) {
if (v.first == "clustsafe") {
int csId = stoi(v.second.get_child("<xmlattr>.id").data());
if (csId == clustsafeID) {
BOOST_FOREACH(boost::property_tree::ptree::value_type &o, v.second.get_child("outlets")) {
if (o.first == "outlet") {
int outId = stoi(o.second.get_child("<xmlattr>.id").data());
if (outId == outletID) {
return stoul(o.second.data());
std::string reading;
boost::property_tree::ptree node = _ptree;
for (size_t i = 0; i < xmlPath.size(); i++) {
const std::string& path = std::get<0>(xmlPath[i]);
const std::string& child = std::get<1>(xmlPath[i]);
const attributesVector_t& attVec = std::get<2>(xmlPath[i]);
unsigned matchCount;
if (child != "") {
BOOST_FOREACH(boost::property_tree::ptree::value_type &v, node.get_child(path)) {
if (v.first == child) {
matchCount = 0;
for (size_t j = 0; j < attVec.size(); j++) {
std::string attributeVal = v.second.get_child("<xmlattr>." + attVec[j].first).data();
if (attributeVal != attVec[j].second) { //attribute values don't match
break;
} else {
matchCount++;
}
}
if (matchCount == attVec.size()) { //all attributes matched
reading = v.second.data();
node = v.second;
break;
}
}
}
} else { //child == ""
reading = node.get(path, "");
break; //last (part of the) path
}
}
return 0;
if (reading == "") {
#ifdef DEBUG
std::cout << "PDU: Value not found!" << std::endl;
#endif
return 0;
}
#ifdef DEBUG
std::cout << "Read: " << reading << std::endl;
#endif
return stoul(reading);
}
void PDUUnit::initalizeStrand(boost::asio::io_service& io) {
......
......@@ -8,10 +8,19 @@
#ifndef SRC_SENSORS_PDU_PDUUNIT_H_
#define SRC_SENSORS_PDU_PDUUNIT_H_
#include <tuple>
#include <vector>
#include <utility>
#include <string>
#include <boost/asio.hpp>
#include <boost/property_tree/ptree.hpp>
typedef std::vector<std::pair<std::string, std::string>> attributesVector_t;
typedef std::vector<std::tuple<std::string, std::string, attributesVector_t>> xmlPathVector_t;
class PDUUnit {
public:
PDUUnit();
virtual ~PDUUnit();
......@@ -38,11 +47,10 @@ public:
* Finds and returns the corresponding value in the energy.xml
* Checks if the _ptree is still up to date. If not updates it by a call to refresh().
*
* @param clustsafeID ID of the clustsafe to find in the energy.xml
* @param outletID ID of the outlet to find within the clustsafe
* @return The value in the latest energy.xml, denoted by clustsafeID and outletID
* @param xmlPath Holds all data required to identify the XML path to the sensor value
* @return The value in the latest energy.xml, denoted by paths and attributes
*/
uint64_t findValue(unsigned clustsafeID, unsigned outletID);
uint64_t readValue(const xmlPathVector_t& xmlPath);
private:
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment