From c7b5204fe490815d22b431e216651a0a04eb0508 Mon Sep 17 00:00:00 2001 From: Jon Trulson Date: Mon, 28 Mar 2016 16:47:25 -0600 Subject: [PATCH] bacnetmstp: initial BACnet MS/TP implementation This driver is implemented as a singleton due to it's reliance on the bacnet-stack implementation. This implementation does not currently support multiple BACnet networks at the same time, though in the future it might, depending on the future of bacnet-stack development. The version of bacnet-stack used in developing this driver was 0.8.3. This driver is not intended to be used directly by end users, rather it is intended for UPM drivers supporting specific BACnet devices, such as the Veris E50H5 Energy Meter. Unfortunately, this means that a process can only support a single RS-485 BACnet network, though you can support multiple devices on that network. No examples are provided. Please look at the E50HX driver for an example of how to use this class in a BACnet MS/TP device driver if you want to write one. When initialized, the bacnet-stack library will attach to your RS-485 based BACnet network, and start a Master Finite State Machine (FSM) in a separate thread. This thread will handle the details of token passing and locating other Masters in the network (Poll For Master). This driver will appear as a BACnet Master device on the BACnet network, which supports only the required Device Object and any required services (readProp) and Device Object properties. When initializing the driver, it is important to select a Device Object Instance ID that is unique on your BACnet network. This is the unique identifier that will be used to identify your Master to the rest of the BACnet network. In addition, it may take some time after initialization before you will be able to communicate on the network, as the first thing that has to happen is that all Masters on the network need to be identified (handled by the Master FSM) and a token needs to be received before your Master can begin transmitting (making requests). This may take a couple of minutes on a large network. You can speed this process up by specifying a maxMaster (to initMaster()) that is smaller than the default (127) -- but only if you are CERTAIN that there are no masters with a MAC address higher than the value you choose. If you fail to follow this rule, you may introduce hard to identify token passing problems on the network for yourself and other BACnet Masters. Currently, this driver only supports the readProperty and writeProperty requests to other BACnet devices. In addition, array property reading and writing is not currently supported. Signed-off-by: Jon Trulson Signed-off-by: Mihai Tudor Panu --- src/bacnetmstp/CMakeLists.txt | 24 + src/bacnetmstp/bacnetmstp.cxx | 880 +++++++++++++++++++++++ src/bacnetmstp/bacnetmstp.h | 716 +++++++++++++++++++ src/bacnetmstp/device-client.c | 1026 +++++++++++++++++++++++++++ src/bacnetmstp/device.h | 465 ++++++++++++ src/bacnetmstp/javaupm_bacnetmstp.i | 23 + src/bacnetmstp/jsupm_bacnetmstp.i | 11 + src/bacnetmstp/pyupm_bacnetmstp.i | 15 + src/bacnetmstp/timer.h | 65 ++ 9 files changed, 3225 insertions(+) create mode 100644 src/bacnetmstp/CMakeLists.txt create mode 100644 src/bacnetmstp/bacnetmstp.cxx create mode 100644 src/bacnetmstp/bacnetmstp.h create mode 100644 src/bacnetmstp/device-client.c create mode 100644 src/bacnetmstp/device.h create mode 100644 src/bacnetmstp/javaupm_bacnetmstp.i create mode 100644 src/bacnetmstp/jsupm_bacnetmstp.i create mode 100644 src/bacnetmstp/pyupm_bacnetmstp.i create mode 100644 src/bacnetmstp/timer.h 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