Commit 91e3805b authored by Micha Müller's avatar Micha Müller
Browse files

Pusher: introduce EntityInterface

parent 2fa55ec2
//================================================================================
// Name : EntityInterface.h
// Author : Micha Mueller
// Copyright : Leibniz Supercomputing Centre
// Description : Abstract interface defining sensor entity functionality.
//================================================================================
//================================================================================
// This file is part of DCDB (DataCenter DataBase)
// Copyright (C) 2019-2019 Leibniz Supercomputing Centre
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//================================================================================
#ifndef DCDBPUSHER_INCLUDES_ENTITYINTERFACE_H_
#define DCDBPUSHER_INCLUDES_ENTITYINTERFACE_H_
#include <memory>
#include <boost/asio.hpp>
#include "logging.h"
/** Indentation when printing configuration */
#define eInd " "
/**
* @brief Abstract interface defining sensor entity functionality.
*
* @ingroup pusherplugins
*/
class EntityInterface {
public:
using strand = boost::asio::io_service::strand;
/**
* @brief Constructor
*
* @details Does not initialize _strand.
*
* @param name Name of the entity.
*/
EntityInterface(const std::string& name) :
_name(name),
_mqttPart(""),
_strand(nullptr) {}
/**
* @brief Copy constructor
*
* @details Does not initialize _strand.
*
* @param other Entity to copy construct from.
*/
EntityInterface(const EntityInterface& other) :
_name(other._name),
_mqttPart(other._mqttPart),
_strand(nullptr) {}
/**
* @brief Destructor
*/
virtual ~EntityInterface() {}
/**
* @brief Assignment operator
*
* @details _strand is uninitialized afterwards.
*
* @param other Entity to assign from.
*
* @return EntityInterface
*/
EntityInterface& operator=(const EntityInterface& other) {
_name = other._name;
_mqttPart = other._mqttPart;
_strand = nullptr;
return *this;
}
const std::string& getEntityName() const { return _name; }
const std::string& getMqttPart() const { return _mqttPart; }
const std::unique_ptr<strand>& getStrand() const { return _strand; }
void setEntityName(const std::string& name) { _name = name; }
void setMqttPart(const std::string& mqttPart) {
_mqttPart = mqttPart;
//sanitize mqttPart into uniform /xxxx format
if (_mqttPart.front() != '/') {
_mqttPart.insert(0, "/");
}
if (_mqttPart.back() == '/') {
_mqttPart.erase(_mqttPart.size()-1);
}
}
/**
* @brief Initialize this entity.
*
* @details Initializes base class and subsequently calls init().
*
* @param io IO service to initialize _strand with.
*/
void initEntity(boost::asio::io_service& io) {
if (!_strand) {
_strand.reset(new strand(io));
}
this->init();
}
/**
* @brief Print complete configuration of this entity.
*
* @details Prints configuration of base class and subsequently calls
* printConfig().
*
* @param ll Log severity level to be used from logger.
*/
void printEntityConfig(LOG_LEVEL ll) {
LOG_VAR(ll) << " " << "Entity " << _name;
if (_mqttPart != "") {
LOG_VAR(ll) << eInd << "MQTT part: " << _mqttPart;
}
this->printConfig(ll);
}
/**
* @brief Initialize derived class (if necessary).
*/
virtual void init() { /* do nothing if not overwritten */ };
/**
* @brief Print configuration of derived class.
*
* @param ll Log severity level to be used from logger.
*/
virtual void printConfig(LOG_LEVEL ll) = 0;
protected:
std::string _name; /**< Name of the entity */
std::string _mqttPart; /**< Partial MQTT topic identifying this entity */
std::unique_ptr<strand> _strand; /**< Provides serialized handler execution
to avoid race conditions */
LOGGER lg; /**< Logging instance */
};
#endif /* DCDBPUSHER_INCLUDES_ENTITYINTERFACE_H_ */
...@@ -40,18 +40,15 @@ ...@@ -40,18 +40,15 @@
double BACnetClient::_presentValue; double BACnetClient::_presentValue;
uint8_t BACnetClient::_handlerTransmitBuffer[MAX_PDU]; uint8_t BACnetClient::_handlerTransmitBuffer[MAX_PDU];
BACnetClient::BACnetClient() { BACnetClient::BACnetClient(const std::string& name) :
_strand = NULL; EntityInterface(name),
_invokeId = 0; _invokeId(0),
_presentValue = 0; _timeout(1000) {
_timeout = 1000; _presentValue = 0;
_targetAddress = {0}; _targetAddress = {0};
} }
BACnetClient::~BACnetClient() { BACnetClient::~BACnetClient() {
if (_strand) {
delete _strand;
}
datalink_cleanup(); datalink_cleanup();
} }
...@@ -194,13 +191,6 @@ double BACnetClient::readProperty(uint32_t deviceObjInstance, uint32_t objInstan ...@@ -194,13 +191,6 @@ double BACnetClient::readProperty(uint32_t deviceObjInstance, uint32_t objInstan
return _presentValue; return _presentValue;
} }
void BACnetClient::initializeStrand(boost::asio::io_service& io) {
if (!_strand) {
_strand = new boost::asio::io_service::strand(io);
}
}
void BACnetClient::unrecognizedServiceHandler(uint8_t * service_request, uint16_t service_len, BACNET_ADDRESS * src, BACNET_CONFIRMED_SERVICE_DATA * service_data) { void BACnetClient::unrecognizedServiceHandler(uint8_t * service_request, uint16_t service_len, BACNET_ADDRESS * src, BACNET_CONFIRMED_SERVICE_DATA * service_data) {
boost::log::sources::severity_logger<boost::log::trivial::severity_level> lg; boost::log::sources::severity_logger<boost::log::trivial::severity_level> lg;
int pdu_len = 0; int pdu_len = 0;
...@@ -309,7 +299,6 @@ void BACnetClient::rejectHandler(BACNET_ADDRESS * src, uint8_t invokeId, uint8_t ...@@ -309,7 +299,6 @@ void BACnetClient::rejectHandler(BACNET_ADDRESS * src, uint8_t invokeId, uint8_t
} }
void BACnetClient::printConfig(LOG_LEVEL ll) { void BACnetClient::printConfig(LOG_LEVEL ll) {
LOG_VAR(ll) << " BACnetClient here"; LOG_VAR(ll) << eInd << "Timeout: " << _timeout;
LOG_VAR(ll) << " Timeout: " << _timeout;
} }
...@@ -27,88 +27,121 @@ ...@@ -27,88 +27,121 @@
#ifndef BACNETCLIENT_H_ #ifndef BACNETCLIENT_H_
#define BACNETCLIENT_H_ #define BACNETCLIENT_H_
#include "../../includes/EntityInterface.h"
#include "bacnet/apdu.h" #include "bacnet/apdu.h"
#include "bacnet/bacenum.h" #include "bacnet/bacenum.h"
#include "bacnet/datalink.h" #include "bacnet/datalink.h"
#include <boost/asio.hpp>
#include "logging.h"
/* /*
* NOTE * NOTE
* Had to make some member variables static because of BACnet Stack handler function requirements. * Had to make some member variables static because of BACnet Stack handler
* This should be no problem as we are using only one instance of BACnetClient with serialized access (_strand). * function requirements. This should be no problem as we are using only one
* One should ensure to keep the single instance property in the future. * instance of BACnetClient with serialized access (_strand). One should ensure
* to keep the single instance property in the future.
*/ */
/** /**
* @brief Client to handle BACnet protocol communication. Only one instance allowed! * @brief Client to handle BACnet protocol communication. Only one instance
* allowed!
* *
* @ingroup bacnet * @ingroup bacnet
*/ */
class BACnetClient { class BACnetClient : public EntityInterface {
public:
BACnetClient();
virtual ~BACnetClient();
void initializeStrand(boost::asio::io_service& io); public:
boost::asio::io_service::strand* getStrand() const { BACnetClient(const std::string& name = "BACnetClient");
return _strand; BACnetClient(const BACnetClient& other) = delete;
} virtual ~BACnetClient();
BACnetClient& operator=(const BACnetClient& other) = delete;
/** /**
* Initialize datalink layer and address cache. * @brief Initialize datalink layer and address cache.
* We assume BACnet/IP protocol is used. Also you need to compile with environment variable BACNET_ADDRESS_CACHE_FILE set *
* to enable initialization of address cache from file "address_cache". * @details We assume BACnet/IP protocol is used. Also you need to compile
* with environment variable BACNET_ADDRESS_CACHE_FILE set to
* enable initialization of address cache from file "address_cache".
* *
* @param interface Name of network interface to use * @param interface Name of network interface to use.
* @param address_cache (Path and) filename of the address cache file where the addresses of BACnet devices are stored * @param address_cache (Path and) filename of the address cache file where
* @param port Which port to use of the interface * the addresses of BACnet devices are stored.
* @param timeout Number of milliseconds to wait for a packet when receiving * @param port Which port to use of the interface.
* @param apdu_timeout Number of milliseconds before timeout when sending * @param timeout Number of milliseconds to wait for a packet when
* @param retries Number of retries after an apdu timeout occurs * receiving.
* @param apdu_timeout Number of milliseconds before timeout when sending.
* @param retries Number of retries after an apdu timeout occurs.
*/ */
void init(std::string interface, const std::string& address_cache, unsigned port = 47808, unsigned timeout = 1000, unsigned apdu_timeout = 200, unsigned retries = 0); void init(std::string interface,
const std::string& address_cache,
unsigned port = 47808,
unsigned timeout = 1000,
unsigned apdu_timeout = 200,
unsigned retries = 0);
/** /**
* Sends a READ_PROPERTY request for PROP_PRESENT_VALUE to specified device and decodes the response (READ_PROPERTY_ACK). * @brief Sends a READ_PROPERTY request for PROP_PRESENT_VALUE to specified
* device and decodes the response (READ_PROPERTY_ACK).
* *
* @param deviceObjectInstance Number of device from which to request the current value. * @param deviceObjInstance Number of device from which to request
* @param objectInstance * the current value.
* @param objectType * @param objInstance Object instance
* @param objectProperty * @param objType Object type
* @param objectIndex * @param objProperty Object property
* @param objIndex Object index
* *
* @return The value sent as response from the device. If an error occurs a runtime exception is thrown. * @return The value sent as response from the device. If an error occurs a
* runtime exception is thrown.
* *
* @throws Runtime error * @throws Runtime error
*/ */
double readProperty(uint32_t deviceObjInstance, uint32_t objInstance = 0, BACNET_OBJECT_TYPE objType = OBJECT_DEVICE, BACNET_PROPERTY_ID objProperty = PROP_PRESENT_VALUE, int32_t objIndex = BACNET_ARRAY_ALL); double readProperty(uint32_t deviceObjInstance,
uint32_t objInstance = 0,
BACNET_OBJECT_TYPE objType = OBJECT_DEVICE,
BACNET_PROPERTY_ID objProperty = PROP_PRESENT_VALUE,
int32_t objIndex = BACNET_ARRAY_ALL);
/** /**
* Print information about configured attributes * @brief Print configuration of this class.
* *
* @param ll Severity level of the log messages. * @param ll Log severity level to be used from logger.
*/ */
void printConfig(LOG_LEVEL ll); void printConfig(LOG_LEVEL ll) override;
private: private:
/* Handler to process incoming BACnet data */ /* Handler to process incoming BACnet data */
static void unrecognizedServiceHandler(uint8_t * service_request, uint16_t service_len, BACNET_ADDRESS * src, BACNET_CONFIRMED_SERVICE_DATA * service_data);
/** /**
* Handler for a ReadProperty ACK. * @brief Handler for a ReadProperty ACK.
* Here the actual processing of a valid response takes place * @details Here the actual processing of a valid response takes place.
* *
* @param service_request The contents of the service request. * @param service_request The contents of the service request.
* @param service_len The length of the service_request. * @param service_len The length of the service_request.
* @param src BACNET_ADDRESS of the source of the message * @param src BACNET_ADDRESS of the source of the message.
* @param service_data The BACNET_CONFIRMED_SERVICE_DATA information decoded from the APDU header of this message. * @param service_data The BACNET_CONFIRMED_SERVICE_DATA information
* decoded from the APDU header of this message.
*/ */
static void readPropertyAckHandler(uint8_t * service_request, uint16_t service_len, BACNET_ADDRESS * src, BACNET_CONFIRMED_SERVICE_ACK_DATA * service_data); static void readPropertyAckHandler(uint8_t * service_request,
static void errorHandler(BACNET_ADDRESS * src, uint8_t invokeId, BACNET_ERROR_CLASS error_class, BACNET_ERROR_CODE error_code); uint16_t service_len,
static void abortHandler(BACNET_ADDRESS * src, uint8_t invokeId, uint8_t abort_reason, bool server); BACNET_ADDRESS * src,
static void rejectHandler(BACNET_ADDRESS * src, uint8_t invokeId, uint8_t reject_reason); BACNET_CONFIRMED_SERVICE_ACK_DATA * service_data);
static void unrecognizedServiceHandler(uint8_t * service_request,
uint16_t service_len,
BACNET_ADDRESS * src,
BACNET_CONFIRMED_SERVICE_DATA * service_data);
static void errorHandler(BACNET_ADDRESS * src,
uint8_t invokeId,
BACNET_ERROR_CLASS error_class,
BACNET_ERROR_CODE error_code);
static void abortHandler(BACNET_ADDRESS * src,
uint8_t invokeId,
uint8_t abort_reason,
bool server);
static void rejectHandler(BACNET_ADDRESS * src,
uint8_t invokeId,
uint8_t reject_reason);
uint8_t _invokeId; uint8_t _invokeId;
unsigned _timeout; unsigned _timeout;
...@@ -116,10 +149,6 @@ private: ...@@ -116,10 +149,6 @@ private:
static double _presentValue; static double _presentValue;
static uint8_t _handlerTransmitBuffer[MAX_PDU]; static uint8_t _handlerTransmitBuffer[MAX_PDU];
BACNET_ADDRESS _targetAddress; //store as member variable to enable access in handler methods BACNET_ADDRESS _targetAddress; //store as member variable to enable access in handler methods
boost::asio::io_service::strand* _strand;
LOGGER lg;
}; };
#endif /* BACNETCLIENT_H_ */ #endif /* BACNETCLIENT_H_ */
...@@ -49,7 +49,7 @@ BACnetSensorGroup& BACnetSensorGroup::operator=(const BACnetSensorGroup& other) ...@@ -49,7 +49,7 @@ BACnetSensorGroup& BACnetSensorGroup::operator=(const BACnetSensorGroup& other)
void BACnetSensorGroup::init(boost::asio::io_service& io) { void BACnetSensorGroup::init(boost::asio::io_service& io) {
SensorGroupTemplate::init(io); SensorGroupTemplate::init(io);
if(_bacClient) { if(_bacClient) {
_bacClient->initializeStrand(io); _bacClient->initEntity(io);
} else { } else {
LOG(error) << "No BACnetClient set for sensor " << _groupName << "! Cannot initialize sensor."; LOG(error) << "No BACnetClient set for sensor " << _groupName << "! Cannot initialize sensor.";
} }
......
...@@ -43,29 +43,59 @@ ...@@ -43,29 +43,59 @@
#define RETRIES 2 #define RETRIES 2
IPMIHost::IPMIHost() { IPMIHost::IPMIHost(const std::string& name) :
_ipmiCtx = nullptr; EntityInterface(name),
_sensorReadCtx = nullptr; _ipmiCtx(nullptr),
_hostName = ""; _sensorReadCtx(nullptr),
_userName = std::string("admin"); _userName("admin"),
_password = std::string("admin"); _password("admin"),
_cache = ""; _cache(""),
_auth = IPMI_AUTHENTICATION_TYPE_MD5; _auth(IPMI_AUTHENTICATION_TYPE_MD5),
_priv = IPMI_PRIVILEGE_LEVEL_ADMIN; _priv(IPMI_PRIVILEGE_LEVEL_ADMIN),
_cipher = 3; _cipher(3),
_ipmiVersion = 1; _ipmiVersion(1),
_mqttPart = ""; _sessionTimeout(0),
_retransmissionTimeout = 0; _retransmissionTimeout(0),
_sessionTimeout = 0; _errorCount(0),
_strand = nullptr; _delayNextReadUntil(0) {
_errorCount = 0;
_delayNextReadUntil = 0;
} }
IPMIHost::~IPMIHost() { IPMIHost::IPMIHost(const IPMIHost& other) :
if (_strand) { EntityInterface(other),
delete _strand; _ipmiCtx(nullptr),
} _sensorReadCtx(nullptr),
_userName(other._userName),
_password(other._password),
_cache(other._cache),
_auth(other._auth),
_priv(other._priv),
_cipher(other._cipher),
_ipmiVersion(other._ipmiVersion),
_retransmissionTimeout(other._retransmissionTimeout),
_sessionTimeout(other._sessionTimeout),
_errorCount(0),
_delayNextReadUntil(other._delayNextReadUntil) {
}
IPMIHost::~IPMIHost() {}
IPMIHost& IPMIHost::operator=(const IPMIHost& other) {
EntityInterface::operator=(other);
_ipmiCtx = nullptr;
_sensorReadCtx = nullptr;
_userName = other._userName;
_password = other._password;
_cache = other._cache;
_auth = other._auth;
_priv = other._priv;
_cipher = other._cipher;
_ipmiVersion = other._ipmiVersion;
_sessionTimeout = other._sessionTimeout;
_retransmissionTimeout = other._retransmissionTimeout;
_errorCount = 0;
_delayNextReadUntil = other._delayNextReadUntil;
return *this;
} }
int IPMIHost::connect() { int IPMIHost::connect() {
...@@ -82,9 +112,9 @@ int IPMIHost::connect() { ...@@ -82,9 +112,9 @@ int IPMIHost::connect() {
int flags = IPMI_FLAGS_DEFAULT; int flags = IPMI_FLAGS_DEFAULT;
int rc; int rc;
if (_ipmiVersion == 1) { if (_ipmiVersion == 1) {
rc = ipmi_ctx_open_outofband(_ipmiCtx, _hostName.c_str(), _userName.c_str(), _password.c_str(), _auth, _priv, _sessionTimeout, _retransmissionTimeout, workaround_flags, flags); rc = ipmi_ctx_open_outofband(_ipmiCtx, _name.c_str(), _userName.c_str(), _password.c_str(), _auth, _priv, _sessionTimeout, _retransmissionTimeout, workaround_flags, flags);
} else { } else {
rc = ipmi_ctx_open_outofband_2_0(_ipmiCtx, _hostName.c_str(), _userName.c_str(), _password.c_str(), NULL, 0, _priv, _cipher, _sessionTimeout, _retransmissionTimeout, workaround_flags, flags); rc = ipmi_ctx_open_outofband_2_0(_ipmiCtx, _name.c_str(), _userName.c_str(), _password.c_str(), NULL, 0, _priv, _cipher, _sessionTimeout, _retransmissionTimeout, workaround_flags, flags);
} }
if (rc < 0) { if (rc < 0) {
_errorMsg = "Error opening IPMI connection: " + std::string(ipmi_ctx_errormsg(_ipmiCtx)); _errorMsg = "Error opening IPMI connection: " + std::string(ipmi_ctx_errormsg(_ipmiCtx));
...@@ -126,14 +156,14 @@ bool IPMIHost::getSdrRecord(uint16_t recordId, std::vector<uint8_t>& record) { ...@@ -126,14 +156,14 @@ bool IPMIHost::getSdrRecord(uint16_t recordId, std::vector<uint8_t>& record) {
if (ipmi_sdr_cache_open(sdrCtx, _ipmiCtx, _cache.c_str()) < 0) { if (ipmi_sdr_cache_open(sdrCtx, _ipmiCtx, _cache.c_str()) < 0) {
if ((ipmi_sdr_ctx_errnum(sdrCtx) == IPMI_SDR_ERR_CACHE_READ_CACHE_DOES_NOT_EXIST) || (ipmi_sdr_ctx_errnum(sdrCtx) == IPMI_SDR_ERR_CACHE_INVALID) || (ipmi_sdr_ctx_errnum(sdrCtx) == IPMI_SDR_ERR_CACHE_OUT_OF_DATE)) { if ((ipmi_sdr_ctx_errnum(sdrCtx) == IPMI_SDR_ERR_CACHE_READ_CACHE_DOES_NOT_EXIST) || (ipmi_sdr_ctx_errnum(sdrCtx) == IPMI_SDR_ERR_CACHE_INVALID) || (ipmi_sdr_ctx_errnum(sdrCtx) == IPMI_SDR_ERR_CACHE_OUT_OF_DATE)) {
if ((ipmi_sdr_ctx_errnum(sdrCtx) == IPMI_SDR_ERR_CACHE_INVALID) || (ipmi_sdr_ctx_errnum(sdrCtx) == IPMI_SDR_ERR_CACHE_OUT_OF_DATE)) { if ((ipmi_sdr_ctx_errnum(sdrCtx) == IPMI_SDR_ERR_CACHE_INVALID) || (ipmi_sdr_ctx_errnum(sdrCtx) == IPMI_SDR_ERR_CACHE_OUT_OF_DATE)) {
LOG(debug) << _hostName << "Deleting SDR cache " << _cache; LOG(debug) << _name << "Deleting SDR cache " << _cache;
ipmi_sdr_cache_close(sdrCtx); ipmi_sdr_cache_close(sdrCtx);
ipmi_sdr_cache_delete(sdrCtx, _cache.c_str()); ipmi_sdr_cache_delete(sdrCtx, _cache.c_str());
} }
if (ipmi_sdr_cache_create(sdrCtx, _ipmiCtx, _cache.c_str(), IPMI_SDR_CACHE_CREATE_FLAGS_DEFAULT, NULL, NULL) == 0) { if (ipmi_sdr_cache_create(sdrCtx, _ipmiCtx, _cache.c_str(), IPMI_SDR_CACHE_CREATE_FLAGS_DEFAULT, NULL, NULL) == 0) {
LOG(debug) << _hostName << ": Created new SDR cache " << _cache; LOG(debug) << _name << ": Created new SDR cache " << _cache;
} else { } else {
LOG(debug) << _hostName << ": Error creating new SDR cache " << _cache; LOG(debug) << _name << ": Error creating new SDR cache " << _cache;
} }
} else { } else {
_errorMsg = "Error opening SDR cache: " + std::string(ipmi_sdr_ctx_errormsg(sdrCtx)); _errorMsg = "Error opening SDR cache: " + std::string(ipmi_sdr_ctx_errormsg(sdrCtx));
...@@ -289,26 +319,19 @@ void IPMIHost::increaseErrorCount() { ...@@ -289,26 +319,19 @@ void IPMIHost::increaseErrorCount() {
} }
} }
void IPMIHost::initializeStrand(boost::asio::io_service& io) {
if (!_strand) {
_strand = new boost::asio::io_service::strand(io);
}
}
void IPMIHost::printConfig(LOG_LEVEL ll) { void IPMIHost::printConfig(LOG_LEVEL ll) {
LOG_VAR(ll) << " IPMIHost: " << _hostName; LOG_VAR(ll) << eInd << "IPMIHost: " << getHostName();
LOG_VAR(ll) << " UserName: " << _userName; LOG_VAR(ll) << eInd << "UserName: " << getUserName();
#ifdef DEBUG #ifdef DEBUG
LOG_VAR(ll) << " Password: " << _password; LOG_VAR(ll) << eInd << "Password: " << getPassword();
#else #else
LOG_VAR(ll) << " Password not shown"; LOG_VAR(ll) << eInd << "Password not shown";
#endif #endif
LOG_VAR(ll) << " Cache: " << _cache; LOG_VAR(ll) << eInd << "Cache: " << getCache();
LOG_VAR(ll) << " Auth: " << _auth; LOG_VAR(ll) << eInd << "Auth: " << getAuth();
LOG_VAR