BACnetClient.cpp 11 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//================================================================================
// Name        : BACnetClient.cpp
// Author      : Micha Mueller
// Copyright   : Leibniz Supercomputing Centre
// Description : Source file for BACnetClient class.
//================================================================================

//================================================================================
// This file is part of DCDB (DataCenter DataBase)
// Copyright (C) 2018-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.
//================================================================================
26
27
28

#include "BACnetClient.h"

29
30
31
32
33
34
35
36
37
38
#include "bacnet/address.h"
#include "bacnet/apdu.h"
#include "bacnet/bacapp.h"
#include "bacnet/bacenum.h"
#include "bacnet/bactext.h"
#include "bacnet/datalink.h"
#include "bacnet/npdu.h"
#include "bacnet/reject.h"
#include "bacnet/rp.h"
#include "bacnet/tsm.h"
39

40
double BACnetClient::_presentValue;
41
uint8_t BACnetClient::_handlerTransmitBuffer[MAX_PDU];
42

43
44
45
46
47
BACnetClient::BACnetClient(const std::string& name) :
        EntityInterface(name),
        _invokeId(0),
        _timeout(1000) {
    _presentValue = 0;
48
	_targetAddress = {0};
49
50
51
}

BACnetClient::~BACnetClient() {
52
	datalink_cleanup();
53
54
}

55
void BACnetClient::init(std::string interface, const std::string& address_cache, unsigned port, unsigned timeout, unsigned apdu_timeout, unsigned retries) {
56
57
	_timeout = timeout;

58
59
60
61
62
63
64
	if (FILE *file = fopen(address_cache.c_str(), "r")) {
		fclose(file);
	} else {
		throw std::runtime_error("Can not open address cache file");
	}

	address_init_by_file(address_cache.c_str());
65
66
67
68

	//setup datalink

//#if defined(BACDL_BIP)
69
    bip_set_port(port);
70
//#endif
71
	apdu_timeout_set(apdu_timeout);
72
73
74
	apdu_retries_set(retries);

    if (!datalink_init(&interface[0])) {
75
    	throw std::runtime_error("Failed to setup datalink");
76
77
    }
    //end setup datalink
78
79
80

	/* set the handler for all the services we don't implement
	   It is required to send the proper reject message... */
81
	apdu_set_unrecognized_service_handler_handler(unrecognizedServiceHandler);
82

83
	//NOTE: no handler for read property set even though it is required. We are no real BACnet device
84
85

	/* we only need to handle the data coming back from confirmed (read property) requests */
86
	apdu_set_confirmed_ack_handler(SERVICE_CONFIRMED_READ_PROPERTY,	readPropertyAckHandler);
87
88
89
90
91

	/* handle any errors coming back */
	apdu_set_error_handler(SERVICE_CONFIRMED_READ_PROPERTY, errorHandler);
	apdu_set_abort_handler(abortHandler);
	apdu_set_reject_handler(rejectHandler);
92
93
}

94
double BACnetClient::readProperty(uint32_t deviceObjInstance, uint32_t objInstance, BACNET_OBJECT_TYPE objType, BACNET_PROPERTY_ID objProperty, int32_t objIndex) {
95
	//TODO better use readPropertyMultiple? But how to assign the properties to multiple different MQTT topics?
96

97
98
99
100
	uint16_t pdu_lenRec = 0;
	int pdu_lenSent		= 0;
	int bytes_sent		= 0;
	unsigned max_apdu 	= 0;
101
	_targetAddress	= {0};		//where message goes to
102
103
104
105
106
107
108
109
	BACNET_ADDRESS src	= {0}; 	//where response came from
	BACNET_ADDRESS myAddr = {0};//our address

	BACNET_READ_PROPERTY_DATA data;
	BACNET_NPDU_DATA npdu_data;

	uint8_t RecBuf[MAX_MPDU] = {0};

110
	if (!address_get_by_device(deviceObjInstance, &max_apdu, &_targetAddress)) {
111
112
113
114
		throw std::runtime_error("Address not found");
	}

	/*
115
	if (!_targetAddress) {
116
117
118
119
120
121
122
123
124
125
		throw std::runtime_error("Destination address empty");
	}
	*/

	/*
	if (!dcc_communication_enabled()) {
		throw std::runtime_error("Communication Control disabled");
	}
	*/

126
	if (!(_invokeId = tsm_next_free_invokeID())) {
127
128
129
130
		throw std::runtime_error("No TSM available");
	}

	/* encode the NPDU portion of the packet */
131
	datalink_get_my_address(&myAddr);
132
	npdu_encode_npdu_data(&npdu_data, true, MESSAGE_PRIORITY_NORMAL);
133
	pdu_lenSent = npdu_encode_pdu(&_handlerTransmitBuffer[0], &_targetAddress, &myAddr, &npdu_data);
134
135
136
137
138
139

	/* encode the APDU portion of the packet */
	data.object_type = objType;
	data.object_instance = objInstance;
	data.object_property = objProperty;
	data.array_index = objIndex;
140
	pdu_lenSent += rp_encode_apdu(&_handlerTransmitBuffer[pdu_lenSent], _invokeId, &data);
141
142
143
144
145
146
	/* 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) {
147
148
		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);
149
		if (bytes_sent <= 0) {
150
151
152
			std::string errorMsg = strerror(errno);
			std::string str = "Failed to send ReadProperty Request ";
			throw std::runtime_error(str + errorMsg);
153
154
		}
	} else {
155
		tsm_free_invoke_id(_invokeId);
156
157
158
159
		throw std::runtime_error("Failed to Send ReadProperty Request (exceeds destination maximum APDU)");
	}

	// returns 0 on timeout
160
	pdu_lenRec = datalink_receive(&src, &RecBuf[0], MAX_MPDU, _timeout);
161

162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
	if (pdu_lenRec) {
	    int apdu_offset = 0;
	    BACNET_ADDRESS destRec = { 0 };
	    BACNET_NPDU_DATA npdu_dataRec = { 0 };

		apdu_offset = npdu_decode(&RecBuf[0], &destRec, &src, &npdu_dataRec);
		if (npdu_data.network_layer_message) {
			/* network layer message received!  Handle it! */
			LOG(error) << "Network layer message received. Discarding";
		} else if ((apdu_offset > 0) && (apdu_offset <= pdu_lenRec)) {
			if ((destRec.net == 0) || (destRec.net == BACNET_BROADCAST_NETWORK)) {
				/* only handle the version that we know how to handle */
				/* and we are not a router, so ignore messages with
				   routing information cause they are not for us */
				if (!(destRec.net == BACNET_BROADCAST_NETWORK) && !((RecBuf[apdu_offset] & 0xF0) ==	PDU_TYPE_CONFIRMED_SERVICE_REQUEST)) {
					apdu_handler(&src, &RecBuf[apdu_offset], (uint16_t) (pdu_lenRec - apdu_offset));
				}
			}
		}
181
	} else {
182
		tsm_free_invoke_id(_invokeId);
183
184
185
		throw std::runtime_error("Timeout while waiting for response");
	}

186
187
	if (!tsm_invoke_id_free(_invokeId)) { //should be freed by apdu_handler on success
		tsm_free_invoke_id(_invokeId);
188
189
190
		throw std::runtime_error("Invoke ID was not freed");
	}

Micha Mueller's avatar
Micha Mueller committed
191
	return _presentValue;
192
193
}

