diff --git a/src/bacnetmstp/CMakeLists.txt b/src/bacnetmstp/CMakeLists.txt new file mode 100644 index 00000000..9372b9a4 --- /dev/null +++ b/src/bacnetmstp/CMakeLists.txt @@ -0,0 +1,24 @@ +set (libname "bacnetmstp") +set (libdescription "upm driver module for BACnet MS/TP devices") +set (module_src ${libname}.cxx device-client.c) +set (module_h ${libname}.h) + +pkg_search_module(BACNET libbacnet) +if (BACNET_FOUND) + set (reqlibname "libbacnet") + include_directories(${BACNET_INCLUDE_DIRS}) + upm_module_init() + add_dependencies(${libname} ${BACNET_LIBRARIES}) + target_link_libraries(${libname} ${BACNET_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) + if (BUILDSWIG) + if (BUILDSWIGNODE) + swig_link_libraries (jsupm_${libname} ${BACNET_LIBRARIES} ${MRAA_LIBRARIES} ${NODE_LIBRARIES}) + endif() + if (BUILDSWIGPYTHON) + swig_link_libraries (pyupm_${libname} ${BACNET_LIBRARIES} ${PYTHON_LIBRARIES} ${MRAA_LIBRARIES}) + endif() + if (BUILDSWIGJAVA) + swig_link_libraries (javaupm_${libname} ${BACNET_LIBRARIES} ${MRAAJAVA_LDFLAGS} ${JAVA_LDFLAGS}) + endif() + endif() +endif () diff --git a/src/bacnetmstp/bacnetmstp.cxx b/src/bacnetmstp/bacnetmstp.cxx new file mode 100644 index 00000000..068bcd25 --- /dev/null +++ b/src/bacnetmstp/bacnetmstp.cxx @@ -0,0 +1,880 @@ +/* + * Author: Jon Trulson + * Copyright (c) 2016 Intel Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include "bacnetmstp.h" +#include "handlers.h" +#include "client.h" +#include "txbuf.h" +#include "mstpdef.h" + +using namespace upm; +using namespace std; + +// our singleton instance +BACNETMSTP* BACNETMSTP::m_instance = 0; + +BACNETMSTP::BACNETMSTP() +{ + // set defaults here + m_maxInfoFrames = DEFAULT_MAX_INFO_FRAMES; + m_maxMaster = DEFAULT_MAX_MASTER; + m_baudRate = 38400; + m_macAddr = DEFAULT_MAX_MASTER; + + m_initialized = false; + + // 60 sec, for MS/TP + m_adpuTimeoutMS = 60000; + + m_deviceInstanceID = BACNET_MAX_INSTANCE; + + memset(m_rxBuffer, 0, MAX_MPDU); + + m_returnedValue = {0}; + m_targetAddress = {0}; + m_invokeID = 0; + m_errorDetected = false; + + setDebug(false); +} + +BACNETMSTP::~BACNETMSTP() +{ + if (m_initialized) + datalink_cleanup(); +} + +void BACNETMSTP::setDebug(bool enable) +{ + m_debugging = enable; +} + +void BACNETMSTP::clearErrors() +{ + m_errorType = BACERR_TYPE_NONE; + + // empty out all of our error/reject/abort info + m_rejectReason = REJECT_REASON_OTHER; + m_rejectString.clear(); + + m_abortReason = ABORT_REASON_OTHER; + m_abortString.clear(); + + m_errorClass = ERROR_CLASS_DEVICE; + m_errorCode = ERROR_CODE_OTHER; + m_errorString.clear(); + + m_upmErrorString.clear(); +} + +void BACNETMSTP::handlerError(BACNET_ADDRESS* src, + uint8_t invoke_id, + BACNET_ERROR_CLASS error_class, + BACNET_ERROR_CODE error_code) +{ + if (instance()->m_debugging) + cerr << __FUNCTION__ << ": entered" << endl; + + if (address_match(&(instance()->m_targetAddress), src) && + (invoke_id == instance()->m_invokeID)) + { + instance()->m_errorType = BACERR_TYPE_ERROR; + instance()->m_errorClass = error_class; + instance()->m_errorCode = error_code; + instance()->m_errorString = + bactext_error_class_name((int)error_class) + + string(": ") + bactext_error_code_name((int)error_code); + + instance()->m_errorDetected = true; + } +} + +void BACNETMSTP::handlerAbort(BACNET_ADDRESS* src, + uint8_t invoke_id, + uint8_t abort_reason, + bool server) +{ + (void)server; // not used + + if (instance()->m_debugging) + cerr << __FUNCTION__ << ": entered" << endl; + + if (address_match(&(instance()->m_targetAddress), src) && + (invoke_id == instance()->m_invokeID)) + { + instance()->m_errorType = BACERR_TYPE_ABORT; + instance()->m_abortReason = abort_reason; + instance()->m_abortString = + bactext_abort_reason_name((int)abort_reason); + + instance()->m_errorDetected = true; + } +} + +void BACNETMSTP::handlerReject(BACNET_ADDRESS* src, + uint8_t invoke_id, + uint8_t reject_reason) +{ + if (instance()->m_debugging) + cerr << __FUNCTION__ << ": entered" << endl; + + if (address_match(&(instance()->m_targetAddress), src) && + (invoke_id == instance()->m_invokeID)) + { + instance()->m_errorType = BACERR_TYPE_REJECT; + instance()->m_rejectReason = reject_reason; + instance()->m_rejectString = + bactext_reject_reason_name((int)reject_reason); + + instance()->m_errorDetected = true; + } +} + +void BACNETMSTP::handlerReadPropertyAck(uint8_t* service_request, + uint16_t service_len, + BACNET_ADDRESS* src, + BACNET_CONFIRMED_SERVICE_ACK_DATA* service_data) +{ + int len = 0; + BACNET_READ_PROPERTY_DATA data; + + if (address_match(&(instance()->m_targetAddress), src) && + (service_data->invoke_id == instance()->m_invokeID)) + { + if (instance()->m_debugging) + cerr << __FUNCTION__ << ": got readProp ack" << endl; + + len = rp_ack_decode_service_request(service_request, service_len, + &data); + // FIXME: we don't currently handle arrays (len < service_len) + if (len > 0) + { + bacapp_decode_application_data(data.application_data, + data.application_data_len, + &(instance()->m_returnedValue)); + } + else + { + // shouldn't happen? + cerr << __FUNCTION__ << ": decode app data failed" << endl; + } + } +} + +void BACNETMSTP::handlerWritePropertyAck(BACNET_ADDRESS* src, + uint8_t invoke_id) +{ + if (address_match(&(instance()->m_targetAddress), src) && + (invoke_id == instance()->m_invokeID)) + { + if (instance()->m_debugging) + cerr << __FUNCTION__ << ": got writeProp ack" << endl; + } +} + +void BACNETMSTP::initServiceHandlers() +{ + // this is in device-client.c + Device_Init(NULL); + + // These are service requests we must handle from other masters + + // we need to handle who-is to support dynamic device binding to us + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, handler_who_is); + + // handle i-am to support binding to other devices + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_I_AM, handler_i_am_bind); + + // 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); + + // we must implement read property (it's required) + apdu_set_confirmed_handler(SERVICE_CONFIRMED_READ_PROPERTY, + handler_read_property); + + // These are related to requests we make + + // handle the data coming back from confirmed readProp requests + apdu_set_confirmed_ack_handler(SERVICE_CONFIRMED_READ_PROPERTY, + handlerReadPropertyAck); + + // handle the simple ack for confirmed writeProp requests + apdu_set_confirmed_simple_ack_handler(SERVICE_CONFIRMED_WRITE_PROPERTY, + handlerWritePropertyAck); + + // handle any errors coming back + apdu_set_error_handler(SERVICE_CONFIRMED_READ_PROPERTY, handlerError); + apdu_set_abort_handler(handlerAbort); + apdu_set_reject_handler(handlerReject); +} + +BACNETMSTP* BACNETMSTP::instance() +{ + if (!m_instance) + m_instance = new BACNETMSTP; + + return m_instance; +} + +void BACNETMSTP::initMaster(std::string port, int baudRate, + int deviceInstanceID, int macAddr, int maxMaster, + int maxInfoFrames) +{ + // first some checking + + // if we are already initialized, then it's too late to change things now + if (m_initialized) + { + if (m_debugging) + cerr << __FUNCTION__ << ": Instance is already initialized, ignored." + << endl; + return; + } + + // baudrate + // The standard allows (as of at least 2010) the following baud rates + if ( !(baudRate == 9600 || baudRate == 19200 || baudRate == 38400 + || baudRate == 57600 || baudRate == 76800 || baudRate == 115200) ) + { + throw std::invalid_argument(std::string(__FUNCTION__) + + ": baudRate must be 9600, 19200, 38400, " + + " 57600, 76800, or 115200"); + } + + // maxMaster + // maxMaster must be less than or equal to 127 + if (maxMaster < 0 || maxMaster > DEFAULT_MAX_MASTER) + { + throw std::out_of_range(std::string(__FUNCTION__) + + ": maxMaster must be between 0 and " + + std::to_string(DEFAULT_MAX_MASTER)); + } + + // As a master ourselves, we must have a MAC address also within the + // constraints of maxMaster + if (macAddr < 0 || macAddr > DEFAULT_MAX_MASTER) + { + throw std::out_of_range(std::string(__FUNCTION__) + + ": macAddr must be between 0 and " + + std::to_string(DEFAULT_MAX_MASTER)); + } + + // this should be unique on the network + if (deviceInstanceID >= BACNET_MAX_INSTANCE) + { + throw std::out_of_range(std::string(__FUNCTION__) + + ": deviceInstanceID must be less than " + + std::to_string(BACNET_MAX_INSTANCE) + + ", and must be unique on the network"); + } + + m_port = port; + m_baudRate = baudRate; + m_maxInfoFrames = maxInfoFrames; + m_macAddr = macAddr; + m_maxMaster = maxMaster; + m_deviceInstanceID = deviceInstanceID; + + // Let the fun begin... + + // setup our info + Device_Set_Object_Instance_Number(m_deviceInstanceID); + address_init(); + + initServiceHandlers(); + + dlmstp_set_max_info_frames(m_maxInfoFrames); + dlmstp_set_max_master(m_maxMaster); + dlmstp_set_baud_rate(m_baudRate); + dlmstp_set_mac_address(m_macAddr); + + // FIXME - allow to change? + apdu_timeout_set(m_adpuTimeoutMS); + + // Ordinarily, I'd like to check the return value of this function, + // but even in the face of errors, it always returns true :( This + // function starts the ball rolling, and initializes the Master FSM + // thread. Unfortunately, it doesn't appear this can be turned back + // off without exiting the application. + datalink_init((char *)port.c_str()); + + m_initialized = true; +} + +bool BACNETMSTP::dispatchRequest() +{ + uint16_t pdu_len = 0; + unsigned timeout = 100; // milliseconds + unsigned max_apdu = 0; + time_t elapsed_seconds = 0; + time_t last_seconds = 0; + time_t current_seconds = 0; + time_t timeout_seconds = 0; + bool found = false; + + // address where message came from + BACNET_ADDRESS src = {0}; + + clearErrors(); + m_errorDetected = false; + + uint32_t targetDeviceInstanceID = BACNET_MAX_INSTANCE; + + switch (m_command.cmd) + { + case BACCMD_READ_PROPERTY: + targetDeviceInstanceID = m_command.readPropArgs.targetDeviceInstanceID; + break; + + case BACCMD_WRITE_PROPERTY: + targetDeviceInstanceID = m_command.writePropArgs.targetDeviceInstanceID; + break; + + case BACCMD_NONE: + { + m_errorType = BACERR_TYPE_UPM; + m_upmErrorString = string(__FUNCTION__) + + ": called with BACCMD_NONE, ignoring"; + + return true; // error + } + + break; + + default: + { + // should this throw? + m_errorType = BACERR_TYPE_UPM; + m_upmErrorString = string(__FUNCTION__) + + ": internal error, called with unknown command, ignoring"; + + return true; // error + } + + break; + } + + // timeouts + last_seconds = time(NULL); + timeout_seconds = (apdu_timeout() / 1000) * apdu_retries(); + + // we use 0 to indicate that request hasn't been made yet, so that + // it will be made once the address is bound. + m_invokeID = 0; + + // bind to the device first. + found = address_bind_request(targetDeviceInstanceID, &max_apdu, + &(instance()->m_targetAddress)); + + if (!found) + { + if (m_debugging) + cerr << __FUNCTION__ << ": Address not found, Sending WhoIs..." << endl; + + Send_WhoIs(targetDeviceInstanceID, targetDeviceInstanceID); + } + else + { + if (m_debugging) + cerr << __FUNCTION__ << ": Address was found" << endl; + } + + // loop until either we get our data, an error occurs, or we timeout + while (true) + { + current_seconds = time(NULL); + + // at least one second has passed + if (current_seconds != last_seconds) + tsm_timer_milliseconds((uint16_t) ((current_seconds - + last_seconds) * 1000)); + if (m_errorDetected) + break; + + // we have to wait until the address is bound before proceeding + if (!found) + { + found = + address_bind_request(targetDeviceInstanceID, &max_apdu, + &(instance()->m_targetAddress)); + } + + if (found) + { + // address is bound, and we have not sent our request yet. Make it so. + if (m_invokeID == 0) + { + switch (m_command.cmd) + { + case BACCMD_READ_PROPERTY: + m_invokeID = + Send_Read_Property_Request(targetDeviceInstanceID, + m_command.readPropArgs.objType, + m_command.readPropArgs.objInstance, + m_command.readPropArgs.objProperty, + m_command.readPropArgs.arrayIndex); + if (m_debugging) + cerr << __FUNCTION__ + << ": Called Send_Read_Property_Request(), m_invokeID = " + << (int)m_invokeID << endl; + + break; + + case BACCMD_WRITE_PROPERTY: + m_invokeID = + Send_Write_Property_Request(targetDeviceInstanceID, + m_command.writePropArgs.objType, + m_command.writePropArgs.objInstance, + m_command.writePropArgs.objProperty, + m_command.writePropArgs.propValue, + m_command.writePropArgs.propPriority, + m_command.writePropArgs.arrayIndex); + if (m_debugging) + cerr << __FUNCTION__ + << ": Called Send_Write_Property_Request(), m_invokeID = " + << (int)m_invokeID << endl; + + break; + } + + } + else if (tsm_invoke_id_free(m_invokeID)) + { + // transaction completed successfully + + if (m_debugging) + cerr << __FUNCTION__ << ": Success, m_invokeID = " + << (int)m_invokeID << endl; + + break; + } + else if (tsm_invoke_id_failed(m_invokeID)) + { + // transaction state machine failed, most likely timeout + tsm_free_invoke_id(m_invokeID); + + m_errorType = BACERR_TYPE_UPM; + m_upmErrorString = string(__FUNCTION__) + + ": TSM Timed Out."; + + if (m_debugging) + cerr << m_upmErrorString << endl; + + m_errorDetected = true; + + break; + } + } + else + { + // still waiting to bind. timeout if we've waited too long. + elapsed_seconds += (current_seconds - last_seconds); + if (elapsed_seconds > timeout_seconds) + { + m_errorType = BACERR_TYPE_UPM; + m_upmErrorString = string(__FUNCTION__) + + ": Timed out waiting to bind address."; + + // We output this error unconditionally as this is an + // error you will get if you supply a non-existant + // Device Obeject Instance ID. + + cerr << m_upmErrorString << endl; + cerr << __FUNCTION__ + << ": Did you supply the correct Device Object Instance ID " + << "for your device?" + << endl; + + m_errorDetected = true; + break; + } + } + + // returns 0 bytes on timeout + pdu_len = datalink_receive(&src, m_rxBuffer, MAX_MPDU, timeout); + + // process the packet if valid. This will call our handlers as needed. + if (pdu_len) + npdu_handler(&src, m_rxBuffer, pdu_len); + + // keep track of time for next check + last_seconds = current_seconds; + } + + return m_errorDetected; +} + +bool BACNETMSTP::readProperty(uint32_t targetDeviceInstanceID, + BACNET_OBJECT_TYPE objType, + uint32_t objInstance, + BACNET_PROPERTY_ID objProperty, + uint32_t arrayIndex) +{ + // some sanity checking... + if (objInstance >= BACNET_MAX_INSTANCE) + { + throw std::out_of_range(std::string(__FUNCTION__) + + ": objInstance must be less than " + + std::to_string(BACNET_MAX_INSTANCE)); + } + + // fill in the command structure and dispatch + m_command.cmd = BACCMD_READ_PROPERTY; + m_command.readPropArgs.targetDeviceInstanceID = targetDeviceInstanceID; + m_command.readPropArgs.objType = objType; + m_command.readPropArgs.objInstance = objInstance; + m_command.readPropArgs.objProperty = objProperty; + m_command.readPropArgs.arrayIndex = arrayIndex; // not implemented in the ack handler! + + if (m_debugging) + cerr << __FUNCTION__ << ": calling dispatchRequest()..." << endl; + + // send it off + bool error = dispatchRequest(); + + // clear the command to avoid accidental re-calls + m_command.cmd = BACCMD_NONE; + + return error; +} + +bool BACNETMSTP::writeProperty(uint32_t targetDeviceInstanceID, + BACNET_OBJECT_TYPE objType, + uint32_t objInstance, + BACNET_PROPERTY_ID objProperty, + BACNET_APPLICATION_DATA_VALUE* propValue, + uint8_t propPriority, + int32_t arrayIndex) +{ + // some sanity checking... + if (objInstance >= BACNET_MAX_INSTANCE) + { + throw std::out_of_range(std::string(__FUNCTION__) + + ": objInstance must be less than " + + std::to_string(BACNET_MAX_INSTANCE)); + } + + // fill in the command structure and dispatch + m_command.cmd = BACCMD_WRITE_PROPERTY; + m_command.writePropArgs.targetDeviceInstanceID = targetDeviceInstanceID; + m_command.writePropArgs.objType = objType; + m_command.writePropArgs.objInstance = objInstance; + m_command.writePropArgs.objProperty = objProperty; + m_command.writePropArgs.propValue = propValue; + m_command.writePropArgs.propPriority = propPriority; + m_command.writePropArgs.arrayIndex = arrayIndex; // not implemented! + + if (m_debugging) + cerr << __FUNCTION__ << ": calling dispatchRequest()..." << endl; + + // send it off + bool error = dispatchRequest(); + + // clear the command to avoid accidental re-calls + m_command.cmd = BACCMD_NONE; + + return error; +} + +BACNET_APPLICATION_DATA_VALUE BACNETMSTP::getData() +{ + return m_returnedValue; +} + +uint8_t BACNETMSTP::getDataType() +{ + return m_returnedValue.tag; +} + +float BACNETMSTP::getDataTypeReal() +{ + if (getDataType() == BACNET_APPLICATION_TAG_REAL) + return m_returnedValue.type.Real; + else + { + if (m_debugging) + cerr << __FUNCTION__ << ": Not of Real type, trying to convert..." << endl; + + // try to convert or throw + switch (getDataType()) + { + case BACNET_APPLICATION_TAG_BOOLEAN: + return (getDataTypeBoolean() ? 1.0 : 0.0); + case BACNET_APPLICATION_TAG_UNSIGNED_INT: + return float(getDataTypeUnsignedInt()); + case BACNET_APPLICATION_TAG_SIGNED_INT: + return float(getDataTypeSignedInt()); + default: + throw std::invalid_argument(std::string(__FUNCTION__) + + ": data type (" + + std::to_string(int(getDataType())) + + ") is not convertible to Real"); + } + } +} + +bool BACNETMSTP::getDataTypeBoolean() +{ + if (getDataType() == BACNET_APPLICATION_TAG_BOOLEAN) + return ((m_returnedValue.type.Boolean) ? true : false); + else + throw std::invalid_argument(std::string(__FUNCTION__) + + ": data type (" + + std::to_string(int(getDataType())) + + ") is not convertible to Bool"); +} + +unsigned int BACNETMSTP::getDataTypeUnsignedInt() +{ + if (getDataType() == BACNET_APPLICATION_TAG_UNSIGNED_INT) + return m_returnedValue.type.Unsigned_Int; + else + throw std::invalid_argument(std::string(__FUNCTION__) + + ": data type (" + + std::to_string(int(getDataType())) + + ") is not convertible to UnsignedInt"); +} + +int BACNETMSTP::getDataTypeSignedInt() +{ + if (getDataType() == BACNET_APPLICATION_TAG_SIGNED_INT) + return m_returnedValue.type.Signed_Int; + else + throw std::invalid_argument(std::string(__FUNCTION__) + + ": data type (" + + std::to_string(int(getDataType())) + + ") is not convertible to SignedInt"); +} + +#if defined(BACAPP_DOUBLE) +double BACNETMSTP::getDataTypeDouble() +{ + if (getDataType() == BACNET_APPLICATION_TAG_DOUBLE) + return m_returnedValue.type.Double; + else + { + if (m_debugging) + cerr << __FUNCTION__ << ": Not of Double type, trying to convert..." << endl; + + // try to convert or throw + switch (getDataType()) + { + case BACNET_APPLICATION_TAG_REAL: + return double(getDataTypeReal()); + case BACNET_APPLICATION_TAG_BOOLEAN: + return (getDataTypeBoolean() ? 1.0 : 0.0); + case BACNET_APPLICATION_TAG_UNSIGNED_INT: + return double(getDataTypeUnsignedInt()); + case BACNET_APPLICATION_TAG_SIGNED_INT: + return double(getDataTypeSignedInt()); + default: + throw std::invalid_argument(std::string(__FUNCTION__) + + ": data type (" + + std::to_string(int(getDataType())) + + ") is not convertible to Double"); + } + } +} +#endif // BACAPP_DOUBLE + +unsigned int BACNETMSTP::getDataTypeEnum() +{ + if (getDataType() == BACNET_APPLICATION_TAG_ENUMERATED) + return m_returnedValue.type.Enumerated; + else + throw std::invalid_argument(std::string(__FUNCTION__) + + ": data type (" + + std::to_string(int(getDataType())) + + ") is not convertible to Enum"); +} + +std::string BACNETMSTP::getDataTypeString() +{ + string retval; + + // Here, we can try to accomodate all the types + switch(getDataType()) + { + case BACNET_APPLICATION_TAG_REAL: + retval = std::to_string(getDataTypeReal()); + break; + +#if defined(BACAPP_DOUBLE) + case BACNET_APPLICATION_TAG_DOUBLE: + retval = std::to_string(getDataTypeDouble()); + break; +#endif // BACAPP_DOUBLE + + case BACNET_APPLICATION_TAG_UNSIGNED_INT: + retval = std::to_string(getDataTypeUnsignedInt()); + break; + + case BACNET_APPLICATION_TAG_SIGNED_INT: + retval = std::to_string(getDataTypeSignedInt()); + break; + + case BACNET_APPLICATION_TAG_BOOLEAN: + retval = (getDataTypeBoolean() ? string("true") : string("false")); + break; + + case BACNET_APPLICATION_TAG_CHARACTER_STRING: + retval = string(characterstring_value(&m_returnedValue.type.Character_String), + + characterstring_length(&m_returnedValue.type.Character_String)); + break; + + case BACNET_APPLICATION_TAG_OCTET_STRING: + { + string tmpstr((char *)octetstring_value(&m_returnedValue.type.Octet_String), + + octetstring_length(&m_returnedValue.type.Octet_String)); + retval = string2HexString(tmpstr); + } + + break; + + case BACNET_APPLICATION_TAG_BIT_STRING: + { + int len = bitstring_bits_used(&m_returnedValue.type.Bit_String); + + for (int i=0; i (MAX_CHARACTER_STRING_BYTES - 1)) + { + throw std::invalid_argument(std::string(__FUNCTION__) + + ": value must be less than or equal to " + + std::to_string(MAX_CHARACTER_STRING_BYTES - 1) + + " characters long"); + } + + BACNET_APPLICATION_DATA_VALUE data; + + memset(&data, 0, sizeof(BACNET_APPLICATION_DATA_VALUE)); + + data.tag = BACNET_APPLICATION_TAG_CHARACTER_STRING; + + characterstring_init_ansi(&data.type.Character_String, value.c_str()); + + return data; +} + + +string BACNETMSTP::string2HexString(string input) +{ + static const char* const lut = "0123456789abcdef"; + size_t len = input.size(); + + string output; + output.reserve(3 * len); + for (size_t i = 0; i < len; ++i) + { + const unsigned char c = input[i]; + output.push_back(lut[c >> 4]); + output.push_back(lut[c & 15]); + output.push_back(' '); + } + + return output; +} diff --git a/src/bacnetmstp/bacnetmstp.h b/src/bacnetmstp/bacnetmstp.h new file mode 100644 index 00000000..8abe0dad --- /dev/null +++ b/src/bacnetmstp/bacnetmstp.h @@ -0,0 +1,716 @@ +/* + * Author: Jon Trulson + * Copyright (c) 2016 Intel Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#pragma once + +#include + +// we only support a BACnet RS-485 MS/TP datalink +#define BACDL_MSTP 1 +#undef BACDL_ALL + +// get a variety of bacnet-stack includes... +#include "bacdef.h" +#include "config.h" +#include "bactext.h" +#include "bacerror.h" +#include "iam.h" +#include "arf.h" +#include "tsm.h" +#include "address.h" +#include "npdu.h" +#include "apdu.h" +#include "device.h" +#include "datalink.h" +#include "whois.h" +#include "mstpdef.h" +#include "dlmstp.h" + + +namespace upm { + + /** + * @brief BACNETMSTP base class + * @defgroup bacnetmstp libupm-bacnetmstp + * @ingroup uart + */ + + /** + * @library bacnetmstp + * @sensor bacnetmstp + * @comname UPM API for BACNET MS/TP communications + * @con uart + * @web http://bacnet.sourceforge.net/ + * @brief UPM API for BACNETMSTP + * + * This is a singleton class that provides services to UPM BACnet + * drivers (like E50HX) based on the bacnet-stack at + * http://bacnet.sourceforge.net . This class is implemented as a + * singleton due to the fact that the bacnet-stack implementation + * does not currently allow multiple simultaneous datalinks. We are + * using 0.8.3 of bacnet-stack. In the future this restriction may + * be lifted depending on bacnet-stack, but for now, you are + * stuck with only a single BACnet MS/TP datalink. + * + * This driver is not intended to be used by end users. It is + * intended for use with other UPM drivers that require access to a + * BACnet MS/TP (Master Slave/Token Passing) network over RS-485. + * + * For this reason, no examples are provided. If you wish to + * implement your own BACnet MS/TP driver, please look at the E50HX + * driver to see how this class can be used. + * + * Currently, only readProperty and writeProperty BACnet requests + * are supported. In the future, any other BACnet requests could be + * supported as well. readProperty and writeProperty should provide + * most of what you will need when communicating with BACnet + * devices. Since the source code is open, feel free to add other + * services as you see fit :) + * + * In order to make requests over an MS/TP network, you must be a + * BACnet master. initMaster() is responsible for configuring your + * underlying RS-485 network and starting a Master FSM (finite state + * machine) thread that will be responsible for identifying other + * Masters on the network and negotiating token passing. Your + * master can only transmit when it has the token. + * + * Fortunately, all of these messy details are handled for you by + * this class, or the underlying bacnet-stack library this class + * relies on. + + */ + + class BACNETMSTP { + // Constructor and destructor are protected + + public: + + // error types + typedef enum { + BACERR_TYPE_NONE = 0, + BACERR_TYPE_REJECT, + BACERR_TYPE_ABORT, + BACERR_TYPE_ERROR, + BACERR_TYPE_UPM + } BACERR_TYPE_T; + + // command types we currently support + typedef enum { + BACCMD_NONE = 0, + BACCMD_READ_PROPERTY, + BACCMD_WRITE_PROPERTY + } BACCMD_TYPE_T; + + /** + * Get our singleton instance, initializing it if neccessary. All + * requests to this class should be done through this instance + * accessor. + * + * @return static pointer to our class instance + */ + static BACNETMSTP* instance(); + + /** + * This function initializes the underlying BACNETMSTP Master + * singleton in the event it has not already been initialized. If + * the BACNETMSTP Master singleton has already been initialized, + * then this call will be ignored. There can be only one. + * + * @param port The serial port that the RS-485 interface is + * connected to. + * @param baudRate The baudrate of the RS-485 interface. All + * devices on a BACnet RS-485 bus must run at the same baudrate. + * Valid values are 9600, 19200, 38400, 57600, 76800, and 115200. + * @param deviceInstanceNumber This is the unique Device Object + * Instance number that will be used for our BACnet Master in + * order to communicate over the BACnet interface. This number + * must be between 1-4194302 and must be unique on the BACnet + * network. + * @param macAddr This is the MAC address of our BACnet Master. + * It must be unique on the BACnet segment, and must be between + * 1-127. + * @param maxMaster This specifies to our Master the maximum MAC + * address used by any other Masters on the BACnet network. This + * must be between 1-127, the default is 127. Do not change this + * unless you know what you are doing or you could introduce + * token passing errors on the BACnet network. + * @param maxInfoFrames This number specifies the maximum number + * of transmissions (like requests for data) our Master is allowed + * to make before passing the token to the next Master. The + * default is 1. + */ + void initMaster(std::string port, int baudRate, int deviceInstanceNumber, + int macAddr, int maxMaster=DEFAULT_MAX_MASTER, + int maxInfoFrames=1); + + + /** + * Perform a BACnet readProperty transaction. This function will + * return when either the transaction has completed, or an error + * has occurred. It requests the value of a property, belonging + * to a specific object instance on a specific device. + * + * @param targetDeviceInstanceID This is the Device Object + * Instance ID of the device to send the request to. This number + * will be unique for every device on the network. An address + * lookup will be performed the first time a request is made to a + * device using the WhoHas BACnet service. The result will be + * cached for further use. + * @param objType This is the BACnet object type of the object you + * wish to query. It should be one of the BACNET_OBJECT_TYPE + * values. + * @param objInstance This is the instance number of the Object + * you wish to access. It is an integer starting from 1. + * @param objProperty This is the property of the Object and + * instance you wish to access. It should be one of the + * BACNET_PROPERTY_ID values. + * @param arrayIndex This specifies the index number of an array + * property. This is not currently supported. Until it is, leave + * the default at BACNET_ARRAY_ALL. + * @return true if an error occurred, false otherwise. + */ + bool readProperty(uint32_t targetDeviceInstanceID, + BACNET_OBJECT_TYPE objType, + uint32_t objInstance, + BACNET_PROPERTY_ID objProperty, + uint32_t arrayIndex=BACNET_ARRAY_ALL); + + /** + * Perform a BACnet writeProperty transaction. This function will + * return when either the transaction has completed, or an error + * has occurred. It writes the supplied value to a property, + * belonging to a specific object instance on a specific device. + * + * @param targetDeviceInstanceID This is the Device Object + * Instance ID of the device to send the request to. This number + * will be unique for every device on the network. An address + * lookup will be performed the first time a request is made to a + * device using the WhoHas BACnet service. The result will be + * cached for further use. + * @param objType This is the BACnet object type of the object you + * wish to query. It should be one of the BACNET_OBJECT_TYPE + * values. + * @param objInstance This is the instance number of the Object + * you wish to access. It is an integer starting from 1. + * @param objProperty This is the property of the Object and + * instance you wish to access. It should be one of the + * BACNET_PROPERTY_ID values. + * @param propValue This is a pointer to a + * BACNET_APPLICATION_DATA_VALUE structure containg the data value + * to write to the property. Use the createData*() methods to + * properly create these structures. + * @param propPriority This specifies the priority of a + * commandable property. Leave it at the default unless you know + * what you are doing. In addition, there is conflicting + * information in the bacnet-stack documentation as to whether + * this is even supported. + * @param arrayIndex This specifies the index number of an array + * property. This is not currently supported. Until it is, leave + * the default at BACNET_ARRAY_ALL. + * @return true if an error occurred, false otherwise. + */ + bool writeProperty(uint32_t targetDeviceInstanceID, + BACNET_OBJECT_TYPE objType, + uint32_t objInstance, + BACNET_PROPERTY_ID objProperty, + BACNET_APPLICATION_DATA_VALUE* propValue, + uint8_t propPriority=BACNET_NO_PRIORITY, + int32_t arrayIndex=BACNET_ARRAY_ALL); + + /** + * After a successful readProperty request, this method can be used + * to return a BACNET_APPLICATION_DATA_VALUE structure containing + * the returned data. + * + * @return a BACNET_APPLICATION_DATA_VALUE structure containing + * the returned data. + */ + BACNET_APPLICATION_DATA_VALUE getData(); + + /** + * After a successful readProperty request, this method can be + * used to return the BACnet data type of the returned data. It + * will be one of the BACNET_APPLICATION_TAG_* values. + * + * @return A BACNET_APPLICATION_TAG_* value + */ + uint8_t getDataType(); + + /** + * After a successful readProperty request, this method can be + * used to return the BACnet dataype of the returned data as a + * Real. If the data type (getDataType()) is not a + * BACNET_APPLICATION_TAG_REAL, and the value returned cannot be + * safely converted, an exception is thrown. + * + * @return A floating point value representing the returned data + */ + float getDataTypeReal(); + + /** + * After a successful readProperty request, this method can be + * used to return the BACnet dataype of the returned data as a + * Boolean. If the data type (getDataType()) is not a + * BACNET_APPLICATION_TAG_BOOLEAN, and the value returned cannot + * be safely converted, an exception is thrown. + * + * @return A boolean value representing the returned data + */ + bool getDataTypeBoolean(); + + /** + * After a successful readProperty request, this method can be + * used to return the BACnet dataype of the returned data as a + * unsigned int. If the data type (getDataType()) is not a + * BACNET_APPLICATION_TAG_UNSIGNED_INT, and the value returned + * cannot be safely converted, an exception is thrown. + * + * @return An unsigned int value representing the returned data + */ + unsigned int getDataTypeUnsignedInt(); + + /** + * After a successful readProperty request, this method can be + * used to return the BACnet dataype of the returned data as a + * signed int. If the data type (getDataType()) is not a + * BACNET_APPLICATION_TAG_SIGNED_INT, and the value returned + * cannot be safely converted, an exception is thrown. + * + * @return A signed int value representing the returned data + */ + int getDataTypeSignedInt(); + + /** + * After a successful readProperty request, this method can be + * used to return the BACnet dataype of the returned data as a + * string. Most of the data types except Enum can be converted to + * a string. If the data type (getDataType()) is not a + * BACNET_APPLICATION_TAG_CHARACTER_STRING, and the value returned + * cannot be safely converted, an exception is thrown. + * + * @return A string value representing the returned data + */ + std::string getDataTypeString(); + + /** + * After a successful readProperty request, this method can be + * used to return the BACnet dataype of the returned data as an + * enumeration. If the data type (getDataType()) is not a + * BACNET_APPLICATION_TAG_ENUMERATED an exception is thrown. + * + * @return An unsigned int representing a BACnet enumerant + */ + unsigned int getDataTypeEnum(); + +#if defined(BACAPP_DOUBLE) + /** + * After a successful readProperty request, this method can be + * used to return the BACnet dataype of the returned data as a + * double. If the data type (getDataType()) is not a + * BACNET_APPLICATION_TAG_DOUBLE, and the value returned cannot be + * safely converted, an exception is thrown. + * + * @return A double floating point value representing the returned data + */ + double getDataTypeDouble(); +#endif // BACAPP_DOUBLE + + /** + * This method is used to create and return an initialized + * BACNET_APPLICATION_DATA_VALUE containing a real (floating point + * value). A pointer to this returned structure can then be used + * with writeProperty(). + * + * @param value The floating point value to initialize the structure to. + * @return An initialized structure containing the value + */ + BACNET_APPLICATION_DATA_VALUE createDataReal(float Real); + + /** + * This method is used to create and return an initialized + * BACNET_APPLICATION_DATA_VALUE containing a boolean. A pointer + * to this returned structure can then be used with + * writeProperty(). + * + * @param value The boolean value to initialize the structure to. + * @return An initialized structure containing the value + */ + BACNET_APPLICATION_DATA_VALUE createDataBool(bool value); + + /** + * This method is used to create and return an initialized + * BACNET_APPLICATION_DATA_VALUE containing a signed integer. A + * pointer to this returned structure can then be used with + * writeProperty(). + * + * @param value The signed integer value to initialize the structure to. + * @return An initialized structure containing the value + */ + BACNET_APPLICATION_DATA_VALUE createDataSignedInt(int value); + + /** + * This method is used to create and return an initialized + * BACNET_APPLICATION_DATA_VALUE containing a unsigned integer. A + * pointer to this returned structure can then be used with + * writeProperty(). + * + * @param value The unsigned integer value to initialize the + * structure to. + * @return An initialized structure containing the value + */ + BACNET_APPLICATION_DATA_VALUE createDataUnsignedInt(unsigned int value); + + /** + * This method is used to create and return an initialized + * BACNET_APPLICATION_DATA_VALUE containing a character string. A + * pointer to this returned structure can then be used with + * writeProperty(). Strings are typically limited to 63 characters. + * + * @param value The character string value to initialize the + * structure to. + * @return An initialized structure containing the value + */ + BACNET_APPLICATION_DATA_VALUE createDataString(std::string value); + + /** + * Return an enumration of the last error type to occur. The + * value returned will be one of the BACERR_TYPE_T values. + * + * @return The last error type to occur, one of the BACERR_TYPE_T + * values. + */ + BACERR_TYPE_T getErrorType() + { + return m_errorType; + }; + + /** + * In the event of a BACnet Reject error, return the error code. + * + * @return The Reject error code. + */ + uint8_t getRejectReason() + { + return m_rejectReason; + }; + + /** + * In the event of a BACnet Reject error, return the error string. + * + * @return The Reject error string. + */ + std::string getRejectString() + { + return m_rejectString; + }; + + /** + * In the event of a BACnet Abort error, return the Abort reason code. + * + * @return The Abort reason code. + */ + uint8_t getAbortReason() + { + return m_abortReason; + }; + + /** + * In the event of a BACnet Abort error, return the Abort string. + * + * @return The Abort error string. + */ + std::string getAbortString() + { + return m_abortString; + }; + + /** + * In the event of a general BACnet error, return the BACnet error class. + * + * @return One of the BACNET_ERROR_CLASS error class codes + */ + BACNET_ERROR_CLASS getErrorClass() + { + return m_errorClass; + }; + + /** + * In the event of a general BACnet error, return the BACnet error code. + * + * @return One of the BACNET_ERROR_CODE error codes + */ + BACNET_ERROR_CODE getErrorCode() + { + return m_errorCode; + }; + + /** + * In the event of a general BACnet error, return the BACnet error + * string. + * + * @return A string representing the BACnet error class and code. + */ + std::string getErrorString() + { + return m_errorString; + }; + + /** + * In the event of a non-BACnet UPM error, return a string + * describing the error. + * + * @return A string representing the UPM error. + */ + std::string getUPMErrorString() + { + return m_upmErrorString; + }; + + /** + * Check to see if initMaster) has already been called, and out + * master is initialized. + * + * @return true if the master is initialized, false otherwise + */ + bool isInitialized() + { + return m_initialized; + }; + + /** + * Return the port that was specified to initMaster(). + * + * @return The port specified to initMaster(). + */ + std::string getPort() + { + return m_port; + }; + + /** + * Return the Object Device Instance ID for our Master was + * specified to initMaster(). + * + * @return The Object Device Instance ID for our Master. + */ + uint32_t getDeviceInstanceID() + { + return m_deviceInstanceID; + }; + + /** + * Return the maxInfoFrames parameter that was specified to initMaster(). + * + * @return The maxInfoFrames parameter specified to initMaster(). + */ + int getMaxInfoFrames() + { + return m_maxInfoFrames; + }; + + /** + * Return the maxMaster parameter that was specified to initMaster(). + * + * @return The maxMaster parameter specified to initMaster(). + */ + int getMaxMaster() + { + return m_maxMaster; + }; + + /** + * Return the baud rate parameter that was specified to initMaster(). + * + * @return The baud rate parameter specified to initMaster(). + */ + int getBaudRate() + { + return m_baudRate; + }; + + /** + * Return the MAC address parameter that was specified to initMaster(). + * + * @return The MAC address parameter specified to initMaster(). + */ + int getMACAddress() + { + return m_macAddr; + }; + + /** + * Enable or disable debugging output. + * + * @param enable true to enable debugging, false otherwise + */ + void setDebug(bool enable); + + protected: + /** + * BACNETMSTP constructor + */ + BACNETMSTP(); + + /** + * BACNETMSTP Destructor + */ + ~BACNETMSTP(); + + // clear/reset error states + void clearErrors(); + + // error handler + static void handlerError(BACNET_ADDRESS * src, + uint8_t invoke_id, + BACNET_ERROR_CLASS error_class, + BACNET_ERROR_CODE error_code); + + // abort handler + static void handlerAbort(BACNET_ADDRESS * src, + uint8_t invoke_id, + uint8_t abort_reason, + bool server); + + // reject handler + static void handlerReject(BACNET_ADDRESS * src, + uint8_t invoke_id, + uint8_t reject_reason); + + + // our handler for dealing with return data from a ReadProperty call + static void handlerReadPropertyAck(uint8_t* service_request, + uint16_t service_len, + BACNET_ADDRESS* src, + BACNET_CONFIRMED_SERVICE_ACK_DATA* service_data); + + // our handler for writeProp acks + static void handlerWritePropertyAck(BACNET_ADDRESS* src, + uint8_t invoke_id); + + // initialize our service handlers + void initServiceHandlers(); + + // utility function + std::string string2HexString(std::string input); + + // responsible for dispatching a request to the BACnet network + bool dispatchRequest(); + + private: + // prevent copying and assignment + BACNETMSTP(BACNETMSTP const &) {}; + BACNETMSTP& operator=(BACNETMSTP const&) {}; + + // our class instance + static BACNETMSTP* m_instance; + + // has the class been created yet? + bool m_initialized; + + // Some items we can set for our master + std::string m_port; + int m_maxInfoFrames; + int m_maxMaster; + int m_baudRate; + int m_macAddr; + + // the unique Instance Number of our Master Device Object + uint32_t m_deviceInstanceID; + + // adpu timeout in milliseconds + uint16_t m_adpuTimeoutMS; + + // buffer used for receiving data + uint8_t m_rxBuffer[MAX_MPDU]; + + // our error classfication + BACERR_TYPE_T m_errorType; + + // BACnet reject info + uint8_t m_rejectReason; + std::string m_rejectString; + + // BACnet abort info + uint8_t m_abortReason; + std::string m_abortString; + + // BACnet error info + BACNET_ERROR_CLASS m_errorClass; + BACNET_ERROR_CODE m_errorCode; + std::string m_errorString; + + // generic UPM related errors - we just set the error text to + // something informative. + std::string m_upmErrorString; + + // our returned data from readProperty() + BACNET_APPLICATION_DATA_VALUE m_returnedValue; + + // current bound target address of dispatched service request + // (read/write prop, etc) + BACNET_ADDRESS m_targetAddress; + + // current invokeID (for transaction handling) + uint8_t m_invokeID; + + // error detected flag + bool m_errorDetected; + + // Commands - we create a struct to hold the arguments for each + // command type we support. Then, we create a command struct + // which contains the command type and a union containing the + // relevant arguments. This is used by dispatchRequest() to issue + // the correct request. + + // these may generate SWIG warnings, but they can be ignored as we + // do not expose these outside the class + typedef struct { + uint32_t targetDeviceInstanceID; + BACNET_OBJECT_TYPE objType; + uint32_t objInstance; + BACNET_PROPERTY_ID objProperty; + uint32_t arrayIndex; + } READ_PROPERTY_ARGS_T; + + typedef struct { + uint32_t targetDeviceInstanceID; + BACNET_OBJECT_TYPE objType; + uint32_t objInstance; + BACNET_PROPERTY_ID objProperty; + BACNET_APPLICATION_DATA_VALUE* propValue; + uint8_t propPriority; + int32_t arrayIndex; + } WRITE_PROPERTY_ARGS_T; + + struct { + BACCMD_TYPE_T cmd; + + union { + READ_PROPERTY_ARGS_T readPropArgs; + WRITE_PROPERTY_ARGS_T writePropArgs; + }; + } m_command; + + bool m_debugging; + }; +} diff --git a/src/bacnetmstp/device-client.c b/src/bacnetmstp/device-client.c new file mode 100644 index 00000000..cb25dd96 --- /dev/null +++ b/src/bacnetmstp/device-client.c @@ -0,0 +1,1026 @@ +/* + * Author: Jon Trulson + * Copyright (c) 2016 Intel Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/************************************************************************** +* +* Copyright (C) 2011 Steve Karg +* +* Permission is hereby granted, free of charge, to any person obtaining +* a copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to +* permit persons to whom the Software is furnished to do so, subject to +* the following conditions: +* +* The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*********************************************************************/ + +/** @file device-client.c Lightweight base "class" for handling all + * BACnet objects belonging to a BACnet device, as well as + * Device-specific properties. This Device instance is designed to + * meet minimal functionality for simple clients. */ + +#include +#include +#include /* for memmove */ +#include /* for timezone, localtime */ + +#define BACDL_MSTP 1 +#undef BACDL_ALL + + +/* OS specific include*/ +//#include "net.h" +#include "timer.h" +/* BACnet includes */ +#include "bacdef.h" +#include "bacdcode.h" +#include "bacenum.h" +#include "bacapp.h" +#include "config.h" /* the custom stuff */ +#include "apdu.h" +#include "rp.h" /* ReadProperty handling */ +#include "version.h" +#include "handlers.h" +#include "datalink.h" +#include "address.h" +/* include the device object */ +#include "device.h" /* me */ + +#if defined(__BORLANDC__) || defined(_WIN32) +/* seems to not be defined in time.h as specified by The Open Group */ +/* difference from UTC and local standard time */ +long int timezone; +#endif + +/* note: you really only need to define variables for + properties that are writable or that may change. + The properties that are constant can be hard coded + into the read-property encoding. */ + +static uint32_t Object_Instance_Number = 260001; +static BACNET_CHARACTER_STRING My_Object_Name; +static BACNET_DEVICE_STATUS System_Status = STATUS_OPERATIONAL; +static char *Vendor_Name = BACNET_VENDOR_NAME; +static uint16_t Vendor_Identifier = BACNET_VENDOR_ID; +static char *Model_Name = "UPM Bacnet-o-matic MS/TP"; +static char *Application_Software_Version = "1.0"; +static char *Location = "Unknown"; +static char *Description = "UPM BACNET MS/TP driver"; +/* static uint8_t Protocol_Version = 1; - constant, not settable */ +/* static uint8_t Protocol_Revision = 4; - constant, not settable */ +/* Protocol_Services_Supported - dynamically generated */ +/* Protocol_Object_Types_Supported - in RP encoding */ +/* Object_List - dynamically generated */ +/* static BACNET_SEGMENTATION Segmentation_Supported = SEGMENTATION_NONE; */ +/* static uint8_t Max_Segments_Accepted = 0; */ +/* VT_Classes_Supported */ +/* Active_VT_Sessions */ +static BACNET_TIME Local_Time; /* rely on OS, if there is one */ +static BACNET_DATE Local_Date; /* rely on OS, if there is one */ +/* NOTE: BACnet UTC Offset is inverse of common practice. + If your UTC offset is -5hours of GMT, + then BACnet UTC offset is +5hours. + BACnet UTC offset is expressed in minutes. */ +static int32_t UTC_Offset = 5 * 60; +static bool Daylight_Savings_Status = false; /* rely on OS */ +/* List_Of_Session_Keys */ +/* Time_Synchronization_Recipients */ +/* Max_Master - rely on MS/TP subsystem, if there is one */ +/* Max_Info_Frames - rely on MS/TP subsystem, if there is one */ +/* Device_Address_Binding - required, but relies on binding cache */ +static uint32_t Database_Revision = 0; +/* Configuration_Files */ +/* Last_Restore_Time */ +/* Backup_Failure_Timeout */ +/* Active_COV_Subscriptions */ +/* Slave_Proxy_Enable */ +/* Manual_Slave_Address_Binding */ +/* Auto_Slave_Discovery */ +/* Slave_Address_Binding */ +/* Profile_Name */ + +/* local forward (semi-private) and external prototypes */ +int Device_Read_Property_Local( + BACNET_READ_PROPERTY_DATA * rpdata); +extern int Routed_Device_Read_Property_Local( + BACNET_READ_PROPERTY_DATA * rpdata); +extern bool Routed_Device_Write_Property_Local( + BACNET_WRITE_PROPERTY_DATA * wp_data); + +/* All included BACnet objects */ +static object_functions_t Object_Table[] = { + {OBJECT_DEVICE, + NULL /* Init - don't init Device or it will recourse! */ , + Device_Count, + Device_Index_To_Instance, + Device_Valid_Object_Instance_Number, + Device_Object_Name, + Device_Read_Property_Local, + NULL /* Write_Property */ , + NULL /* Property_Lists */ , + NULL /* ReadRangeInfo */ , + NULL /* Iterator */ , + NULL /* Value_Lists */ , + NULL /* COV */ , + NULL /* COV Clear */ , + NULL /* Intrinsic Reporting */ }, + {MAX_BACNET_OBJECT_TYPE, + NULL /* Init */ , + NULL /* Count */ , + NULL /* Index_To_Instance */ , + NULL /* Valid_Instance */ , + NULL /* Object_Name */ , + NULL /* Read_Property */ , + NULL /* Write_Property */ , + NULL /* Property_Lists */ , + NULL /* ReadRangeInfo */ , + NULL /* Iterator */ , + NULL /* Value_Lists */ , + NULL /* COV */ , + NULL /* COV Clear */ , + NULL /* Intrinsic Reporting */ } +}; + +/** Glue function to let the Device object, when called by a handler, + * lookup which Object type needs to be invoked. + * @ingroup ObjHelpers + * @param Object_Type [in] The type of BACnet Object the handler wants to access. + * @return Pointer to the group of object helper functions that implement this + * type of Object. + */ +static struct object_functions *Device_Objects_Find_Functions( + BACNET_OBJECT_TYPE Object_Type) +{ + struct object_functions *pObject = NULL; + + pObject = &Object_Table[0]; + while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { + /* handle each object type */ + if (pObject->Object_Type == Object_Type) { + return (pObject); + } + + pObject++; + } + + return (NULL); +} + +unsigned Device_Count( + void) +{ + return 1; +} + +uint32_t Device_Index_To_Instance( + unsigned index) +{ + index = index; + return Object_Instance_Number; +} + +/* methods to manipulate the data */ + +/** Return the Object Instance number for our (single) Device Object. + * This is a key function, widely invoked by the handler code, since + * it provides "our" (ie, local) address. + * @ingroup ObjIntf + * @return The Instance number used in the BACNET_OBJECT_ID for the Device. + */ +uint32_t Device_Object_Instance_Number( + void) +{ +#ifdef BAC_ROUTING + return Routed_Device_Object_Instance_Number(); +#else + return Object_Instance_Number; +#endif +} + +bool Device_Set_Object_Instance_Number( + uint32_t object_id) +{ + bool status = true; /* return value */ + + if (object_id <= BACNET_MAX_INSTANCE) { + /* Make the change and update the database revision */ + Object_Instance_Number = object_id; + Device_Inc_Database_Revision(); + } else + status = false; + + return status; +} + +bool Device_Valid_Object_Instance_Number( + uint32_t object_id) +{ + return (Object_Instance_Number == object_id); +} + +bool Device_Object_Name( + uint32_t object_instance, + BACNET_CHARACTER_STRING * object_name) +{ + bool status = false; + + if (object_instance == Object_Instance_Number) { + status = characterstring_copy(object_name, &My_Object_Name); + } + + return status; +} + +bool Device_Set_Object_Name( + BACNET_CHARACTER_STRING * object_name) +{ + bool status = false; /*return value */ + + if (!characterstring_same(&My_Object_Name, object_name)) { + /* Make the change and update the database revision */ + status = characterstring_copy(&My_Object_Name, object_name); + Device_Inc_Database_Revision(); + } + + return status; +} + +BACNET_DEVICE_STATUS Device_System_Status( + void) +{ + return System_Status; +} + +int Device_Set_System_Status( + BACNET_DEVICE_STATUS status, + bool local) +{ + int result = 0; /*return value - 0 = ok, -1 = bad value, -2 = not allowed */ + + /* We limit the options available depending on whether the source is + * internal or external. */ + if (local) { + switch (status) { + case STATUS_OPERATIONAL: + case STATUS_OPERATIONAL_READ_ONLY: + case STATUS_DOWNLOAD_REQUIRED: + case STATUS_DOWNLOAD_IN_PROGRESS: + case STATUS_NON_OPERATIONAL: + System_Status = status; + break; + + /* Don't support backup at present so don't allow setting */ + case STATUS_BACKUP_IN_PROGRESS: + result = -2; + break; + + default: + result = -1; + break; + } + } else { + switch (status) { + /* Allow these for the moment as a way to easily alter + * overall device operation. The lack of password protection + * or other authentication makes allowing writes to this + * property a risky facility to provide. + */ + case STATUS_OPERATIONAL: + case STATUS_OPERATIONAL_READ_ONLY: + case STATUS_NON_OPERATIONAL: + System_Status = status; + break; + + /* Don't allow outsider set this - it should probably + * be set if the device config is incomplete or + * corrupted or perhaps after some sort of operator + * wipe operation. + */ + case STATUS_DOWNLOAD_REQUIRED: + /* Don't allow outsider set this - it should be set + * internally at the start of a multi packet download + * perhaps indirectly via PT or WF to a config file. + */ + case STATUS_DOWNLOAD_IN_PROGRESS: + /* Don't support backup at present so don't allow setting */ + case STATUS_BACKUP_IN_PROGRESS: + result = -2; + break; + + default: + result = -1; + break; + } + } + + return (result); +} + +const char *Device_Vendor_Name( + void) +{ + return Vendor_Name; +} + +/** Returns the Vendor ID for this Device. + * See the assignments at http://www.bacnet.org/VendorID/BACnet%20Vendor%20IDs.htm + * @return The Vendor ID of this Device. + */ +uint16_t Device_Vendor_Identifier( + void) +{ + return Vendor_Identifier; +} + +void Device_Set_Vendor_Identifier( + uint16_t vendor_id) +{ + Vendor_Identifier = vendor_id; +} + +const char *Device_Model_Name( + void) +{ + return Model_Name; +} + +bool Device_Set_Model_Name( + const char *name, + size_t length) +{ + bool status = false; /*return value */ + + if (length < sizeof(Model_Name)) { + memmove(Model_Name, name, length); + Model_Name[length] = 0; + status = true; + } + + return status; +} + +const char *Device_Firmware_Revision( + void) +{ + return BACnet_Version; +} + +const char *Device_Application_Software_Version( + void) +{ + return Application_Software_Version; +} + +bool Device_Set_Application_Software_Version( + const char *name, + size_t length) +{ + bool status = false; /*return value */ + + if (length < sizeof(Application_Software_Version)) { + memmove(Application_Software_Version, name, length); + Application_Software_Version[length] = 0; + status = true; + } + + return status; +} + +const char *Device_Description( + void) +{ + return Description; +} + +bool Device_Set_Description( + const char *name, + size_t length) +{ + bool status = false; /*return value */ + + if (length < sizeof(Description)) { + memmove(Description, name, length); + Description[length] = 0; + status = true; + } + + return status; +} + +const char *Device_Location( + void) +{ + return Location; +} + +bool Device_Set_Location( + const char *name, + size_t length) +{ + bool status = false; /*return value */ + + if (length < sizeof(Location)) { + memmove(Location, name, length); + Location[length] = 0; + status = true; + } + + return status; +} + +uint8_t Device_Protocol_Version( + void) +{ + return BACNET_PROTOCOL_VERSION; +} + +uint8_t Device_Protocol_Revision( + void) +{ + return BACNET_PROTOCOL_REVISION; +} + +BACNET_SEGMENTATION Device_Segmentation_Supported( + void) +{ + return SEGMENTATION_NONE; +} + +uint32_t Device_Database_Revision( + void) +{ + return Database_Revision; +} + +void Device_Set_Database_Revision( + uint32_t revision) +{ + Database_Revision = revision; +} + +/* + * Shortcut for incrementing database revision as this is potentially + * the most common operation if changing object names and ids is + * implemented. + */ +void Device_Inc_Database_Revision( + void) +{ + Database_Revision++; +} + +/** Get the total count of objects supported by this Device Object. + * @note Since many network clients depend on the object list + * for discovery, it must be consistent! + * @return The count of objects, for all supported Object types. + */ +unsigned Device_Object_List_Count( + void) +{ + unsigned count = 0; /* number of objects */ + struct object_functions *pObject = NULL; + + /* initialize the default return values */ + pObject = &Object_Table[0]; + while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { + if (pObject->Object_Count) { + count += pObject->Object_Count(); + } + pObject++; + } + + return count; +} + +/** Lookup the Object at the given array index in the Device's Object List. + * Even though we don't keep a single linear array of objects in the Device, + * this method acts as though we do and works through a virtual, concatenated + * array of all of our object type arrays. + * + * @param array_index [in] The desired array index (1 to N) + * @param object_type [out] The object's type, if found. + * @param instance [out] The object's instance number, if found. + * @return True if found, else false. + */ +bool Device_Object_List_Identifier( + unsigned array_index, + int *object_type, + uint32_t * instance) +{ + bool status = false; + unsigned count = 0; + unsigned object_index = 0; + unsigned temp_index = 0; + struct object_functions *pObject = NULL; + + /* array index zero is length - so invalid */ + if (array_index == 0) { + return status; + } + object_index = array_index - 1; + /* initialize the default return values */ + pObject = &Object_Table[0]; + while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { + if (pObject->Object_Count) { + object_index -= count; + count = pObject->Object_Count(); + if (object_index < count) { + /* Use the iterator function if available otherwise + * look for the index to instance to get the ID */ + if (pObject->Object_Iterator) { + /* First find the first object */ + temp_index = pObject->Object_Iterator(~(unsigned) 0); + /* Then step through the objects to find the nth */ + while (object_index != 0) { + temp_index = pObject->Object_Iterator(temp_index); + object_index--; + } + /* set the object_index up before falling through to next bit */ + object_index = temp_index; + } + if (pObject->Object_Index_To_Instance) { + *object_type = pObject->Object_Type; + *instance = + pObject->Object_Index_To_Instance(object_index); + status = true; + break; + } + } + } + pObject++; + } + + return status; +} + +/** Determine if we have an object with the given object_name. + * If the object_type and object_instance pointers are not null, + * and the lookup succeeds, they will be given the resulting values. + * @param object_name [in] The desired Object Name to look for. + * @param object_type [out] The BACNET_OBJECT_TYPE of the matching Object. + * @param object_instance [out] The object instance number of the matching Object. + * @return True on success or else False if not found. + */ +bool Device_Valid_Object_Name( + BACNET_CHARACTER_STRING * object_name1, + int *object_type, + uint32_t * object_instance) +{ + bool found = false; + int type = 0; + uint32_t instance; + unsigned max_objects = 0, i = 0; + bool check_id = false; + BACNET_CHARACTER_STRING object_name2; + struct object_functions *pObject = NULL; + + max_objects = Device_Object_List_Count(); + for (i = 1; i <= max_objects; i++) { + check_id = Device_Object_List_Identifier(i, &type, &instance); + if (check_id) { + pObject = Device_Objects_Find_Functions(type); + if ((pObject != NULL) && (pObject->Object_Name != NULL) && + (pObject->Object_Name(instance, &object_name2) && + characterstring_same(object_name1, &object_name2))) { + found = true; + if (object_type) { + *object_type = type; + } + if (object_instance) { + *object_instance = instance; + } + break; + } + } + } + + return found; +} + +/** Determine if we have an object of this type and instance number. + * @param object_type [in] The desired BACNET_OBJECT_TYPE + * @param object_instance [in] The object instance number to be looked up. + * @return True if found, else False if no such Object in this device. + */ +bool Device_Valid_Object_Id( + int object_type, + uint32_t object_instance) +{ + bool status = false; /* return value */ + struct object_functions *pObject = NULL; + + pObject = Device_Objects_Find_Functions(object_type); + if ((pObject != NULL) && (pObject->Object_Valid_Instance != NULL)) { + status = pObject->Object_Valid_Instance(object_instance); + } + + return status; +} + +/** Copy a child object's object_name value, given its ID. + * @param object_type [in] The BACNET_OBJECT_TYPE of the child Object. + * @param object_instance [in] The object instance number of the child Object. + * @param object_name [out] The Object Name found for this child Object. + * @return True on success or else False if not found. + */ +bool Device_Object_Name_Copy( + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_CHARACTER_STRING * object_name) +{ + struct object_functions *pObject = NULL; + bool found = false; + + pObject = Device_Objects_Find_Functions(object_type); + if ((pObject != NULL) && (pObject->Object_Name != NULL)) { + found = pObject->Object_Name(object_instance, object_name); + } + + return found; +} + +static void Update_Current_Time( + void) +{ + struct tm *tblock = NULL; +#if defined(_MSC_VER) + time_t tTemp; +#else + struct timeval tv; +#endif +/* +struct tm + +int tm_sec Seconds [0,60]. +int tm_min Minutes [0,59]. +int tm_hour Hour [0,23]. +int tm_mday Day of month [1,31]. +int tm_mon Month of year [0,11]. +int tm_year Years since 1900. +int tm_wday Day of week [0,6] (Sunday =0). +int tm_yday Day of year [0,365]. +int tm_isdst Daylight Savings flag. +*/ +#if defined(_MSC_VER) + time(&tTemp); + tblock = (struct tm *)localtime(&tTemp); +#else + if (gettimeofday(&tv, NULL) == 0) { + tblock = (struct tm *)localtime((const time_t *)&tv.tv_sec); + } +#endif + + if (tblock) { + datetime_set_date(&Local_Date, (uint16_t) tblock->tm_year + 1900, + (uint8_t) tblock->tm_mon + 1, (uint8_t) tblock->tm_mday); +#if !defined(_MSC_VER) + datetime_set_time(&Local_Time, (uint8_t) tblock->tm_hour, + (uint8_t) tblock->tm_min, (uint8_t) tblock->tm_sec, + (uint8_t) (tv.tv_usec / 10000)); +#else + datetime_set_time(&Local_Time, (uint8_t) tblock->tm_hour, + (uint8_t) tblock->tm_min, (uint8_t) tblock->tm_sec, 0); +#endif + if (tblock->tm_isdst) { + Daylight_Savings_Status = true; + } else { + Daylight_Savings_Status = false; + } + /* note: timezone is declared in stdlib. */ + UTC_Offset = timezone / 60; + } else { + datetime_date_wildcard_set(&Local_Date); + datetime_time_wildcard_set(&Local_Time); + Daylight_Savings_Status = false; + } +} + +void Device_getCurrentDateTime( + BACNET_DATE_TIME * DateTime) +{ + Update_Current_Time(); + + DateTime->date = Local_Date; + DateTime->time = Local_Time; +} + +int32_t Device_UTC_Offset(void) +{ + Update_Current_Time(); + + return UTC_Offset; +} + +bool Device_Daylight_Savings_Status(void) +{ + return Daylight_Savings_Status; +} + +/* return the length of the apdu encoded or BACNET_STATUS_ERROR for error or + BACNET_STATUS_ABORT for abort message */ +int Device_Read_Property_Local( + BACNET_READ_PROPERTY_DATA * rpdata) +{ + int apdu_len = 0; /* return value */ + int len = 0; /* apdu len intermediate value */ + BACNET_BIT_STRING bit_string; + BACNET_CHARACTER_STRING char_string; + unsigned i = 0; + int object_type = 0; + uint32_t instance = 0; + unsigned count = 0; + uint8_t *apdu = NULL; + struct object_functions *pObject = NULL; + bool found = false; + + if ((rpdata == NULL) || (rpdata->application_data == NULL) || + (rpdata->application_data_len == 0)) { + return 0; + } + apdu = rpdata->application_data; + switch (rpdata->object_property) { + case PROP_OBJECT_IDENTIFIER: + apdu_len = + encode_application_object_id(&apdu[0], OBJECT_DEVICE, + Object_Instance_Number); + break; + case PROP_OBJECT_NAME: + apdu_len = + encode_application_character_string(&apdu[0], &My_Object_Name); + break; + case PROP_OBJECT_TYPE: + apdu_len = encode_application_enumerated(&apdu[0], OBJECT_DEVICE); + break; + case PROP_DESCRIPTION: + characterstring_init_ansi(&char_string, Description); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + break; + case PROP_SYSTEM_STATUS: + apdu_len = encode_application_enumerated(&apdu[0], System_Status); + break; + case PROP_VENDOR_NAME: + characterstring_init_ansi(&char_string, Vendor_Name); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + break; + case PROP_VENDOR_IDENTIFIER: + apdu_len = + encode_application_unsigned(&apdu[0], Vendor_Identifier); + break; + case PROP_MODEL_NAME: + characterstring_init_ansi(&char_string, Model_Name); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + break; + case PROP_FIRMWARE_REVISION: + characterstring_init_ansi(&char_string, BACnet_Version); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + break; + case PROP_APPLICATION_SOFTWARE_VERSION: + characterstring_init_ansi(&char_string, + Application_Software_Version); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + break; + case PROP_LOCATION: + characterstring_init_ansi(&char_string, Location); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + break; + case PROP_PROTOCOL_VERSION: + apdu_len = + encode_application_unsigned(&apdu[0], + Device_Protocol_Version()); + break; + case PROP_PROTOCOL_REVISION: + apdu_len = + encode_application_unsigned(&apdu[0], + Device_Protocol_Revision()); + break; + case PROP_PROTOCOL_SERVICES_SUPPORTED: + /* Note: list of services that are executed, not initiated. */ + bitstring_init(&bit_string); + for (i = 0; i < MAX_BACNET_SERVICES_SUPPORTED; i++) { + /* automatic lookup based on handlers set */ + bitstring_set_bit(&bit_string, (uint8_t) i, + apdu_service_supported((BACNET_SERVICES_SUPPORTED) i)); + } + apdu_len = encode_application_bitstring(&apdu[0], &bit_string); + break; + case PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED: + /* Note: this is the list of objects that can be in this device, + not a list of objects that this device can access */ + bitstring_init(&bit_string); + for (i = 0; i < MAX_ASHRAE_OBJECT_TYPE; i++) { + /* initialize all the object types to not-supported */ + bitstring_set_bit(&bit_string, (uint8_t) i, false); + } + /* set the object types with objects to supported */ + + pObject = &Object_Table[0]; + while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { + if ((pObject->Object_Count) && (pObject->Object_Count() > 0)) { + bitstring_set_bit(&bit_string, pObject->Object_Type, true); + } + pObject++; + } + apdu_len = encode_application_bitstring(&apdu[0], &bit_string); + break; + case PROP_OBJECT_LIST: + count = Device_Object_List_Count(); + /* Array element zero is the number of objects in the list */ + if (rpdata->array_index == 0) + apdu_len = encode_application_unsigned(&apdu[0], count); + /* if no index was specified, then try to encode the entire list */ + /* into one packet. Note that more than likely you will have */ + /* to return an error if the number of encoded objects exceeds */ + /* your maximum APDU size. */ + else if (rpdata->array_index == BACNET_ARRAY_ALL) { + for (i = 1; i <= count; i++) { + found = + Device_Object_List_Identifier(i, &object_type, + &instance); + if (found) { + len = + encode_application_object_id(&apdu[apdu_len], + object_type, instance); + apdu_len += len; + /* assume next one is the same size as this one */ + /* can we all fit into the APDU? Don't check for last entry */ + if ((i != count) && (apdu_len + len) >= MAX_APDU) { + /* Abort response */ + rpdata->error_code = + ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED; + apdu_len = BACNET_STATUS_ABORT; + break; + } + } else { + /* error: internal error? */ + rpdata->error_class = ERROR_CLASS_SERVICES; + rpdata->error_code = ERROR_CODE_OTHER; + apdu_len = BACNET_STATUS_ERROR; + break; + } + } + } else { + found = + Device_Object_List_Identifier(rpdata->array_index, + &object_type, &instance); + if (found) { + apdu_len = + encode_application_object_id(&apdu[0], object_type, + instance); + } else { + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX; + apdu_len = BACNET_STATUS_ERROR; + } + } + break; + case PROP_MAX_APDU_LENGTH_ACCEPTED: + apdu_len = encode_application_unsigned(&apdu[0], MAX_APDU); + break; + case PROP_SEGMENTATION_SUPPORTED: + apdu_len = + encode_application_enumerated(&apdu[0], + Device_Segmentation_Supported()); + break; + case PROP_APDU_TIMEOUT: + apdu_len = encode_application_unsigned(&apdu[0], apdu_timeout()); + break; + case PROP_NUMBER_OF_APDU_RETRIES: + apdu_len = encode_application_unsigned(&apdu[0], apdu_retries()); + break; + case PROP_DEVICE_ADDRESS_BINDING: + /* FIXME: the real max apdu remaining should be passed into function */ + apdu_len = address_list_encode(&apdu[0], MAX_APDU); + break; + case PROP_DATABASE_REVISION: + apdu_len = + encode_application_unsigned(&apdu[0], Database_Revision); + break; +#if defined(BACDL_MSTP) + case PROP_MAX_INFO_FRAMES: + apdu_len = + encode_application_unsigned(&apdu[0], + dlmstp_max_info_frames()); + break; + case PROP_MAX_MASTER: + apdu_len = + encode_application_unsigned(&apdu[0], dlmstp_max_master()); + break; +#endif + case PROP_ACTIVE_COV_SUBSCRIPTIONS: + break; + default: + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + apdu_len = BACNET_STATUS_ERROR; + break; + } + /* only array properties can have array options */ + if ((apdu_len >= 0) && (rpdata->object_property != PROP_OBJECT_LIST) && + (rpdata->array_index != BACNET_ARRAY_ALL)) { + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; + apdu_len = BACNET_STATUS_ERROR; + } + + return apdu_len; +} + +/** Looks up the requested Object and Property, and encodes its Value in an APDU. + * @ingroup ObjIntf + * If the Object or Property can't be found, sets the error class and code. + * + * @param rpdata [in,out] Structure with the desired Object and Property info + * on entry, and APDU message on return. + * @return The length of the APDU on success, else BACNET_STATUS_ERROR + */ +int Device_Read_Property( + BACNET_READ_PROPERTY_DATA * rpdata) +{ + int apdu_len = BACNET_STATUS_ERROR; + struct object_functions *pObject = NULL; + + /* initialize the default return values */ + rpdata->error_class = ERROR_CLASS_OBJECT; + rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT; + pObject = Device_Objects_Find_Functions(rpdata->object_type); + if (pObject != NULL) { + if (pObject->Object_Valid_Instance && + pObject->Object_Valid_Instance(rpdata->object_instance)) { + if (pObject->Object_Read_Property) { + apdu_len = pObject->Object_Read_Property(rpdata); + } + } else { + rpdata->error_class = ERROR_CLASS_OBJECT; + rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + } else { + rpdata->error_class = ERROR_CLASS_OBJECT; + rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + + return apdu_len; +} + +/** Initialize the Device Object. + Initialize the group of object helper functions for any supported Object. + Initialize each of the Device Object child Object instances. + * @ingroup ObjIntf + * @param object_table [in,out] array of structure with object functions. + * Each Child Object must provide some implementation of each of these + * functions in order to properly support the default handlers. + */ +void Device_Init( + object_functions_t * object_table) +{ + struct object_functions *pObject = NULL; + + characterstring_init_ansi(&My_Object_Name, "SimpleClient"); + /* we don't use the object table passed in */ + (void) object_table; + pObject = &Object_Table[0]; + while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { + if (pObject->Object_Init) { + pObject->Object_Init(); + } + pObject++; + } +} diff --git a/src/bacnetmstp/device.h b/src/bacnetmstp/device.h new file mode 100644 index 00000000..b9c54dbe --- /dev/null +++ b/src/bacnetmstp/device.h @@ -0,0 +1,465 @@ +/************************************************************************** +* +* Copyright (C) 2005 Steve Karg +* +* Permission is hereby granted, free of charge, to any person obtaining +* a copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to +* permit persons to whom the Software is furnished to do so, subject to +* the following conditions: +* +* The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*********************************************************************/ + +/** @file device.h Defines functions for handling all BACnet objects belonging + * to a BACnet device, as well as Device-specific properties. */ + +#ifndef DEVICE_H +#define DEVICE_H + +#include +#include +#include "bacdef.h" +#include "bacenum.h" +#include "wp.h" +#include "rd.h" +#include "rp.h" +#include "rpm.h" +#include "readrange.h" + +/** Called so a BACnet object can perform any necessary initialization. + * @ingroup ObjHelpers + */ +typedef void ( + *object_init_function) ( + void); + +/** Counts the number of objects of this type. + * @ingroup ObjHelpers + * @return Count of implemented objects of this type. + */ +typedef unsigned ( + *object_count_function) ( + void); + +/** Maps an object index position to its corresponding BACnet object instance number. + * @ingroup ObjHelpers + * @param index [in] The index of the object, in the array of objects of its type. + * @return The BACnet object instance number to be used in a BACNET_OBJECT_ID. + */ +typedef uint32_t( + *object_index_to_instance_function) + ( + unsigned index); + +/** Provides the BACnet Object_Name for a given object instance of this type. + * @ingroup ObjHelpers + * @param object_instance [in] The object instance number to be looked up. + * @param object_name [in,out] Pointer to a character_string structure that + * will hold a copy of the object name if this is a valid object_instance. + * @return True if the object_instance is valid and object_name has been + * filled with a copy of the Object's name. + */ +typedef bool( + *object_name_function) + ( + uint32_t object_instance, + BACNET_CHARACTER_STRING * object_name); + +/** Look in the table of objects of this type, and see if this is a valid + * instance number. + * @ingroup ObjHelpers + * @param [in] The object instance number to be looked up. + * @return True if the object instance refers to a valid object of this type. + */ +typedef bool( + *object_valid_instance_function) ( + uint32_t object_instance); + +/** Helper function to step through an array of objects and find either the + * first one or the next one of a given type. Used to step through an array + * of objects which is not necessarily contiguious for each type i.e. the + * index for the 'n'th object of a given type is not necessarily 'n'. + * @ingroup ObjHelpers + * @param [in] The index of the current object or a value of ~0 to indicate + * start at the beginning. + * @return The index of the next object of the required type or ~0 (all bits + * == 1) to indicate no more objects found. + */ +typedef unsigned ( + *object_iterate_function) ( + unsigned current_index); + +/** Look in the table of objects of this type, and get the COV Value List. + * @ingroup ObjHelpers + * @param [in] The object instance number to be looked up. + * @param [out] The value list + * @return True if the object instance supports this feature, and has changed. + */ +typedef bool( + *object_value_list_function) ( + uint32_t object_instance, + BACNET_PROPERTY_VALUE * value_list); + +/** Look in the table of objects for this instance to see if value changed. + * @ingroup ObjHelpers + * @param [in] The object instance number to be looked up. + * @return True if the object instance has changed. + */ +typedef bool( + *object_cov_function) ( + uint32_t object_instance); + +/** Look in the table of objects for this instance to clear the changed flag. + * @ingroup ObjHelpers + * @param [in] The object instance number to be looked up. + */ +typedef void ( + *object_cov_clear_function) ( + uint32_t object_instance); + +/** Intrinsic Reporting funcionality. + * @ingroup ObjHelpers + * @param [in] Object instance. + */ +typedef void ( + *object_intrinsic_reporting_function) ( + uint32_t object_instance); + + +/** Defines the group of object helper functions for any supported Object. + * @ingroup ObjHelpers + * Each Object must provide some implementation of each of these helpers + * in order to properly support the handlers. Eg, the ReadProperty handler + * handler_read_property() relies on the instance of Object_Read_Property + * for each Object type, or configure the function as NULL. + * In both appearance and operation, this group of functions acts like + * they are member functions of a C++ Object base class. + */ +typedef struct object_functions { + BACNET_OBJECT_TYPE Object_Type; + object_init_function Object_Init; + object_count_function Object_Count; + object_index_to_instance_function Object_Index_To_Instance; + object_valid_instance_function Object_Valid_Instance; + object_name_function Object_Name; + read_property_function Object_Read_Property; + write_property_function Object_Write_Property; + rpm_property_lists_function Object_RPM_List; + rr_info_function Object_RR_Info; + object_iterate_function Object_Iterator; + object_value_list_function Object_Value_List; + object_cov_function Object_COV; + object_cov_clear_function Object_COV_Clear; + object_intrinsic_reporting_function Object_Intrinsic_Reporting; +} object_functions_t; + +/* String Lengths - excluding any nul terminator */ +#define MAX_DEV_NAME_LEN 32 +#define MAX_DEV_LOC_LEN 64 +#define MAX_DEV_MOD_LEN 32 +#define MAX_DEV_VER_LEN 16 +#define MAX_DEV_DESC_LEN 64 + +/** Structure to define the Object Properties common to all Objects. */ +typedef struct commonBacObj_s { + + /** The BACnet type of this object (ie, what class is this object from?). + * This property, of type BACnetObjectType, indicates membership in a + * particular object type class. Each inherited class will be of one type. + */ + BACNET_OBJECT_TYPE mObject_Type; + + /** The instance number for this class instance. */ + uint32_t Object_Instance_Number; + + /** Object Name; must be unique. + * This property, of type CharacterString, shall represent a name for + * the object that is unique within the BACnet Device that maintains it. + */ + char Object_Name[MAX_DEV_NAME_LEN]; + +} COMMON_BAC_OBJECT; + + +/** Structure to define the Properties of Device Objects which distinguish + * one instance from another. + * This structure only defines fields for properties that are unique to + * a given Device object. The rest may be fixed in device.c or hard-coded + * into the read-property encoding. + * This may be useful for implementations which manage multiple Devices, + * eg, a Gateway. + */ +typedef struct devObj_s { + /** The BACnet Device Address for this device; ->len depends on DLL type. */ + BACNET_ADDRESS bacDevAddr; + + /** Structure for the Object Properties common to all Objects. */ + COMMON_BAC_OBJECT bacObj; + + /** Device Description. */ + char Description[MAX_DEV_DESC_LEN]; + + /** The upcounter that shows if the Device ID or object structure has changed. */ + uint32_t Database_Revision; +} DEVICE_OBJECT_DATA; + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + void Device_Init( + object_functions_t * object_table); + + bool Device_Reinitialize( + BACNET_REINITIALIZE_DEVICE_DATA * rd_data); + + BACNET_REINITIALIZED_STATE Device_Reinitialized_State( + void); + + rr_info_function Device_Objects_RR_Info( + BACNET_OBJECT_TYPE object_type); + + void Device_getCurrentDateTime( + BACNET_DATE_TIME * DateTime); + int32_t Device_UTC_Offset(void); + bool Device_Daylight_Savings_Status(void); + + void Device_Property_Lists( + const int **pRequired, + const int **pOptional, + const int **pProprietary); + void Device_Objects_Property_List( + BACNET_OBJECT_TYPE object_type, + struct special_property_list_t *pPropertyList); + /* functions to support COV */ + bool Device_Encode_Value_List( + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_PROPERTY_VALUE * value_list); + bool Device_Value_List_Supported( + BACNET_OBJECT_TYPE object_type); + bool Device_COV( + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance); + void Device_COV_Clear( + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance); + + uint32_t Device_Object_Instance_Number( + void); + bool Device_Set_Object_Instance_Number( + uint32_t object_id); + bool Device_Valid_Object_Instance_Number( + uint32_t object_id); + unsigned Device_Object_List_Count( + void); + bool Device_Object_List_Identifier( + unsigned array_index, + int *object_type, + uint32_t * instance); + + unsigned Device_Count( + void); + uint32_t Device_Index_To_Instance( + unsigned index); + + bool Device_Object_Name( + uint32_t object_instance, + BACNET_CHARACTER_STRING * object_name); + bool Device_Set_Object_Name( + BACNET_CHARACTER_STRING * object_name); + /* Copy a child object name, given its ID. */ + bool Device_Object_Name_Copy( + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_CHARACTER_STRING * object_name); + bool Device_Object_Name_ANSI_Init(const char * value); + + BACNET_DEVICE_STATUS Device_System_Status( + void); + int Device_Set_System_Status( + BACNET_DEVICE_STATUS status, + bool local); + + const char *Device_Vendor_Name( + void); + + uint16_t Device_Vendor_Identifier( + void); + void Device_Set_Vendor_Identifier( + uint16_t vendor_id); + + const char *Device_Model_Name( + void); + bool Device_Set_Model_Name( + const char *name, + size_t length); + + const char *Device_Firmware_Revision( + void); + + const char *Device_Application_Software_Version( + void); + bool Device_Set_Application_Software_Version( + const char *name, + size_t length); + + const char *Device_Description( + void); + bool Device_Set_Description( + const char *name, + size_t length); + + const char *Device_Location( + void); + bool Device_Set_Location( + const char *name, + size_t length); + + /* some stack-centric constant values - no set methods */ + uint8_t Device_Protocol_Version( + void); + uint8_t Device_Protocol_Revision( + void); + BACNET_SEGMENTATION Device_Segmentation_Supported( + void); + + uint32_t Device_Database_Revision( + void); + void Device_Set_Database_Revision( + uint32_t revision); + void Device_Inc_Database_Revision( + void); + + bool Device_Valid_Object_Name( + BACNET_CHARACTER_STRING * object_name, + int *object_type, + uint32_t * object_instance); + bool Device_Valid_Object_Id( + int object_type, + uint32_t object_instance); + + int Device_Read_Property( + BACNET_READ_PROPERTY_DATA * rpdata); + bool Device_Write_Property( + BACNET_WRITE_PROPERTY_DATA * wp_data); + + bool DeviceGetRRInfo( + BACNET_READ_RANGE_DATA * pRequest, /* Info on the request */ + RR_PROP_INFO * pInfo); /* Where to put the information */ + + int Device_Read_Property_Local( + BACNET_READ_PROPERTY_DATA * rpdata); + bool Device_Write_Property_Local( + BACNET_WRITE_PROPERTY_DATA * wp_data); + +#if defined(INTRINSIC_REPORTING) + void Device_local_reporting( + void); +#endif + +/* Prototypes for Routing functionality in the Device Object. + * Enable by defining BAC_ROUTING in config.h and including gw_device.c + * in the build (lib/Makefile). + */ + void Routing_Device_Init( + uint32_t first_object_instance); + + uint16_t Add_Routed_Device( + uint32_t Object_Instance, + BACNET_CHARACTER_STRING * Object_Name, + const char *Description); + DEVICE_OBJECT_DATA *Get_Routed_Device_Object( + int idx); + BACNET_ADDRESS *Get_Routed_Device_Address( + int idx); + + void routed_get_my_address( + BACNET_ADDRESS * my_address); + + bool Routed_Device_Address_Lookup( + int idx, + uint8_t address_len, + uint8_t * mac_adress); + bool Routed_Device_GetNext( + BACNET_ADDRESS * dest, + int *DNET_list, + int *cursor); + bool Routed_Device_Is_Valid_Network( + uint16_t dest_net, + int *DNET_list); + + uint32_t Routed_Device_Index_To_Instance( + unsigned index); + bool Routed_Device_Valid_Object_Instance_Number( + uint32_t object_id); + bool Routed_Device_Name( + uint32_t object_instance, + BACNET_CHARACTER_STRING * object_name); + uint32_t Routed_Device_Object_Instance_Number( + void); + bool Routed_Device_Set_Object_Instance_Number( + uint32_t object_id); + bool Routed_Device_Set_Object_Name( + uint8_t encoding, + const char *value, + size_t length); + bool Routed_Device_Set_Description( + const char *name, + size_t length); + void Routed_Device_Inc_Database_Revision( + void); + int Routed_Device_Service_Approval( + BACNET_CONFIRMED_SERVICE service, + int service_argument, + uint8_t * apdu_buff, + uint8_t invoke_id); + + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +/** @defgroup ObjFrmwk Object Framework + * The modules in this section describe the BACnet-stack's framework for + * BACnet-defined Objects (Device, Analog Input, etc). There are two submodules + * to describe this arrangement: + * - The "object helper functions" which provide C++-like common functionality + * to all supported object types. + * - The interface between the implemented Objects and the BAC-stack services, + * specifically the handlers, which are mediated through function calls to + * the Device object. + *//** @defgroup ObjHelpers Object Helper Functions + * @ingroup ObjFrmwk + * This section describes the function templates for the helper functions that + * provide common object support. + *//** @defgroup ObjIntf Handler-to-Object Interface Functions + * @ingroup ObjFrmwk + * This section describes the fairly limited set of functions that link the + * BAC-stack handlers to the BACnet Object instances. All of these calls are + * situated in the Device Object, which "knows" how to reach its child Objects. + * + * Most of these calls have a common operation: + * -# Call Device_Objects_Find_Functions( for the desired Object_Type ) + * - Gets a pointer to the object_functions for this Type of Object. + * -# Call the Object's Object_Valid_Instance( for the desired object_instance ) + * to make sure there is such an instance. + * -# Call the Object helper function needed by the handler, + * eg Object_Read_Property() for the RP handler. + * + */ +#endif diff --git a/src/bacnetmstp/javaupm_bacnetmstp.i b/src/bacnetmstp/javaupm_bacnetmstp.i new file mode 100644 index 00000000..f3b79276 --- /dev/null +++ b/src/bacnetmstp/javaupm_bacnetmstp.i @@ -0,0 +1,23 @@ +%module javaupm_bacnetmstp +%include "../upm.i" +%include "typemaps.i" +%include "cpointer.i" +%include "arrays_java.i"; +%include "../java_buffer.i" + +%{ + #include "bacnetmstp.h" +%} + +%include "bacnetmstp.h" + +%pragma(java) jniclasscode=%{ + static { + try { + System.loadLibrary("javaupm_bacnetmstp"); + } catch (UnsatisfiedLinkError e) { + System.err.println("Native code library failed to load. \n" + e); + System.exit(1); + } + } +%} diff --git a/src/bacnetmstp/jsupm_bacnetmstp.i b/src/bacnetmstp/jsupm_bacnetmstp.i new file mode 100644 index 00000000..0a8adea8 --- /dev/null +++ b/src/bacnetmstp/jsupm_bacnetmstp.i @@ -0,0 +1,11 @@ +%module jsupm_bacnetmstp +%include "../upm.i" +%include "stdint.i" +%include "cpointer.i" + +%pointer_functions(float, floatp); + +%include "bacnetmstp.h" +%{ + #include "bacnetmstp.h" +%} diff --git a/src/bacnetmstp/pyupm_bacnetmstp.i b/src/bacnetmstp/pyupm_bacnetmstp.i new file mode 100644 index 00000000..727f9ef9 --- /dev/null +++ b/src/bacnetmstp/pyupm_bacnetmstp.i @@ -0,0 +1,15 @@ +// Include doxygen-generated documentation +%include "pyupm_doxy2swig.i" +%module pyupm_bacnetmstp +%include "../upm.i" +%include "stdint.i" +%include "cpointer.i" + +%feature("autodoc", "3"); + +%pointer_functions(float, floatp); + +%include "bacnetmstp.h" +%{ + #include "bacnetmstp.h" +%} diff --git a/src/bacnetmstp/timer.h b/src/bacnetmstp/timer.h new file mode 100644 index 00000000..a2fff783 --- /dev/null +++ b/src/bacnetmstp/timer.h @@ -0,0 +1,65 @@ +/************************************************************************** +* +* Copyright (C) 2009 Steve Karg +* +* Permission is hereby granted, free of charge, to any person obtaining +* a copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to +* permit persons to whom the Software is furnished to do so, subject to +* the following conditions: +* +* The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*********************************************************************/ +#ifndef TIMER_H +#define TIMER_H + +#include +#include +#include /* for timeval */ + +/* Timer Module */ +#ifndef MAX_MILLISECOND_TIMERS +#define TIMER_SILENCE 0 +#define MAX_MILLISECOND_TIMERS 1 +#endif + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + uint32_t timeGetTime( + void); + + void timer_init( + void); + uint32_t timer_milliseconds( + unsigned index); + bool timer_elapsed_milliseconds( + unsigned index, + uint32_t value); + bool timer_elapsed_seconds( + unsigned index, + uint32_t value); + bool timer_elapsed_minutes( + unsigned index, + uint32_t seconds); + uint32_t timer_milliseconds_set( + unsigned index, + uint32_t value); + uint32_t timer_reset( + unsigned index); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif