Commit 9e43f473 authored by Micha Mueller's avatar Micha Mueller
Browse files

Complete first idea of BACnetClient. Probably needs some more work until if functions

parent b482c4fe
......@@ -10,6 +10,7 @@
//#include "client.h"
#include "bacnetTEMP/address.h"
#include "bacnetTEMP/apdu.h"
#include "bacnetTEMP/bacapp.h"
#include "bacnetTEMP/bactext.h"
#include "bacnetTEMP/datalink.h"
#include "bacnetTEMP/npdu.h"
......@@ -19,30 +20,64 @@
BACnetClient::BACnetClient() {
_strand = NULL;
_invokeId = 0;
_presentValue = 0;
_timeout = 1000;
//TODO setup datalink
_targetAddress = {0};
}
BACnetClient::~BACnetClient() {
// TODO teardown datalink
datalink_cleanup();
}
void BACnetClient::init() {
//TODO setup address cache
//compile BACnet stack with BACNET_ADDRESS_CACHE_FILE for initialization from file address_cache
address_init();
//setup datalink
char *pEnv = NULL;
//#if defined(BACDL_BIP)
pEnv = getenv("BACNET_IP_PORT");
if (pEnv) {
bip_set_port(htons((uint16_t) strtol(pEnv, NULL, 0)));
} else {
/* BIP_Port is statically initialized to 0xBAC0,
* so if it is different, then it was programmatically altered,
* and we shouldn't just stomp on it here.
* Unless it is set below 1024, since:
* "The range for well-known ports managed by the IANA is 0-1023."
*/
if (ntohs(bip_get_port()) < 1024)
bip_set_port(htons(0xBAC0));
}
//#endif
pEnv = getenv("BACNET_APDU_TIMEOUT");
if (pEnv) {
apdu_timeout_set((uint16_t) strtol(pEnv, NULL, 0));
} else {
//
}
pEnv = getenv("BACNET_APDU_RETRIES");
if (pEnv) {
apdu_retries_set((uint8_t) strtol(pEnv, NULL, 0));
}
/* === Initialize the Datalink Here === */
if (!datalink_init(getenv("BACNET_IFACE"))) {
exit(1);
}
//end setup datalink
//Device_Init(NULL); // Initializes Device Object; TODO do we need this?
/* set the handler for all the services we don't implement
It is required to send the proper reject message... */
//apdu_set_unrecognized_service_handler_handler(handler_unrecognized_service); //TODO implement handler_unrecognized_service
apdu_set_unrecognized_service_handler_handler(handler_unrecognized_service);
/* we must implement read property - it's required! */
// TODO should we really implement read property just because it's required? otherwise it should be handled by handler for unrecognized services
//apdu_set_confirmed_handler(SERVICE_CONFIRMED_READ_PROPERTY, handler_read_property);
//NOTE: no handler for read property set even though it is required. We are no real BACnet device
/* we only need to handle the data coming back from confirmed (read property) requests */
//apdu_set_confirmed_ack_handler(SERVICE_CONFIRMED_READ_PROPERTY, My_Read_Property_Ack_Handler); //TODO implement read_property_ack_handler
apdu_set_confirmed_ack_handler(SERVICE_CONFIRMED_READ_PROPERTY, readPropertyAckHandler); //TODO implement read_property_ack_handler
/* handle any errors coming back */
apdu_set_error_handler(SERVICE_CONFIRMED_READ_PROPERTY, errorHandler);
......@@ -51,12 +86,13 @@ void BACnetClient::init() {
}
uint64_t BACnetClient::readProperty(uint32_t deviceObjInstance, uint32_t objInstance, BACNET_OBJECT_TYPE objType, BACNET_PROPERTY_ID objProperty, int32_t objIndex) {
uint8_t invoke_id = 0;
//TODO better use readPropertyMultiple?
uint16_t pdu_lenRec = 0;
int pdu_lenSent = 0;
int bytes_sent = 0;
unsigned max_apdu = 0;
BACNET_ADDRESS dest = {0}; //where message goes to
_targetAddress = {0}; //where message goes to
BACNET_ADDRESS src = {0}; //where response came from
BACNET_ADDRESS myAddr = {0};//our address
......@@ -65,12 +101,12 @@ uint64_t BACnetClient::readProperty(uint32_t deviceObjInstance, uint32_t objInst
uint8_t RecBuf[MAX_MPDU] = {0};
if (!address_get_by_device(deviceObjInstance, &max_apdu, &dest)) {
if (!address_get_by_device(deviceObjInstance, &max_apdu, &_targetAddress)) {
throw std::runtime_error("Address not found");
}
/*
if (!dest) {
if (!_targetAddress) {
throw std::runtime_error("Destination address empty");
}
*/
......@@ -81,35 +117,35 @@ uint64_t BACnetClient::readProperty(uint32_t deviceObjInstance, uint32_t objInst
}
*/
if (!(invoke_id = tsm_next_free_invokeID())) {
if (!(_invokeId = tsm_next_free_invokeID())) {
throw std::runtime_error("No TSM available");
}
/* encode the NPDU portion of the packet */
datalink_get_my_address(&my_address);
npdu_encode_npdu_data(&npdu_data, true, MESSAGE_PRIORITY_NORMAL);
pdu_lenSent = npdu_encode_pdu(&_handlerTransmitBuffer[0], &dest, &my_address, &npdu_data);
pdu_lenSent = npdu_encode_pdu(&_handlerTransmitBuffer[0], &_targetAddress, &my_address, &npdu_data);
/* encode the APDU portion of the packet */
data.object_type = objType;
data.object_instance = objInstance;
data.object_property = objProperty;
data.array_index = objIndex;
pdu_lenSent += rp_encode_apdu(&_handlerTransmitBuffer[pdu_lenSent], invoke_id, &data);
pdu_lenSent += rp_encode_apdu(&_handlerTransmitBuffer[pdu_lenSent], _invokeId, &data);
/* will it fit in the sender?
note: if there is a bottleneck router in between
us and the destination, we won't know unless
we have a way to check for that and update the
max_apdu in the address binding table. */
if ((uint16_t) pdu_lenSent < max_apdu) {
tsm_set_confirmed_unsegmented_transaction(invoke_id, &dest, &npdu_data, &_handlerTransmitBuffer[0], (uint16_t) pdu_lenSent);
bytes_sent = datalink_send_pdu(&dest, &npdu_data, &_handlerTransmitBuffer[0], pdu_lenSent);
tsm_set_confirmed_unsegmented_transaction(_invokeId, &_targetAddress, &npdu_data, &_handlerTransmitBuffer[0], (uint16_t) pdu_lenSent);
bytes_sent = datalink_send_pdu(&_targetAddress, &npdu_data, &_handlerTransmitBuffer[0], pdu_lenSent);
if (bytes_sent <= 0) {
throw std::runtime_error("Failed to Send ReadProperty Request (%s)", strerror(errno));
}
} else {
tsm_free_invoke_id(invoke_id);
invoke_id = 0;
tsm_free_invoke_id(_invokeId);
_invokeId = 0;
throw std::runtime_error("Failed to Send ReadProperty Request (exceeds destination maximum APDU)");
}
......@@ -140,12 +176,12 @@ uint64_t BACnetClient::readProperty(uint32_t deviceObjInstance, uint32_t objInst
}
}
} else {
tsm_free_invoke_id(invoke_id);
tsm_free_invoke_id(_invokeId);
throw std::runtime_error("Timeout while waiting for response");
}
if (!tsm_invoke_id_free(invoke_id)) { //should be freed by apdu_handler on success
tsm_free_invoke_id(invoke_id);
if (!tsm_invoke_id_free(_invokeId)) { //should be freed by apdu_handler on success
tsm_free_invoke_id(_invokeId);
throw std::runtime_error("Invoke ID was not freed");
}
......@@ -159,23 +195,21 @@ void BACnetClient::initializeStrand(boost::asio::io_service& io) {
}
}
void BACnetClient::handler_unrecognized_service(uint8_t * service_request, uint16_t service_len, BACNET_ADDRESS * src, BACNET_CONFIRMED_SERVICE_DATA * service_data) {
int len = 0;
void BACnetClient::unrecognizedServiceHandler(uint8_t * service_request, uint16_t service_len, BACNET_ADDRESS * src, BACNET_CONFIRMED_SERVICE_DATA * service_data) {
int pdu_len = 0;
int bytes_sent = 0;
BACNET_NPDU_DATA npdu_data;
BACNET_ADDRESS my_address;
BACNET_ADDRESS myAddress;
(void) service_request;
(void) service_len;
/* encode the NPDU portion of the packet */
datalink_get_my_address(&my_address);
datalink_get_my_address(&myAddress);
npdu_encode_npdu_data(&npdu_data, false, MESSAGE_PRIORITY_NORMAL);
pdu_len = npdu_encode_pdu(&_handlerTransmitBuffer[0], src, &my_address, &npdu_data); //TODO any problems with using member handlerTransmitBuffer ?
pdu_len = npdu_encode_pdu(&_handlerTransmitBuffer[0], src, &myAddress, &npdu_data);
/* encode the APDU portion of the packet */
len = reject_encode_apdu(&_handlerTransmitBuffer[pdu_len], service_data->invoke_id, REJECT_REASON_UNRECOGNIZED_SERVICE);
pdu_len += len;
pdu_len += reject_encode_apdu(&_handlerTransmitBuffer[pdu_len], service_data->invoke_id, REJECT_REASON_UNRECOGNIZED_SERVICE);
/* send the data */
bytes_sent = datalink_send_pdu(src, &npdu_data, &_handlerTransmitBuffer[0], pdu_len);
if (bytes_sent > 0) {
......@@ -185,15 +219,80 @@ void BACnetClient::handler_unrecognized_service(uint8_t * service_request, uint1
}
}
void BACnetClient::errorHandler(BACNET_ADDRESS * src, uint8_t invoke_id, BACNET_ERROR_CLASS error_class, BACNET_ERROR_CODE error_code) {
LOG(error) << "BACnet Error: " << bactext_error_class_name((int) error_class) << bactext_error_code_name((int) error_code);
void BACnetClient::readPropertyAckHandler(uint8_t * service_request, uint16_t service_len, BACNET_ADDRESS * src, BACNET_CONFIRMED_SERVICE_ACK_DATA * service_data)
{
int serviceLen = 0;
int appDataLen = 0;
uint8_t *application_data;
int application_data_len;
BACNET_READ_PROPERTY_DATA data;
BACNET_APPLICATION_DATA_VALUE value; /* for decode value data */
if (!(address_match(&_targetAddress, src) && (service_data->invoke_id == _invokeId))) {
throw std::runtime_error("Message not determined for us");
}
serviceLen = rp_ack_decode_service_request(service_request, service_len, &data);
if (serviceLen <= 0) {
throw std::runtime_error("Decode failed");
}
// rp_ack_print_data(&data);
if (data) {
application_data = data.application_data;
application_data_len = data.application_data_len;
appDataLen = bacapp_decode_application_data(application_data, (uint8_t) application_data_len, &value);
//bacapp_print_value(stdout, &object_value);
//TODO what kind of data is returned? which cases do we need to handle? fit they into uint64_t at all??
switch (value.tag) {
case BACNET_APPLICATION_TAG_NULL:
LOG(trace) << "TAG_NULL";
_presentValue = 0;
break;
case BACNET_APPLICATION_TAG_BOOLEAN:
LOG(trace) << "TAG_BOOLEAN";
_presentValue = (value.type.Boolean) ? 1 : 0;
break;
case BACNET_APPLICATION_TAG_UNSIGNED_INT:
LOG(trace) << "TAG_UNSIGNED_INT";
_presentValue = (unsigned long) value.type.Unsigned_Int;
break;
case BACNET_APPLICATION_TAG_SIGNED_INT:
LOG(trace) << "TAG_SIGNED_INT";
_presentValue = (long) value.type.Signed_Int;
break;
case BACNET_APPLICATION_TAG_REAL:
LOG(trace) << "TAG_REAL";
_presentValue = (double) value.type.Real;
break;
case BACNET_APPLICATION_TAG_DOUBLE:
LOG(trace) << "TAG_DOUBLE";
_presentValue = value.type.Double;
break;
default:
throw std::runtime_error("Value tag not supported");
break;
}
}
}
void BACnetClient::abortHandler(BACNET_ADDRESS * src, uint8_t invoke_id, uint8_t abort_reason, bool server) {
LOG(error) << "BACnet Abort: " << bactext_abort_reason_name((int) abort_reason);
void BACnetClient::errorHandler(BACNET_ADDRESS * src, uint8_t invokeId, BACNET_ERROR_CLASS error_class, BACNET_ERROR_CODE error_code) {
if (address_match(&_targetAddress, src) && (invokeId == _invokeId)) {
LOG(error) << "BACnet Error: " << bactext_error_class_name((int) error_class) << bactext_error_code_name((int) error_code);
}
}
void BACnetClient::rejectHandler(BACNET_ADDRESS * src, uint8_t invoke_id, uint8_t reject_reason) {
LOG(error) << "BACnet Reject: " << bactext_reject_reason_name((int) reject_reason);
void BACnetClient::abortHandler(BACNET_ADDRESS * src, uint8_t invokeId, uint8_t abort_reason, bool server) {
if (address_match(&_targetAddress, src) && (invokeId == _invokeId)) {
LOG(error) << "BACnet Abort: " << bactext_abort_reason_name((int) abort_reason);
}
}
void BACnetClient::rejectHandler(BACNET_ADDRESS * src, uint8_t invokeId, uint8_t reject_reason) {
if (address_match(&_targetAddress, src) && (invokeId == _invokeId)) {
LOG(error) << "BACnet Reject: " << bactext_reject_reason_name((int) reject_reason);
}
}
......@@ -46,14 +46,28 @@ public:
private:
/* Handler to process incoming BACnet data */
void handler_unrecognized_service(uint8_t * service_request, uint16_t service_len, BACNET_ADDRESS * src, BACNET_CONFIRMED_SERVICE_DATA * service_data);
void errorHandler(BACNET_ADDRESS * src, uint8_t invoke_id, BACNET_ERROR_CLASS error_class, BACNET_ERROR_CODE error_code);
void abortHandler(BACNET_ADDRESS * src, uint8_t invoke_id, uint8_t abort_reason, bool server);
void rejectHandler(BACNET_ADDRESS * src, uint8_t invoke_id, uint8_t reject_reason);
void unrecognizedServiceHandler(uint8_t * service_request, uint16_t service_len, BACNET_ADDRESS * src, BACNET_CONFIRMED_SERVICE_DATA * service_data);
/**
* Handler for a ReadProperty ACK.
* Doesn't actually do anything, except, for debugging, to print out the ACK data of a matching request.
*
* @param service_request The contents of the service request.
* @param service_len The length of the service_request.
* @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.
*/
void readPropertyAckHandler(uint8_t * service_request, uint16_t service_len, BACNET_ADDRESS * src, BACNET_CONFIRMED_SERVICE_ACK_DATA * service_data);
void errorHandler(BACNET_ADDRESS * src, uint8_t invokeId, BACNET_ERROR_CLASS error_class, BACNET_ERROR_CODE error_code);
void abortHandler(BACNET_ADDRESS * src, uint8_t invokeId, uint8_t abort_reason, bool server);
void rejectHandler(BACNET_ADDRESS * src, uint8_t invokeId, uint8_t reject_reason);
uint8_t _invokeId; //store as member variable to enable access in handler methods
unsigned _timeout;
BACNET_READ_PROPERTY_DATA _propertyData;
uint64_t _presentValue;
uint8_t _handlerTransmitBuffer[MAX_PDU];
BACNET_ADDRESS _targetAddress; //store as member variable to enable access in handler methods
boost::asio::io_service::strand* _strand;
......
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