194
void BACnetClient::unrecognizedServiceHandler(uint8_t * service_request, uint16_t service_len, BACNET_ADDRESS * src, BACNET_CONFIRMED_SERVICE_DATA * service_data) {
195
	boost::log::sources::severity_logger<boost::log::trivial::severity_level> lg;
196
197
198
    int pdu_len = 0;
    int bytes_sent = 0;
    BACNET_NPDU_DATA npdu_data;
199
    BACNET_ADDRESS myAddress;
200
201
202
203
204

    (void) service_request;
    (void) service_len;

    /* encode the NPDU portion of the packet */
205
    datalink_get_my_address(&myAddress);
206
    npdu_encode_npdu_data(&npdu_data, false, MESSAGE_PRIORITY_NORMAL);
207
    pdu_len = npdu_encode_pdu(&_handlerTransmitBuffer[0], src, &myAddress, &npdu_data);
208
    /* encode the APDU portion of the packet */
209
    pdu_len += reject_encode_apdu(&_handlerTransmitBuffer[pdu_len], service_data->invoke_id, REJECT_REASON_UNRECOGNIZED_SERVICE);
210
211
212
213
214
215
216
217
218
    /* send the data */
    bytes_sent = datalink_send_pdu(src, &npdu_data, &_handlerTransmitBuffer[0], pdu_len);
    if (bytes_sent > 0) {
    	LOG(info) << "BACnet: Sent Reject";
    } else {
    	LOG(info) << "BACnet: Could not send Reject: " << strerror(errno);
    }
}

219
220
void BACnetClient::readPropertyAckHandler(uint8_t * service_request, uint16_t service_len, BACNET_ADDRESS * src, BACNET_CONFIRMED_SERVICE_ACK_DATA * service_data)
{
221
	boost::log::sources::severity_logger<boost::log::trivial::severity_level> lg;
222
    int serviceLen = 0;
223
    //int appDataLen = 0;
224
225
226
227
228
	uint8_t *application_data;
	int application_data_len;
    BACNET_READ_PROPERTY_DATA data;
    BACNET_APPLICATION_DATA_VALUE value;        /* for decode value data */

229
    /*if (!(address_match(&_targetAddress, src) && (service_data->invoke_id == _invokeId))) {
230
    	throw std::runtime_error("Message not determined for us");
231
    }*/
232
233
234

	serviceLen = rp_ack_decode_service_request(service_request, service_len, &data);
	if (serviceLen <= 0) {
235
		tsm_free_invoke_id(service_data->invoke_id);
236
237
238
239
		throw std::runtime_error("Decode failed");
	}
   //     rp_ack_print_data(&data);

240
241
	application_data = data.application_data;
	application_data_len = data.application_data_len;
242
	/*appDataLen = */bacapp_decode_application_data(application_data, (uint8_t) application_data_len, &value);
243
244
245

	//bacapp_print_value(stdout, &object_value);

246
	//TODO what kind of data is returned? which cases do we need to handle? fit they into int64_t at all??
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
	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:
273
			tsm_free_invoke_id(service_data->invoke_id);
274
275
			throw std::runtime_error("Value tag not supported");
			break;
276
	}
277
278
}

279
void BACnetClient::errorHandler(BACNET_ADDRESS * src, uint8_t invokeId, BACNET_ERROR_CLASS error_class, BACNET_ERROR_CODE error_code) {
280
281
282
283
284
	tsm_free_invoke_id(invokeId);
	std::string str = "BACnet Error: ";
	std::string errorMsg1 = bactext_error_class_name((int) error_class);
	std::string errorMsg2 = bactext_error_code_name((int) error_code);
	throw std::runtime_error(str + errorMsg1 + errorMsg2);
285
286
}

287
void BACnetClient::abortHandler(BACNET_ADDRESS * src, uint8_t invokeId, uint8_t abort_reason, bool server) {
288
289
290
291
	tsm_free_invoke_id(invokeId);
	std::string str = "BACnet Abort: ";
	std::string errorMsg = bactext_abort_reason_name((int) abort_reason);
	throw std::runtime_error(str + errorMsg);
292
293
294
}

void BACnetClient::rejectHandler(BACNET_ADDRESS * src, uint8_t invokeId, uint8_t reject_reason) {
295
296
297
298
	tsm_free_invoke_id(invokeId);
	std::string str = "BACnet Reject: ";
	std::string errorMsg = bactext_reject_reason_name((int) reject_reason);
	throw std::runtime_error(str + errorMsg);
299
300
}

301
void BACnetClient::printEntityConfig(LOG_LEVEL ll) {
302
    LOG_VAR(ll) << eInd << "Timeout: " << _timeout;
303
304
}